#!/usr/bin/env python # -*- coding: utf-8 -*- """ Copyright (c) 2019 Red Hat, Inc All rights reserved. This software may be modified and distributed under the terms of the BSD license. See the LICENSE file for details. """ import argparse import copy import logging import os import sys import json import re import subprocess import tempfile from textwrap import dedent from osbs import set_logging from osbs.api import OSBS from osbs.conf import Configuration from osbs.constants import DEFAULT_CONFIGURATION_FILE, DEFAULT_ARRANGEMENT_VERSION from tests.constants import (TEST_BUILD, TEST_IMAGESTREAM, TEST_ORCHESTRATOR_BUILD, TEST_CANCELLED_BUILD) from osbs.exceptions import OsbsException, OsbsResponseException from osbs.cli.capture import setup_json_capture OS_VERSION = "3.9.41" ORCH_BUILD_LOG = dedent("""2017-06-23 17:18:41,791 platform:- - atomic_reactor.foo - DEBUG - this is from the orchestrator build 2017-06-23 17:18:41,791 platform:x86_64 - atomic_reactor.foo - INFO - 2017-06-23 17:18:41,400 platform:- atomic_reactor.foo - DEBUG - this is from a worker build 2017-06-23 05:04:51,334 platform:x86_64 - atomic_reactor.plugins.orchestrate_build - INFO - "ContainersPaused": 0, 2017-06-23 17:18:41,791 - I really like bacon """) BASE_BUILD_LOG = u" líne 1 \n".encode('utf-8') MOCK_CONFIG_MAP = { "apiVersion": "v1", "data": { "special.how": "\"very\"", "special.type": "{\"quark\": \"charm\"}", "config.yaml": "version:1", "config.yml": "version:2", "config.ymlll": "{\"version\":3}", "config.json": "{\"version\":4}" }, "kind": "ConfigMap", "metadata": { "creationTimestamp": "2017-06-14T16:49:47Z", "name": "special-config", "namespace": "osbs-qa01", "resourceVersion": "117005745", "selfLink": "/api/v1/namespaces/osbs-qa01/configmaps/special-config", "uid": "797c8350-5121-11e7-9ce0-0efeb5d2209c" } } DEFAULT_DIR = "tests/mock_jsons" DEFAULT_IMAGESTREAM_SERVER = "quay.io" DEFAULT_IMAGESTREAM_FILE = "prometheus/prometheus" DEFAULT_IMAGESTREAM_TAGS = ['latest', 'master', 'v2.13.0'] def canonize_data(build_data, build_name=TEST_BUILD, build_status=None): build_data["status"]["config"] = { "kind": "BuildConfig", "name": build_name, "namespace": "default", } build_data["metadata"]["name"] = build_name if build_status: build_data["status"]["phase"] = build_status try: del build_data["status"]["logSnippet"] del build_data["status"]["message"] except KeyError: pass return build_data def find_or_make_dir(dir_name): if not os.path.exists(dir_name): os.makedirs(dir_name) class MockCreator(object): def __init__(self): parser = argparse.ArgumentParser(description="osbs test harness mock JSON creator") parser.add_argument("user", action='store', help="name of user to use for Basic Authentication in OSBS") parser.add_argument("--config", action='store', metavar="PATH", help="path to configuration file", default=DEFAULT_CONFIGURATION_FILE) parser.add_argument("--instance", "-i", action='store', metavar="SECTION_NAME", help="section within config for requested instance", default="stage") parser.add_argument("--password", action='store', help="password to use for Basic Authentication in OSBS") parser.add_argument("--mock-dir", metavar="DIR", action="store", default=DEFAULT_DIR, help="mock JSON responses are stored in DIR") parser.add_argument("--imagestream", metavar="IMAGESTREAM", action="store", default=DEFAULT_IMAGESTREAM_FILE, help="Image name for image stream import. Defaults to " + DEFAULT_IMAGESTREAM_FILE) parser.add_argument("--image_server", metavar="IMAGESERVER", action="store", default=DEFAULT_IMAGESTREAM_SERVER, help="Server for image stream import. Defaults to " + DEFAULT_IMAGESTREAM_SERVER) parser.add_argument("--image_tags", metavar="IMAGETAGS", action="store", nargs=3, default=DEFAULT_IMAGESTREAM_TAGS, help="Image stream tags as 3 space separated values.") parser.add_argument("--os-version", metavar="OS_VER", action="store", default=OS_VERSION, help="OpenShift version of the mock JSONs") args = parser.parse_args() self.user = args.user self.password = args.password mock_path = args.mock_dir self.mock_dir = "/".join([mock_path, args.os_version]) find_or_make_dir(self.mock_dir) self.capture_dir = tempfile.mkdtemp() args.git_url = "https://github.com/TomasTomecek/docker-hello-world.git" args.git_branch = "master" args.git_commit = "HEAD" os_conf = Configuration(conf_file=args.config, conf_section=args.instance, cli_args=args) build_conf = Configuration(conf_file=args.config, conf_section=args.instance, cli_args=args) set_logging(level=logging.INFO) self.osbs = OSBS(os_conf, build_conf) setup_json_capture(self.osbs, os_conf, self.capture_dir) self.imagestream_file = args.imagestream self.imagestream_server = args.image_server self.imagestream_tags = args.image_tags self.rh_pattern = re.template("redhat.com") self.ex_pattern = "(\S+\.)*redhat.com" # noqa:W605 def clean_data(self, out_data): if isinstance(out_data, dict): cleaned_data = {} for key, data in out_data.items(): cleaned_data[key] = self.clean_data(data) return cleaned_data elif isinstance(out_data, list): cleaned_data = [] for data in out_data: cleaned_data.append(self.clean_data(data)) return cleaned_data elif isinstance(out_data, str): if re.search(self.rh_pattern, out_data): return re.sub(self.ex_pattern, "example.com", out_data) else: return out_data else: return out_data def comp_write(self, out_name, out_data): cleaned_data = self.clean_data(out_data) out_path = "/".join([self.mock_dir, out_name]) with open(out_path, "w") as outf: try: json.dump(cleaned_data, outf, indent=4) except (ValueError, TypeError): outf.write(json.dumps(cleaned_data, indent=4)) def create_mock_builds_list(self): kwargs = {} # get build list self.osbs.list_builds(**kwargs) # find 'get-namespaces_osbs-stage_builds_-000.json' file and parse it into # 'builds_list.json', 'builds_list_empty.json', 'builds_list_one.json' all_builds = "get-namespaces_osbs-stage_builds_-000.json" all_builds_path = "/".join([self.capture_dir, all_builds]) with open(all_builds_path, "r") as infile: builds_data = json.load(infile) builds_items = copy.copy(builds_data["items"]) builds_data["items"] = [] self.comp_write("builds_list_empty.json", builds_data) if not builds_items: return builds_data["items"].append(builds_items[0]) self.comp_write("builds_list_one.json", builds_data) if len(builds_items) < 2: return builds_data["items"].append(builds_items[1]) self.comp_write("builds_list.json", builds_data) os.remove(all_builds_path) def create_pods_list(self, build_id): self.osbs.get_pod_for_build(build_id) pods_pre = "get-namespaces_osbs-stage_pods_?labelSelector=openshift.io%2Fbuild.name%3D" for i in range(0, 4): try: pods_fname = pods_pre + build_id + "-00{}.json".format(i) pods_inpath = "/".join([self.capture_dir, pods_fname]) os.stat(pods_inpath) break except OSError: continue with open(pods_inpath, "r") as infile: pods_data = json.load(infile) image = "buildroot:latest" pods_items = pods_data["items"] or [] for pod in pods_items: pod_containers = pod["status"]["containerStatuses"] or [] for container in pod_containers: container["imageID"] = "docker-pullable://" + image container["image"] = image self.comp_write("pods.json", pods_data) os.remove(pods_inpath) def create_mock_get_user(self): self.osbs.get_user() user_list = "get-users_~_-000.json" user_list_path = "/".join([self.capture_dir, user_list]) with open(user_list_path, "r") as infile: user_data = json.load(infile) user_data["groups"] = [] user_data["identities"] = None if "fullName" in user_data: del user_data["fullName"] user_data["metadata"]["name"] = "test" user_data["metadata"]["selfLink"] = "/apis/user.openshift.io/v1/users/test" self.comp_write("get_user.json", user_data) os.remove(user_list_path) def create_a_mock_build(self, func, build_name, out_tag, build_args): try: build = func(**build_args) build_id = build.get_build_name() build = self.osbs.wait_for_build_to_get_scheduled(build_id) self.osbs.watch_builds() build = self.osbs.wait_for_build_to_finish(build_id) self.osbs.get_build_logs(build_id) except (subprocess.CalledProcessError, OsbsException): pass watch_data = canonize_data(copy.deepcopy(build.json), build_name, "Complete") watch_obj = { "object": watch_data, "type": "MODIFIED" } out_name = "watch_build_test-" + out_tag + "build-123.json" self.comp_write(out_name, watch_obj) build_fname = "get-namespaces_osbs-stage_builds_" + build_id + "_-000.json" build_path = "/".join([self.capture_dir, build_fname]) with open(build_path, "r") as infile: build_data = canonize_data(json.load(infile), build_name, "Complete") out_name = "build_test-" + out_tag + "build-123.json" self.comp_write(out_name, build_data) os.remove(build_path) return out_name def create_mock_build(self): build_kwargs = { 'git_uri': self.osbs.build_conf.get_git_uri(), 'git_ref': self.osbs.build_conf.get_git_ref(), 'git_branch': self.osbs.build_conf.get_git_branch(), 'user': self.osbs.build_conf.get_user(), 'release': TEST_BUILD, 'platform': "x86_64", 'arrangement_version': DEFAULT_ARRANGEMENT_VERSION, 'scratch': True, } build_name = self.create_a_mock_build(self.osbs.create_worker_build, TEST_BUILD, "", build_kwargs) build_path = "/".join([self.mock_dir, build_name]) if os.stat(build_path): with open(build_path, "r") as infile: build_data = json.load(infile) build_data["kind"] = "BuildConfig" self.comp_write("created_build_config_test-build-config-123.json", build_data) del build_kwargs['platform'] self.create_a_mock_build(self.osbs.create_orchestrator_build, TEST_ORCHESTRATOR_BUILD, "orchestrator-", build_kwargs) def create_mock_imagestream(self): imagestream_repo = "/".join([self.imagestream_server, self.imagestream_file]) try: imagestream = self.osbs.create_image_stream(TEST_IMAGESTREAM, imagestream_repo) except OsbsResponseException: print("imagestream {} already exists.".format(TEST_IMAGESTREAM)) print("run `oc delete imagestream {}` and try again".format(TEST_IMAGESTREAM)) exit(0) self.osbs.import_image_tags(TEST_IMAGESTREAM, self.imagestream_tags, imagestream_repo) for tag in self.imagestream_tags: self.osbs.ensure_image_stream_tag(imagestream.json(), tag) imagestreamimport_fname = "post-namespaces_osbs-stage_imagestreamimports_-000.json" imagestreamimport_path = "/".join([self.capture_dir, imagestreamimport_fname]) imagestreamimport_data = [] imagestream_data = [] with open(imagestreamimport_path, "r") as infile: imagestreamimport_data = json.load(infile) self.osbs.get_image_stream(TEST_IMAGESTREAM) imagestream_fname = "get-namespaces_osbs-stage_imagestreams_test_imagestream-001.json" imagestream_path = "/".join([self.capture_dir, imagestream_fname]) with open(imagestream_path, "r") as infile: imagestream_data = json.load(infile) # I'm not really happy with setting this value, but the imagestream tests in test_conf.py # fail without it imagestream_data["spec"].setdefault("dockerImageRepository", imagestream_data["status"].get("dockerImageRepository")) # override the tags to the values expected by the tasks for i in range(0, 3): tag_line = "7.2.username-{}".format(66 + i) tag_name = "example.com:8888/username/rhel7:" + tag_line spec_tag = imagestream_data["spec"]["tags"][i] spec_tag["from"]["name"] = tag_name spec_tag["name"] = tag_line status_tag = imagestream_data["status"]["tags"][i] status_tag["tag"] = tag_line import_status = imagestreamimport_data["status"] import_status["images"][i]["tag"] = tag_line import_status["import"]["spec"]["tags"][i]["name"] = tag_line import_status["import"]["status"]["tags"][i]["tag"] = tag_line self.comp_write("imagestreamimport.json", imagestreamimport_data) self.comp_write("imagestream.json", imagestream_data) def create_mock_build_other(self): build_kwargs = { 'git_uri': self.osbs.build_conf.get_git_uri(), 'git_ref': self.osbs.build_conf.get_git_ref(), 'git_branch': self.osbs.build_conf.get_git_branch(), 'user': self.osbs.build_conf.get_user(), 'release': TEST_BUILD, 'platform': "x86_64", 'arrangement_version': DEFAULT_ARRANGEMENT_VERSION, 'scratch': True, } build_id = "" try: build = self.osbs.create_worker_build(**build_kwargs) build_id = build.get_build_name() self.osbs.wait_for_build_to_get_scheduled(build_id) self.create_pods_list(build_id) self.osbs.cancel_build(build_id) self.osbs.wait_for_build_to_finish(build_id) self.osbs.get_build_logs(build_id) except OsbsException: self.create_pods_list(build_id) instant_fname = "post-namespaces_osbs-stage_builds_-000.json" instant_path = "/".join([self.capture_dir, instant_fname]) with open(instant_path, "r") as infile: instant_data = canonize_data(json.load(infile)) self.comp_write("instantiated_test-build-config-123.json", instant_data) os.remove(instant_path) cancel_args = [ {"suffix": "_-000-001.json", "version": "get", "phase": None}, {"suffix": "_-000-000.json", "version": "put", "phase": "Cancelled"}, ] for data in cancel_args: build_fname = "get-watch_namespaces_osbs-stage_builds_" + build_id + data["suffix"] build_path = "/".join([self.capture_dir, build_fname]) with open(build_path, "r") as infile: cancel_obj = json.load(infile) cancel_data = canonize_data(copy.deepcopy(cancel_obj["object"]), TEST_CANCELLED_BUILD, data["phase"]) self.comp_write("build_test-build-cancel-123_" + data["version"] + ".json", cancel_data) os.remove(build_path) def create_mock_static_files(self): # these aren't JSON, so just write them out out_path = "/".join([self.mock_dir, "build_test-orchestrator-build-123_logs.txt"]) with open(out_path, "w") as outf: outf.write(ORCH_BUILD_LOG) out_path = "/".join([self.mock_dir, "build_test-build-123_logs.txt"]) with open(out_path, "wb") as outf: outf.write(BASE_BUILD_LOG) self.comp_write("create_config_map.json", MOCK_CONFIG_MAP) def main(): mock_builder = MockCreator() mock_builder.create_mock_builds_list() mock_builder.create_mock_get_user() mock_builder.create_mock_build_other() mock_builder.create_mock_build() mock_builder.create_mock_imagestream() mock_builder.create_mock_static_files() return 0 if __name__ == '__main__': sys.exit(main())