diff --git a/.zuul.yaml b/.zuul.yaml index 3bfbcb70..3c0f6fd7 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -14,11 +14,72 @@ check: jobs: - airship-promenade-linter + - airship-docker-build-gate + gate: jobs: - airship-promenade-linter + - airship-docker-build-gate + + post: + jobs: + - airship-docker-build-post - job: name: airship-promenade-linter run: tools/gate/playbooks/zuul-linter.yaml nodeset: openstack-helm-single-node + +- job: + name: airship-docker-build-base + run: tools/gate/playbooks/docker-image-build.yaml + nodeset: ubuntu-xenial + +- job: + name: airship-docker-build-gate + parent: airship-docker-build-base + vars: + publish: false + tags: + dynamic: + patch_set: true + +- job: + name: airship-docker-build-post + parent: airship-docker-build-base + secrets: + - airship_dockerhub_creds + vars: + publish: true + tags: + dynamic: + branch: true + commit: true + static: + - latest + +- secret: + name: airship_dockerhub_creds + data: + username: !encrypted/pkcs1-oaep + - KIl1I3Ki2tvGCCqlRNE0BPgkJo5Fw8St5eX9gCr9TncYtnvZuusDjlbvJS5jFd4o0Coj8 + 4LniBnmitm+mQiaPrWvM0Op4w8fA12oFA2bF6ZG/R2ZhzDzRSC7ucpoHoNDuZWeSh2v38 + /rNPTRw4wgm6p3tPHeNLelBOkE8WbN/v6Wj03pKGBQC5Z1Ua1BD2DoNZqAfH2mnxrgEs1 + r7jvc3Livw3qvJVqtleRBldxN89GOlH9uU6XUdlUVUBecEv03SfgrRXU5njGa16KunyOE + cs3VdLqh65EXrrx4vc7RPOx+Ql6wshDtD6vhF4zEthEfRdYUK+6rY368b9O5WdOSHJTL5 + yMQwItXYn+4AzPKPwf4dHeJmbHhXp3q7aI57WlMip71ReJNGpS2S2WekNGrPrl/5Fp/Ns + XdpEZwoq2wAVDZe9nsfSAOLICxurQVawpOrJBcCot8zCe/7xYPHVBnvktV08oTIyykMOx + LYtl7Vq/0VwI+aoMfEUPBjuzApvgRIRi0IbkzrEJvD6Y17nrJ7uGbimyYhz76g2GrCPex + mCDZ7DReoX/ECIwpsurib51O7KH4uQh3fX5yrbnuXCQzfdCrX12b5uRPWnfFDMcj6rq4+ + 36N7tof6O6ooTl0fbQSGHK38Smaz7lWt69WW2FjT6IfNiI7rEV3aeACtfbG6JU= + password: !encrypted/pkcs1-oaep + - PleEYq7618O5ujy1Hy3a3nT2WLjYC46PPWnGwra2KKKfvoDSB/wfwLhZ9p9M9OODb5xHJ + Me0V+7bD0xBPK9VQIpOlj1BA3PXcyX6zzVvbCSJKmILFhg81++aGBUnSDuKe+Y7qPTgjI + l5hW69LJkyHtj6WVKWGo+7Le3kjbtEMZt7lqhtdhWlWfFNWoTDfpAzvj/14yRp5xTZPFt + zWYdj2grjjDmFjUHRbdbyUnfNEI+WZeXM5U+0F7jyAEkW3yqBeQV9InM1dhwNFqpGFflk + XXIamyjhf/DpT0e7kBDd8NKvb6m+ubPUE8HG+TLEqcSN0mOxfTYWHx4okrkAXrSDhYyi5 + DWj0lfrr/YE7XE5NnPfmDEgo53lx8yiFmiQ4iz4UtAhjSftUm99EOrL2vFCRawKVW+liw + 9nExTVGUexLiy+DcIAShVWEVwQPtIr/HcleuO4zLfaljNS9NHBke9lHrU+QZ3AZnkd0/A + wcA8OJdGauWiAMvSLqvQEWP3xRBiZe63OATtaWQTmOxzn+9crmIf1eVhCpiNEQNTlmTNe + fB0SPYANzVkAF8+ZAmcGBt24Ivc+uSBqrqzR7lFkbDaEbBrTjddKcfbUCoul0yye+qfPj + cow63ecH56++tyGyfMKpNL5WJ2GpbCPwiB8ZwDQZv7iQD0TpinhJxAmvt5qhcs= diff --git a/tools/gate/playbooks/docker-image-build.yaml b/tools/gate/playbooks/docker-image-build.yaml new file mode 100644 index 00000000..d4f8fabf --- /dev/null +++ b/tools/gate/playbooks/docker-image-build.yaml @@ -0,0 +1,56 @@ +- hosts: all + tasks: + - name: Install Docker (Debian) + block: + - apt: + name: "{{ item }}" + with_items: + - docker.io + - python-pip + when: ansible_os_family == 'Debian' + - pip: + name: docker + version: 2.7.0 + become: True + + - name: Debug tag generation inputs + block: + - debug: + var: publish + - debug: + var: tags + - debug: + var: zuul + - debug: + msg: "{{ tags | to_json }}" + + - name: Determine tags + shell: echo '{{ tags | to_json }}' | python {{ zuul.project.src_dir }}/tools/image_tags.py + environment: + BRANCH: "{{ zuul.branch }}" + CHANGE: "{{ zuul.change }}" + COMMIT: "{{ zuul.newrev }}" + PATCHSET: "{{ zuul.patchset }}" + register: image_tags + + - name: Debug computed tags + debug: + var: image_tags + + - name: Build images + docker_image: + name: airshipit/promenade + dockerfile: "Dockerfile" + path: "{{ zuul.project.src_dir }}" + pull: false + tag: "{{ item }}" + with_items: "{{ image_tags.stdout_lines }}" + become: True + + - name: Push to Docker Hub + block: + - command: docker login -u {{ airship_dockerhub_creds.username }} -p {{ airship_dockerhub_creds.password }} + no_log: True + - command: docker push docker.io/airshipit/promenade:{{ item }} + with_items: "{{ image_tags.stdout_lines }}" + when: publish diff --git a/tools/image_tags.py b/tools/image_tags.py new file mode 100644 index 00000000..9717953e --- /dev/null +++ b/tools/image_tags.py @@ -0,0 +1,113 @@ +#!/bin/python + +import json +import logging +import os +import sys + +LOG = logging.getLogger(__name__) + +LOG_FORMAT = '%(asctime)s %(levelname)-8s %(name)s:%(funcName)s [%(lineno)3d] %(message)s' # noqa + + +class TagGenExeception(Exception): + pass + + +def read_config(stream, env): + config = {} + try: + config['tags'] = json.load(stream) + except ValueError: + LOG.exception('Failed to decode JSON from input stream') + config['tags'] = {} + + LOG.debug('Configuration after reading stream: %s', config) + + config['context'] = { + 'branch': env.get('BRANCH'), + 'change': env.get('CHANGE'), + 'commit': env.get('COMMIT'), + 'ps': env.get('PATCHSET'), + } + + LOG.info('Final configuration: %s', config) + + return config + + +def build_tags(config): + tags = config.get('tags', {}).get('static', []) + LOG.debug('Dynamic tags: %s', tags) + tags.extend(build_dynamic_tags(config)) + LOG.info('All tags: %s', tags) + return tags + + +def build_dynamic_tags(config): + dynamic_tags = [] + + dynamic_tags.extend(_build_branch_tag(config)) + dynamic_tags.extend(_build_commit_tag(config)) + dynamic_tags.extend(_build_ps_tag(config)) + + return dynamic_tags + + +def _build_branch_tag(config): + if _valid_dg(config, 'branch'): + return [config['context']['branch']] + else: + return [] + + +def _build_commit_tag(config): + if _valid_dg(config, 'commit'): + return [config['context']['commit']] + else: + return [] + + +def _build_ps_tag(config): + if _valid_dg(config, 'patch_set', 'change') and _valid_dg( + config, 'patch_set', 'ps'): + return [ + '%s-%s' % (config['context']['change'], config['context']['ps']) + ] + else: + return [] + + +def _valid_dg(config, dynamic_tag, context_name=None): + if context_name is None: + context_name = dynamic_tag + + if config.get('tags', {}).get('dynamic', {}).get(dynamic_tag): + if config.get('context', {}).get(context_name): + return True + else: + raise TagGenExeception('Dynamic tag "%s" requested, but "%s"' + ' not found in context' % (dynamic_tag, + context_name)) + else: + return False + + +def main(): + config = read_config(sys.stdin, os.environ) + tags = build_tags(config) + + for tag in tags: + print(tag) + + +if __name__ == '__main__': + logging.basicConfig(format=LOG_FORMAT, level=logging.WARNING) + try: + main() + except TagGenExeception: + LOG.exception('Failed to generate tags') + sys.exit(1) + except: + LOG.exception('Unexpected exception') + sys.exit(2) diff --git a/tox.ini b/tox.ini index 73a85840..048ab5ec 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ commands = [testenv:fmt] deps = yapf==0.20.0 commands = - yapf -ir {toxinidir}/promenade {toxinidir}/tests + yapf -ir {toxinidir}/promenade {toxinidir}/tests {toxinidir}/tools/image_tags.py [testenv:freeze] deps = -r{toxinidir}/requirements-direct.txt @@ -52,5 +52,5 @@ deps = yapf==0.20.0 flake8==3.5.0 commands = - yapf -rd {toxinidir}/promenade {toxinidir}/tests + yapf -rd {toxinidir}/promenade {toxinidir}/tests {toxinidir}/tools/image_tags.py flake8 {toxinidir}/promenade