cli.py 2.88 KB
Newer Older
David Trudgian's avatar
David Trudgian committed
1
import click
2
3
import json
from os import path
David Trudgian's avatar
David Trudgian committed
4
5
6
import shutil
from multiprocessing import Process

David Trudgian's avatar
David Trudgian committed
7
from . import VERSION
8
from .clair import check_clair, post_layer, get_report, format_report_text, ClairException
David Trudgian's avatar
David Trudgian committed
9
from .util import sha256, wait_net_service, err_and_exit
10
from .image import check_image, image_to_tgz, http_server
David Trudgian's avatar
David Trudgian committed
11

David Trudgian's avatar
David Trudgian committed
12

David Trudgian's avatar
David Trudgian committed
13
@click.command()
14
15
@click.option('--clair-uri', default="http://localhost:6060",
              help='Base URI for your Clair server')
David Trudgian's avatar
David Trudgian committed
16
17
@click.option('--text-output', is_flag=True, help='Report in Text (Default)')
@click.option('--json-output', is_flag=True, help='Report in JSON')
David Trudgian's avatar
David Trudgian committed
18
19
@click.option('--bind-ip', default="127.0.0.1",
              help='IP address that the HTTP server providing image to Clair should listen on')
20
21
@click.option('--bind-port', default=8088,
              help='Port that the HTTP server providing image to Clair should listen on')
David Trudgian's avatar
David Trudgian committed
22
@click.option('--quiet', is_flag=True, help='Suppress progress messages to STDERR')
David Trudgian's avatar
David Trudgian committed
23
@click.version_option(version=VERSION)
David Trudgian's avatar
David Trudgian committed
24
@click.argument('image', required=True)
David Trudgian's avatar
David Trudgian committed
25
def cli(image, clair_uri, text_output, json_output, bind_ip, bind_port, quiet):
David Trudgian's avatar
David Trudgian committed
26

David Trudgian's avatar
David Trudgian committed
27
28
    API_URI = clair_uri + '/v1/'

David Trudgian's avatar
David Trudgian committed
29
    # Check image exists, and export it to a gzipped tar in a temporary directory
David Trudgian's avatar
David Trudgian committed
30
    check_image(image)
David Trudgian's avatar
David Trudgian committed
31
    (tar_dir, tar_file) = image_to_tgz(image, quiet)
David Trudgian's avatar
David Trudgian committed
32

David Trudgian's avatar
David Trudgian committed
33
34
    # Image name for Clair will be the SHA256 of the .tar.gz
    image_name = sha256(tar_file)
David Trudgian's avatar
David Trudgian committed
35
36
    if not quiet:
        click.echo("Image has SHA256: %s" % image_name, err=True)
David Trudgian's avatar
David Trudgian committed
37

David Trudgian's avatar
David Trudgian committed
38
    # Make sure we can talk to Clair OK
39
40
41
42
    try:
        check_clair(API_URI, quiet)
    except ClairException as e:
        err_and_exit(e.message)
David Trudgian's avatar
David Trudgian committed
43

David Trudgian's avatar
David Trudgian committed
44
45
    # Start an HTTP server to serve the .tar.gz from our temporary directory
    # so that Clair can retrieve it
David Trudgian's avatar
David Trudgian committed
46
    httpd = Process(target=http_server, args=(tar_dir, bind_ip, bind_port, quiet))
David Trudgian's avatar
David Trudgian committed
47
    httpd.daemon = True
David Trudgian's avatar
David Trudgian committed
48
    httpd.start()
49
    # Allow up to 30 seconds for the httpd to start and be answering requests
David Trudgian's avatar
David Trudgian committed
50
51
52
    httpd_ready = wait_net_service(bind_ip, bind_port, 30)
    if not httpd_ready:
        httpd.terminate()
53
54
        shutil.rmtree()
        err_and_exit("Error: HTTP server did not become ready\n", 1)
55

David Trudgian's avatar
David Trudgian committed
56
57
58
    image_uri = 'http://%s:%d/%s' % (bind_ip, bind_port, path.basename(tar_file))

    # Register the iamge with Clair as a docker layer that has no parent
59
60
61
62
63
64
    try:
        post_layer(API_URI, image_name, image_uri, quiet)
    except ClairException as e:
        httpd.terminate()
        shutil.rmtree()
        err_and_exit(e.message, 1)
David Trudgian's avatar
David Trudgian committed
65
66
67
68
69
70
71
72
73

    # Done with the .tar.gz so stop serving it and remove the temp dir
    httpd.terminate()
    shutil.rmtree(tar_dir)

    # Retrieve the vulnerability report from Clair
    report = get_report(API_URI, image_name)

    # Spit out the report on STDOUT
David Trudgian's avatar
David Trudgian committed
74
75
76
77
78
    if json_output:
        pretty_report = json.dumps(report, separators=(',', ':'), sort_keys=True, indent=2)
        click.echo(pretty_report)
    else:
        format_report_text(report)