def test_mirror_config_server_hostname(run_skopeo_mock, initialized_db, app, monkeypatch): """ Set REPO_MIRROR_SERVER_HOSTNAME to override SERVER_HOSTNAME config. """ mirror, repo = create_mirror_repo_robot(["latest", "7.1"]) skopeo_calls = [ { "args": [ "/usr/bin/skopeo", "--debug", "inspect", "--tls-verify=True", "docker://registry.example.com/namespace/repository:latest", ], "results": SkopeoResults(True, [], '{"RepoTags": ["latest"]}', ""), }, { "args": [ "/usr/bin/skopeo", "--debug", "copy", "--all", "--remove-signatures", "--src-tls-verify=True", "--dest-tls-verify=True", "--dest-creds", "%s:%s" % (mirror.internal_robot.username, retrieve_robot_token(mirror.internal_robot)), "docker://registry.example.com/namespace/repository:latest", "docker://config_server_hostname/mirror/repo:latest", ], "results": SkopeoResults(True, [], "Success", ""), }, ] def skopeo_test(args, proxy): try: skopeo_call = skopeo_calls.pop(0) assert args == skopeo_call["args"] assert proxy == {} return skopeo_call["results"] except Exception as e: skopeo_calls.append(skopeo_call) raise e run_skopeo_mock.side_effect = skopeo_test monkeypatch.setenv("DEBUGLOG", "true") with patch.dict("data.model.config.app_config", {"REPO_MIRROR_SERVER_HOSTNAME": "config_server_hostname"}): worker = RepoMirrorWorker() worker._process_mirrors() assert [] == skopeo_calls
def test_quote_params(run_skopeo_mock, initialized_db, app): """ Basic test of successful mirror. """ mirror, repo = create_mirror_repo_robot(["latest", "7.1"]) mirror.external_reference = "& rm -rf /;/namespace/repository" mirror.external_registry_username = "******" mirror.save() skopeo_calls = [ { "args": [ "/usr/bin/skopeo", "inspect", "--tls-verify=True", "--creds", "`rm -rf /`", "'docker://& rm -rf /;/namespace/repository:latest'", ], "results": SkopeoResults(True, [], '{"RepoTags": ["latest"]}', ""), }, { "args": [ "/usr/bin/skopeo", "copy", "--all", "--src-tls-verify=True", "--dest-tls-verify=True", "--dest-creds", "%s:%s" % (mirror.internal_robot.username, retrieve_robot_token(mirror.internal_robot)), "--src-creds", "`rm -rf /`", "'docker://& rm -rf /;/namespace/repository:latest'", "docker://localhost:5000/mirror/repo:latest", ], "results": SkopeoResults(True, [], "stdout", "stderr"), }, ] def skopeo_test(args, proxy): try: skopeo_call = skopeo_calls.pop(0) assert args == skopeo_call["args"] assert proxy == {} return skopeo_call["results"] except Exception as e: skopeo_calls.append(skopeo_call) raise e run_skopeo_mock.side_effect = skopeo_test worker = RepoMirrorWorker() worker._process_mirrors() assert [] == skopeo_calls
def test_mirror_unsigned_images(run_skopeo_mock, initialized_db, app): """ Test whether the insecure-policy option is added when a repository is passed with unsigned_images. """ mirror, repo = create_mirror_repo_robot(["latest"], external_registry_config={ "verify_tls": False, "unsigned_images": True }) skopeo_calls = [ { "args": [ "/usr/bin/skopeo", "inspect", "--tls-verify=False", "docker://registry.example.com/namespace/repository:latest", ], "results": SkopeoResults(True, [], '{"RepoTags": ["latest"]}', ""), }, { "args": [ "/usr/bin/skopeo", "--insecure-policy", "copy", "--all", "--remove-signatures", "--src-tls-verify=False", "--dest-tls-verify=True", "--dest-creds", "%s:%s" % (mirror.internal_robot.username, retrieve_robot_token(mirror.internal_robot)), "docker://registry.example.com/namespace/repository:latest", "docker://localhost:5000/mirror/repo:latest", ], "results": SkopeoResults(True, [], "stdout", "stderr"), }, ] def skopeo_test(args, proxy): try: skopeo_call = skopeo_calls.pop(0) assert args == skopeo_call["args"] assert proxy == {} return skopeo_call["results"] except Exception as e: skopeo_calls.append(skopeo_call) raise e run_skopeo_mock.side_effect = skopeo_test worker = RepoMirrorWorker() worker._process_mirrors() assert [] == skopeo_calls
def test_successful_mirror_verbose_logs(run_skopeo_mock, initialized_db, app, monkeypatch): """ Basic test of successful mirror with verbose logs turned on. """ mirror, repo = create_mirror_repo_robot(["latest", "7.1"]) skopeo_calls = [ { "args": [ "/usr/bin/skopeo", "--debug", "inspect", "--tls-verify=True", "docker://registry.example.com/namespace/repository:latest", ], "results": SkopeoResults(True, [], '{"RepoTags": ["latest"]}', ""), }, { "args": [ "/usr/bin/skopeo", "--debug", "copy", "--all", "--remove-signatures", "--src-tls-verify=True", "--dest-tls-verify=True", "--dest-creds", "%s:%s" % (mirror.internal_robot.username, retrieve_robot_token(mirror.internal_robot)), "docker://registry.example.com/namespace/repository:latest", "docker://localhost:5000/mirror/repo:latest", ], "results": SkopeoResults(True, [], "Success", ""), }, ] def skopeo_test(args, proxy): try: skopeo_call = skopeo_calls.pop(0) assert args == skopeo_call["args"] assert proxy == {} return skopeo_call["results"] except Exception as e: skopeo_calls.append(skopeo_call) raise e run_skopeo_mock.side_effect = skopeo_test monkeypatch.setenv("DEBUGLOG", "true") worker = RepoMirrorWorker() worker._process_mirrors() assert [] == skopeo_calls
def test_successful_disabled_sync_now(run_skopeo_mock, initialized_db, app): """ Disabled mirrors still allow "sync now". """ mirror, repo = create_mirror_repo_robot(["latest", "7.1"]) mirror.is_enabled = False mirror.sync_status = RepoMirrorStatus.SYNC_NOW mirror.save() skopeo_calls = [ { "args": [ "/usr/bin/skopeo", "inspect", "--tls-verify=True", "docker://registry.example.com/namespace/repository:latest", ], "results": SkopeoResults(True, [], '{"RepoTags": ["latest"]}', ""), }, { "args": [ "/usr/bin/skopeo", "copy", "--all", "--remove-signatures", "--src-tls-verify=True", "--dest-tls-verify=True", "--dest-creds", "%s:%s" % (mirror.internal_robot.username, retrieve_robot_token(mirror.internal_robot)), "docker://registry.example.com/namespace/repository:latest", "docker://localhost:5000/mirror/repo:latest", ], "results": SkopeoResults(True, [], "stdout", "stderr"), }, ] def skopeo_test(args, proxy): try: skopeo_call = skopeo_calls.pop(0) assert args == skopeo_call["args"] assert proxy == {} return skopeo_call["results"] except Exception as e: skopeo_calls.append(skopeo_call) raise e run_skopeo_mock.side_effect = skopeo_test worker = RepoMirrorWorker() worker._process_mirrors() assert [] == skopeo_calls
def test_successful_mirror(run_skopeo_mock, initialized_db, app): """ Basic test of successful mirror. """ mirror, repo = create_mirror_repo_robot( ["latest", "7.1"], external_registry_config={"verify_tls": False}) skopeo_calls = [ { "args": [ "/usr/bin/skopeo", "inspect", "--tls-verify=False", u"docker://registry.example.com/namespace/repository:latest", ], "results": SkopeoResults(True, [], '{"RepoTags": ["latest"]}', ""), }, { "args": [ "/usr/bin/skopeo", "copy", "--src-tls-verify=False", "--dest-tls-verify=True", "--dest-creds", "%s:%s" % (mirror.internal_robot.username, retrieve_robot_token(mirror.internal_robot)), u"docker://registry.example.com/namespace/repository:latest", u"docker://localhost:5000/mirror/repo:latest", ], "results": SkopeoResults(True, [], "stdout", "stderr"), }, ] def skopeo_test(args, proxy): try: skopeo_call = skopeo_calls.pop(0) assert args == skopeo_call["args"] assert proxy == {} return skopeo_call["results"] except Exception as e: skopeo_calls.append(skopeo_call) raise e run_skopeo_mock.side_effect = skopeo_test worker = RepoMirrorWorker() worker._process_mirrors() assert [] == skopeo_calls
def test_robot(initialized_db): # Create a robot account. user = create_user_noverify('foobar', '*****@*****.**', email_required=False) robot, token = create_robot('foo', user) assert retrieve_robot_token(robot) == token # Ensure we can retrieve its information. found = lookup_robot('foobar+foo') assert found == robot creds = get_pull_credentials('foobar+foo') assert creds is not None assert creds['username'] == 'foobar+foo' assert creds['password'] == token assert verify_robot('foobar+foo', token) == robot with pytest.raises(InvalidRobotException): assert verify_robot('foobar+foo', 'someothertoken') with pytest.raises(InvalidRobotException): assert verify_robot('foobar+unknownbot', token)
def test_robot(initialized_db): # Create a robot account. user = create_user_noverify("foobar", "*****@*****.**", email_required=False) robot, token = create_robot("foo", user) assert retrieve_robot_token(robot) == token # Ensure we can retrieve its information. found = lookup_robot("foobar+foo") assert found == robot creds = get_pull_credentials("foobar+foo") assert creds is not None assert creds["username"] == "foobar+foo" assert creds["password"] == token assert verify_robot("foobar+foo", token) == robot with pytest.raises(InvalidRobotException): assert verify_robot("foobar+foo", "someothertoken") with pytest.raises(InvalidRobotException): assert verify_robot("foobar+unknownbot", token)
def test_rollback(run_skopeo_mock, initialized_db, app): """ Tags in the repo: "updated" - this tag will be updated during the mirror "removed" - this tag will be removed during the mirror "created" - this tag will be created during the mirror """ mirror, repo = create_mirror_repo_robot(["updated", "created", "zzerror"]) _create_tag(repo, "updated") _create_tag(repo, "deleted") skopeo_calls = [ { "args": [ "/usr/bin/skopeo", "inspect", "--tls-verify=True", "docker://registry.example.com/namespace/repository:updated", ], "results": SkopeoResults( True, [], '{"RepoTags": ["latest", "updated", "created", "zzerror"]}', ""), }, { "args": [ "/usr/bin/skopeo", "copy", "--all", "--src-tls-verify=True", "--dest-tls-verify=True", "--dest-creds", "%s:%s" % (mirror.internal_robot.username, retrieve_robot_token(mirror.internal_robot)), "docker://registry.example.com/namespace/repository:created", "docker://localhost:5000/mirror/repo:created", ], "results": SkopeoResults(True, [], "Success", ""), }, { "args": [ "/usr/bin/skopeo", "copy", "--all", "--src-tls-verify=True", "--dest-tls-verify=True", "--dest-creds", "%s:%s" % (mirror.internal_robot.username, retrieve_robot_token(mirror.internal_robot)), "docker://registry.example.com/namespace/repository:updated", "docker://localhost:5000/mirror/repo:updated", ], "results": SkopeoResults(True, [], "Success", ""), }, { "args": [ "/usr/bin/skopeo", "copy", "--all", "--src-tls-verify=True", "--dest-tls-verify=True", "--dest-creds", "%s:%s" % (mirror.internal_robot.username, retrieve_robot_token(mirror.internal_robot)), "docker://registry.example.com/namespace/repository:zzerror", "docker://localhost:5000/mirror/repo:zzerror", ], "results": SkopeoResults(False, [], "", "ERROR"), }, ] def skopeo_test(args, proxy): try: skopeo_call = skopeo_calls.pop(0) assert args == skopeo_call["args"] assert proxy == {} if args[1] == "copy" and args[7].endswith(":updated"): _create_tag(repo, "updated") elif args[1] == "copy" and args[7].endswith(":created"): _create_tag(repo, "created") return skopeo_call["results"] except Exception as e: skopeo_calls.append(skopeo_call) raise e run_skopeo_mock.side_effect = skopeo_test worker = RepoMirrorWorker() worker._process_mirrors() assert [] == skopeo_calls
def perform_mirror(skopeo, mirror): """ Run mirror on all matching tags of remote repository. """ if os.getenv("DEBUGLOG", "false").lower() == "true": verbose_logs = True else: verbose_logs = False mirror = claim_mirror(mirror) if mirror == None: raise PreemptedException emit_log( mirror, "repo_mirror_sync_started", "start", "'%s' with tag pattern '%s'" % (mirror.external_reference, ",".join(mirror.root_rule.rule_value)), ) # Fetch the tags to mirror, being careful to handle exceptions. The 'Exception' is safety net only, allowing # easy communication by user through bug report. tags = [] try: tags = tags_to_mirror(skopeo, mirror) except RepoMirrorSkopeoException as e: emit_log( mirror, "repo_mirror_sync_failed", "end", "'%s' with tag pattern '%s': %s" % (mirror.external_reference, ",".join(mirror.root_rule.rule_value), str(e)), tags=", ".join(tags), stdout=e.stdout, stderr=e.stderr, ) release_mirror(mirror, RepoMirrorStatus.FAIL) return except Exception as e: emit_log( mirror, "repo_mirror_sync_failed", "end", "'%s' with tag pattern '%s': INTERNAL ERROR" % (mirror.external_reference, ",".join(mirror.root_rule.rule_value)), tags=", ".join(tags), stdout="Not applicable", stderr=traceback.format_exc(e), ) release_mirror(mirror, RepoMirrorStatus.FAIL) return if tags == []: emit_log( mirror, "repo_mirror_sync_success", "end", "'%s' with tag pattern '%s'" % (mirror.external_reference, ",".join(mirror.root_rule.rule_value)), tags="No tags matched", ) release_mirror(mirror, RepoMirrorStatus.SUCCESS) return # Sync tags now_ms = database.get_epoch_timestamp_ms() overall_status = RepoMirrorStatus.SUCCESS try: delete_obsolete_tags(mirror, tags) try: username = ( mirror.external_registry_username.decrypt() if mirror.external_registry_username else None ) password = ( mirror.external_registry_password.decrypt() if mirror.external_registry_password else None ) except DecryptionFailureException: logger.exception( "Failed to decrypt username or password for mirroring %s", mirror.repository ) raise dest_server = ( app.config.get("REPO_MIRROR_SERVER_HOSTNAME", None) or app.config["SERVER_HOSTNAME"] ) for tag in tags: src_image = "docker://%s:%s" % (mirror.external_reference, tag) dest_image = "docker://%s/%s/%s:%s" % ( dest_server, mirror.repository.namespace_user.username, mirror.repository.name, tag, ) with database.CloseForLongOperation(app.config): result = skopeo.copy( src_image, dest_image, src_tls_verify=mirror.external_registry_config.get("verify_tls", True), dest_tls_verify=app.config.get( "REPO_MIRROR_TLS_VERIFY", True ), # TODO: is this a config choice or something else? src_username=username, src_password=password, dest_username=mirror.internal_robot.username, dest_password=retrieve_robot_token(mirror.internal_robot), proxy=mirror.external_registry_config.get("proxy", {}), verbose_logs=verbose_logs, ) if not result.success: overall_status = RepoMirrorStatus.FAIL emit_log( mirror, "repo_mirror_sync_tag_failed", "finish", "Source '%s' failed to sync" % src_image, tag=tag, stdout=result.stdout, stderr=result.stderr, ) logger.info("Source '%s' failed to sync." % src_image) else: emit_log( mirror, "repo_mirror_sync_tag_success", "finish", "Source '%s' successful sync" % src_image, tag=tag, stdout=result.stdout, stderr=result.stderr, ) logger.info("Source '%s' successful sync." % src_image) mirror = claim_mirror(mirror) if mirror is None: emit_log( mirror, "repo_mirror_sync_failed", "lost", "'%s' with tag pattern '%s'" % (mirror.external_reference, ",".join(mirror.root_rule.rule_value)), ) except Exception as e: overall_status = RepoMirrorStatus.FAIL emit_log( mirror, "repo_mirror_sync_failed", "end", "'%s' with tag pattern '%s': INTERNAL ERROR" % (mirror.external_reference, ",".join(mirror.root_rule.rule_value)), tags=", ".join(tags), stdout="Not applicable", stderr=traceback.format_exc(e), ) release_mirror(mirror, overall_status) return finally: if overall_status == RepoMirrorStatus.FAIL: emit_log( mirror, "repo_mirror_sync_failed", "lost", "'%s' with tag pattern '%s'" % (mirror.external_reference, ",".join(mirror.root_rule.rule_value)), ) rollback(mirror, now_ms) else: emit_log( mirror, "repo_mirror_sync_success", "end", "'%s' with tag pattern '%s'" % (mirror.external_reference, ",".join(mirror.root_rule.rule_value)), tags=", ".join(tags), ) release_mirror(mirror, overall_status) return overall_status