Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add chartpress --list-images #96

Merged
merged 5 commits into from
Oct 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 41 additions & 11 deletions chartpress.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import re
import shutil
import subprocess
import sys
from tempfile import TemporaryDirectory

import docker
Expand All @@ -33,14 +34,26 @@
yaml.indent(mapping=2, offset=2, sequence=4)


def log(message):
"""Print messages to stderr

to avoid conflicts with piped output, e.g. `--list-images`
"""
print(message, file=sys.stderr)


def run_cmd(call, cmd, *, echo=True, **kwargs):
"""Run a command and echo it first"""
if echo:
print('$> ' + ' '.join(map(pipes.quote, cmd)))
log("$> " + " ".join(map(pipes.quote, cmd)))
return call(cmd, **kwargs)


check_call = partial(run_cmd, subprocess.check_call)
def check_call(cmd, **kwargs):
kwargs.setdefault("stdout", sys.stderr.fileno())
return run_cmd(subprocess.check_call, cmd, **kwargs)


check_output = partial(run_cmd, subprocess.check_output)


Expand Down Expand Up @@ -381,15 +394,15 @@ def build_images(prefix, images, tag=None, push=False, force_push=False, chart_v
)
build_image(image_spec, context_path, dockerfile_path=dockerfile_path, build_args=build_args)
else:
print(f"Skipping build for {image_spec}, it already exists")
log(f"Skipping build for {image_spec}, it already exists")

if push or force_push:
if force_push or image_needs_pushing(image_spec):
check_call([
'docker', 'push', image_spec
])
else:
print(f"Skipping push for {image_spec}, already on registry")
log(f"Skipping push for {image_spec}, already on registry")
return value_modifications


Expand Down Expand Up @@ -419,7 +432,7 @@ def build_values(name, values_mods):
for repo_key in keys:
before = mod_obj.get(repo_key, None)
if before != value['repository']:
print(f"Updating {values_file}: {key}.{repo_key}: {value}")
log(f"Updating {values_file}: {key}.{repo_key}: {value}")
mod_obj[repo_key] = value['repository']
else:
possible_keys = ' or '.join(IMAGE_REPOSITORY_KEYS)
Expand All @@ -429,7 +442,7 @@ def build_values(name, values_mods):

before = mod_obj.get('tag', None)
if before != value['tag']:
print(f"Updating {values_file}: {key}.tag: {value}")
log(f"Updating {values_file}: {key}.tag: {value}")
mod_obj['tag'] = value['tag']
elif isinstance(mod_obj, str):
# scalar image string, not dict with separate repository, tag keys
Expand All @@ -439,7 +452,7 @@ def build_values(name, values_mods):
except (KeyError, IndexError):
before = None
if before != image:
print(f"Updating {values_file}: {key}: {image}")
log(f"Updating {values_file}: {key}: {image}")
parent[last_part] = image
else:
raise TypeError(
Expand Down Expand Up @@ -500,7 +513,7 @@ def build_chart(name, version=None, paths=None, long=False):
version = _get_identifier(latest_tag_in_branch, n_commits, chart_commit, long)

if chart['version'] != version:
print(f"Updating {chart_file}: version: {version}")
log(f"Updating {chart_file}: version: {version}")
chart['version'] = version

with open(chart_file, 'w') as f:
Expand Down Expand Up @@ -589,14 +602,14 @@ def publish_pages(chart_name, chart_version, chart_repo_github_path, chart_repo_
class ActionStoreDeprecated(argparse.Action):
"""Used with argparse as a deprecation action."""
def __call__(self, parser, namespace, values, option_string=None):
print(f"Warning: use of {'|'.join(self.option_strings)} is deprecated.")
log(f"Warning: use of {'|'.join(self.option_strings)} is deprecated.")
setattr(namespace, self.dest, values)


class ActionAppendDeprecated(argparse.Action):
"""Used with argparse as a deprecation action."""
def __call__(self, parser, namespace, values, option_string=None):
print(f"Warning: use of {'|'.join(self.option_strings)} is deprecated.")
log(f"Warning: use of {'|'.join(self.option_strings)} is deprecated.")
if not getattr(namespace, self.dest):
setattr(namespace, self.dest, [])
getattr(namespace, self.dest).append(values)
Expand Down Expand Up @@ -659,12 +672,17 @@ def main(args=None):
action='store_true',
help='Enforce the image build step, regardless of if the image already is available either locally or remotely.',
)

argparser.add_argument(
"--list-images",
action="store_true",
help="print list of images to stdout. Images will not be built.",
)
argparser.add_argument(
'--version',
action='store_true',
help='Print current chartpress version and exit.',
)

argparser.add_argument(
'--commit-range',
action=ActionStoreDeprecated,
Expand All @@ -677,6 +695,9 @@ def main(args=None):
print(f"chartpress version {__version__}")
return

if args.list_images:
args.skip_build = True

with open('chartpress.yaml') as f:
config = yaml.load(f)

Expand Down Expand Up @@ -720,6 +741,15 @@ def main(args=None):
skip_build=args.skip_build or args.reset,
long=args.long,
)
if args.list_images:
seen_images = set()
for key, image_dict in value_mods.items():
image = "{repository}:{tag}".format(**image_dict)
if image not in seen_images:
print(image)
# record image, in case the same image occurs in multiple places
seen_images.add(image)
return
build_values(chart['name'], value_mods)

if args.publish_chart:
Expand Down
21 changes: 21 additions & 0 deletions tests/test_chartpress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import sys
from subprocess import run, PIPE


def test_list_images(git_repo):
p = run(
[sys.executable, "-m", "chartpress", "--list-images"],
check=True,
stdout=PIPE,
stderr=PIPE,
)
stdout = p.stdout.decode("utf8").strip()
# echo stdout/stderr for debugging
sys.stderr.write(p.stderr.decode("utf8", "replace"))
sys.stdout.write(stdout)

images = stdout.strip().splitlines()
assert len(images) == 1
# split hash_suffix which will be different every run
pre_hash, hash_suffix = images[0].rsplit(".", 1)
assert pre_hash == "testchart/testimage:0.0.1-n001"
13 changes: 10 additions & 3 deletions tests/test_repo_interactions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import os
import sys

import chartpress


def test_git_repo_fixture(git_repo):
# assert we use the git repo as our current working directory
assert git_repo.working_dir == os.getcwd()
Expand All @@ -27,7 +29,7 @@ def test_chartpress_run(git_repo, capfd):

# run chartpress
out = _capture_output([], capfd)

# verify image was built
# verify the fallback tag of "0.0.1" when a tag is missing
assert f"Successfully tagged testchart/testimage:{tag}" in out
Expand Down Expand Up @@ -244,7 +246,7 @@ def test_chartpress_run_alternative(git_repo_alternative, capfd):
assert f"Successfully tagged test-image-name-configuration:{tag}" in out


def _capture_output(args, capfd):
def _capture_output(args, capfd, expect_output=False):
"""
Calls chartpress given provided arguments and captures the output during the
call.
Expand All @@ -260,13 +262,18 @@ def _capture_output(args, capfd):
_, _ = capfd.readouterr()
chartpress.main(args)
out, err = capfd.readouterr()
if not expect_output:
assert out == ""

# since the output was captured, print it back out again for debugging
# purposes if a test fails for example
header = f'--- chartpress {" ".join(args)} ---'
footer = "-" * len(header)
print()
print(header)
print("out:")
print(out)
print("err:")
print(err, file=sys.stderr)
print(footer)
return out
return err