Add API test scripts for tag immutability and scan signed image

1. Add 7 API python test scripts for tag immutability;
2. Swagger file has object struct defination issue on imuutability policy rule, because
   this policy is in shared with retention rule, therefore, they should be unified to
   the same  type of rule;
3. Fixed a timeout issue of action keyword, waiting for a long period;
4. Add API test scripts for scan signed image.

Signed-off-by: danfengliu <danfengl@vmware.com>
This commit is contained in:
danfengliu 2020-11-03 07:32:13 +00:00
parent 99d818f4db
commit 1b127b16c5
23 changed files with 542 additions and 111 deletions

View File

@ -2893,7 +2893,7 @@ paths:
schema:
type: array
items:
$ref: '#/definitions/ImmutableTagRule'
$ref: '#/definitions/RetentionRule'
'400':
description: Illegal format of provided ID value.
'401':
@ -2913,10 +2913,11 @@ paths:
format: int64
required: true
description: Relevant project ID.
- name: immutabletagrule
- name: RetentionRule
in: body
required: true
schema:
$ref: '#/definitions/ImmutableTagRule'
$ref: '#/definitions/RetentionRule'
tags:
- Products
responses:
@ -2946,10 +2947,11 @@ paths:
format: int64
required: true
description: Immutable tag rule ID.
- name: immutabletagrule
- name: RetentionRule
in: body
required: true
schema:
$ref: '#/definitions/ImmutableTagRule'
$ref: '#/definitions/RetentionRule'
tags:
- Products
responses:
@ -5407,20 +5409,6 @@ definitions:
enabled:
type: boolean
description: The quota is enable or disable
ImmutableTagRule:
type: object
properties:
id:
type: integer
format: int64
project_id:
type: integer
format: int64
tag_filter:
type: string
enabled:
type: boolean
ScannerRegistration:
type: object
description: |

View File

@ -13,7 +13,7 @@ class Artifact(base.Base, object):
client = self._get_client(**kwargs)
return client.list_artifacts(project_name, repo_name)
def get_reference_info(self, project_name, repo_name, reference, ignore_not_found = False,**kwargs):
def get_reference_info(self, project_name, repo_name, reference, expect_status_code = 200, ignore_not_found = False,**kwargs):
client = self._get_client(**kwargs)
params = {}
if "with_signature" in kwargs:
@ -22,12 +22,21 @@ class Artifact(base.Base, object):
params["with_tag"] = kwargs["with_tag"]
if "with_scan_overview" in kwargs:
params["with_scan_overview"] = kwargs["with_scan_overview"]
if "with_immutable_status" in kwargs:
params["with_immutable_status"] = kwargs["with_immutable_status"]
try:
return client.get_artifact_with_http_info(project_name, repo_name, reference, **params)
data, status_code, _ = client.get_artifact_with_http_info(project_name, repo_name, reference, **params)
return data
except ApiException as e:
if e.status == 404 and ignore_not_found == True:
return []
return None
else:
raise Exception("Failed to get reference, {} {}".format(e.status, e.body))
else:
base._assert_status_code(expect_status_code, status_code)
base._assert_status_code(200, status_code)
return None
def delete_artifact(self, project_name, repo_name, reference, expect_status_code = 200, expect_response_body = None, **kwargs):
client = self._get_client(**kwargs)
@ -39,9 +48,9 @@ class Artifact(base.Base, object):
if expect_response_body is not None:
base._assert_status_body(expect_response_body, e.body)
return
base._assert_status_code(expect_status_code, status_code)
base._assert_status_code(200, status_code)
else:
base._assert_status_code(expect_status_code, status_code)
base._assert_status_code(200, status_code)
def get_addition(self, project_name, repo_name, reference, addition, **kwargs):
client = self._get_client(**kwargs)
@ -62,10 +71,10 @@ class Artifact(base.Base, object):
if expect_response_body is not None:
base._assert_status_body(expect_response_body, e.body)
return
base._assert_status_code(expect_status_code, status_code)
base._assert_status_code(201, status_code)
return data
else:
base._assert_status_code(expect_status_code, status_code)
base._assert_status_code(201, status_code)
return data
def create_tag(self, project_name, repo_name, reference, tag_name, expect_status_code = 201, ignore_conflict = False, **kwargs):
client = self._get_client(**kwargs)
@ -75,12 +84,19 @@ class Artifact(base.Base, object):
except ApiException as e:
if e.status == 409 and ignore_conflict == True:
return
base._assert_status_code(expect_status_code, status_code)
else:
raise Exception("Create tag error, {}.".format(e.body))
else:
base._assert_status_code(expect_status_code, status_code)
def delete_tag(self, project_name, repo_name, reference, tag_name, expect_status_code = 200, **kwargs):
client = self._get_client(**kwargs)
_, status_code, _ = client.delete_tag_with_http_info(project_name, repo_name, reference, tag_name)
base._assert_status_code(expect_status_code, status_code)
try:
_, status_code, _ = client.delete_tag_with_http_info(project_name, repo_name, reference, tag_name)
except ApiException as e:
base._assert_status_code(expect_status_code, e.status)
else:
base._assert_status_code(expect_status_code, status_code)
def check_image_scan_result(self, project_name, repo_name, reference, expected_scan_status = "Success", **kwargs):
timeout_count = 30
@ -91,7 +107,7 @@ class Artifact(base.Base, object):
if (timeout_count == 0):
break
artifact = self.get_reference_info(project_name, repo_name, reference, **kwargs)
scan_status = artifact[0].scan_overview['application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0'].scan_status
scan_status = artifact.scan_overview['application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0'].scan_status
if scan_status == expected_scan_status:
return
raise Exception("Scan image result is {}, not as expected {}.".format(scan_status, expected_scan_status))
@ -99,10 +115,10 @@ class Artifact(base.Base, object):
def check_reference_exist(self, project_name, repo_name, reference, ignore_not_found = False, **kwargs):
artifact = self.get_reference_info( project_name, repo_name, reference, ignore_not_found=ignore_not_found, **kwargs)
return {
0: False,
}.get(len(artifact), True)
None: False,
}.get(artifact, True)
def waiting_for_reference_exist(self, project_name, repo_name, reference, ignore_not_found = False, period = 60, loop_count = 8, **kwargs):
def waiting_for_reference_exist(self, project_name, repo_name, reference, ignore_not_found = True, period = 60, loop_count = 8, **kwargs):
_loop_count = loop_count
while True:
print("Waiting for reference {} round...".format(_loop_count))
@ -114,4 +130,4 @@ class Artifact(base.Base, object):
if artifact and artifact !=[]:
return artifact
time.sleep(period)
raise Exception("Referencet is not exist {} {} {}.".format(project_name, repo_name, reference))
raise Exception("Reference is not exist {} {} {}.".format(project_name, repo_name, reference))

View File

@ -65,7 +65,7 @@ def _assert_status_code(expect_code, return_code):
raise Exception(r"HTTPS status code s not as we expected. Expected {}, while actual HTTPS status code is {}.".format(expect_code, return_code))
def _assert_status_body(expect_status_body, returned_status_body):
if expect_status_body.strip() != returned_status_body.strip():
if str(returned_status_body.strip()).lower().find(expect_status_body.lower()) < 0:
raise Exception(r"HTTPS status body s not as we expected. Expected {}, while actual HTTPS status body is {}.".format(expect_status_body, returned_status_body))
def _random_name(prefix):

View File

@ -132,22 +132,19 @@ class DockerAPI(object):
raise Exception(r" Docker tag image {} failed, error is [{}]".format (image, str(err)))
def docker_image_push(self, harbor_registry, tag, expected_error_message = None):
caught_err = False
ret = ""
ret = None
if expected_error_message is "":
expected_error_message = None
try:
self.DCLIENT.push(harbor_registry, tag)
return ret
ret = self.DCLIENT.push(harbor_registry, tag)
except Exception as err:
caught_err = True
if expected_error_message is not None:
print( "docker image push error:", str(err))
if str(err).lower().find(expected_error_message.lower()) < 0:
raise Exception(r"Push image: Return message {} is not as expected {}".format(str(err), expected_error_message))
else:
raise Exception(r" Docker push image {} failed, error is [{}]".format (harbor_registry, message))
if caught_err == False:
else:
if expected_error_message is not None:
if str(ret).lower().find(expected_error_message.lower()) < 0:
raise Exception(r" Failed to catch error [{}] when push image {}, return message: {}".

View File

@ -13,8 +13,10 @@ def pull_harbor_image(registry, username, password, image, tag, expected_login_e
return
time.sleep(2)
ret = _docker_api.docker_image_pull(r'{}/{}'.format(registry, image), tag = tag, expected_error_message = expected_error_message)
print("Docker pull image return message: {}".format(ret))
def push_image_to_project(project_name, registry, username, password, image, tag, expected_login_error_message = None, expected_error_message = None, profix_for_image = None, new_image=None):
print("Start to push image {}/{}/{}:{}".format(registry, project_name, image, tag) )
_docker_api = DockerAPI()
_docker_api.docker_login(registry, username, password, expected_error_message = expected_login_error_message)
time.sleep(2)
@ -22,15 +24,14 @@ def push_image_to_project(project_name, registry, username, password, image, tag
return
_docker_api.docker_image_pull(image, tag = tag)
time.sleep(2)
original_name = image
image = new_image or image
if profix_for_image == None:
new_harbor_registry, new_tag = _docker_api.docker_image_tag(r'{}:{}'.format(image, tag), r'{}/{}/{}'.format(registry, project_name, image))
new_harbor_registry, new_tag = _docker_api.docker_image_tag(r'{}:{}'.format(original_name, tag), r'{}/{}/{}'.format(registry, project_name, image), tag = tag)
else:
new_harbor_registry, new_tag = _docker_api.docker_image_tag(r'{}:{}'.format(image, tag), r'{}/{}/{}/{}'.format(registry, project_name, profix_for_image, image))
new_harbor_registry, new_tag = _docker_api.docker_image_tag(r'{}:{}'.format(original_name, tag), r'{}/{}/{}/{}'.format(registry, project_name, profix_for_image, image), tag = tag)
time.sleep(2)
_docker_api.docker_image_push(new_harbor_registry, new_tag, expected_error_message = expected_error_message)
return r'{}/{}'.format(project_name, image), new_tag
@ -146,4 +147,4 @@ class Repository(base.Base, object):
base._assert_status_code(expect_status_code, status_code)
base._assert_status_code(200, status_code)
return data
return data

View File

@ -1,6 +1,93 @@
# -*- coding: utf-8 -*-
import base
import swagger_client
class Tag_Immutability(base.Base):
# -*- coding: utf-8 -*-
import base
import swagger_client
from swagger_client.rest import ApiException
class Tag_Immutability(base.Base):
def create_tag_immutability_policy_rule(self, project_id, selector_repository_decoration = "repoMatches",
selector_repository="**", selector_tag_decoration = "matches",
selector_tag="**", expect_status_code = 201, **kwargs):
#repoExcludes,excludes
client = self._get_client(**kwargs)
retention_rule = swagger_client.RetentionRule(
action="immutable",
template="immutable_template",
priority = 0,
scope_selectors={
"repository": [
{
"kind": "doublestar",
"decoration": selector_repository_decoration,
"pattern": selector_repository
}
]
},
tag_selectors=[
{
"kind": "doublestar",
"decoration": selector_tag_decoration,
"pattern": selector_tag
}
]
)
try:
_, status_code, header = client.projects_project_id_immutabletagrules_post_with_http_info(project_id, retention_rule)
except ApiException as e:
base._assert_status_code(expect_status_code, e.status)
else:
base._assert_status_code(expect_status_code, status_code)
base._assert_status_code(201, status_code)
return base._get_id_from_header(header)
def list_tag_immutability_policy_rules(self, project_id, **kwargs):
client = self._get_client(**kwargs)
return client.projects_project_id_immutabletagrules_get(project_id)
def get_rule(self, project_id, rule_id, **kwargs):
rules = self.list_tag_immutability_policy_rules(project_id, **kwargs)
for r in rules:
if r.id == rule_id:
return r
return None
def update_tag_immutability_policy_rule(self, project_id, rule_id, selector_repository_decoration = None,
selector_repository=None, selector_tag_decoration = None,
selector_tag=None, disabled = None, expect_status_code = 200, **kwargs):
rule = self.get_rule( project_id, rule_id,**kwargs)
if selector_repository_decoration:
rule.scope_selectors["repository"][0].decoration = selector_repository_decoration
if selector_repository:
rule.scope_selectors["repository"][0].pattern = selector_repository
if selector_tag_decoration:
rule.tag_selectors[0].decoration = selector_tag_decoration
if selector_tag:
rule.tag_selectors[0].pattern = selector_tag
if disabled is not None:
rule.disabled = disabled
client = self._get_client(**kwargs)
try:
_, status_code, header = client.projects_project_id_immutabletagrules_id_put_with_http_info(project_id, rule_id, rule)
except ApiException as e:
base._assert_status_code(expect_status_code, e.status)
if expect_response_body is not None:
base._assert_status_body(expect_response_body, e.body)
else:
base._assert_status_code(expect_status_code, status_code)
base._assert_status_code(200, status_code)
return base._get_id_from_header(header)
def create_rule(self, project_id, selector_repository_decoration = "repoMatches", selector_repository="**",
selector_tag_decoration = "matches", selector_tag="**",
expect_status_code = 201, disabled = False, **kwargs):
rule_id = self.create_tag_immutability_policy_rule(project_id, selector_repository_decoration = selector_repository_decoration,
selector_repository = selector_repository,
selector_tag_decoration = selector_tag_decoration,
selector_tag = selector_tag, expect_status_code = expect_status_code, **kwargs)
if expect_status_code != 201:
return
self.update_tag_immutability_policy_rule(project_id, rule_id, selector_repository_decoration = selector_repository_decoration,
selector_repository = selector_repository, selector_tag_decoration = selector_tag_decoration,
selector_tag = selector_tag, disabled = disabled, expect_status_code = 200, **kwargs)
return rule_id

View File

@ -76,7 +76,7 @@ class TestAssignRoleToLdapGroup(unittest.TestCase):
repo_name_dev, _ = push_image_to_project(project_name, harbor_server, USER_DEV["username"], USER_DEV["password"], USER_DEV["repo"], "latest")
artifacts = self.artifact.list_artifacts(project_name, USER_DEV["repo"], **USER_DEV)
self.assertTrue(len(artifacts) == 1)
push_image_to_project(project_name, harbor_server, USER_GUEST["username"], USER_GUEST["password"], USER_GUEST["repo"], "latest")
push_image_to_project(project_name, harbor_server, USER_GUEST["username"], USER_GUEST["password"], USER_GUEST["repo"], "latest", expected_error_message = "unauthorized to access repository")
artifacts = self.artifact.list_artifacts(project_name, USER_GUEST["repo"], **USER_GUEST)
self.assertTrue(len(artifacts) == 0)

View File

@ -97,19 +97,19 @@ class TestProjects(unittest.TestCase):
src_tag_data = self.artifact.get_reference_info(TestProjects.project_src_repo_name, TestProjects.src_repo_name.split('/')[1], tag_name, **TestProjects.USER_RETAG_CLIENT)
TestProjects.dst_repo_name = TestProjects.project_dst_repo_name+"/"+ dst_repo_sub_name
#8. Retag image in project(PA) to project(PB), it should be forbidden;
self.artifact.copy_artifact(TestProjects.project_dst_repo_name, dst_repo_sub_name, TestProjects.src_repo_name+"@"+src_tag_data[0].digest, expect_status_code=403, **TestProjects.USER_RETAG_CLIENT)
self.artifact.copy_artifact(TestProjects.project_dst_repo_name, dst_repo_sub_name, TestProjects.src_repo_name+"@"+src_tag_data.digest, expect_status_code=403, **TestProjects.USER_RETAG_CLIENT)
#9. Update role of user-retag as admin member of project(PB);
self.project.update_project_member_role(TestProjects.project_dst_repo_id, retag_member_id, 1, **ADMIN_CLIENT)
#10. Retag image in project(PA) to project(PB), it should be successful;
self.artifact.copy_artifact(TestProjects.project_dst_repo_name, dst_repo_sub_name, TestProjects.src_repo_name+"@"+src_tag_data[0].digest, **TestProjects.USER_RETAG_CLIENT)
self.artifact.copy_artifact(TestProjects.project_dst_repo_name, dst_repo_sub_name, TestProjects.src_repo_name+"@"+src_tag_data.digest, **TestProjects.USER_RETAG_CLIENT)
#11. Get repository(RB)'s image tag detail information;
dst_tag_data = self.artifact.get_reference_info(TestProjects.project_dst_repo_name, dst_repo_sub_name, tag_name, **TestProjects.USER_RETAG_CLIENT)
#12. Read digest of retaged image, it must be the same with the image in repository(RA);
self.assertEqual(src_tag_data[0].digest, dst_tag_data[0].digest)
self.assertEqual(src_tag_data.digest, dst_tag_data.digest)
#13. Pull image from project(PB) by user_retag, it must be successful;"
pull_harbor_image(harbor_server, user_retag_name, user_retag_password, TestProjects.dst_repo_name, tag_name)

View File

@ -71,7 +71,7 @@ class TestProjects(unittest.TestCase):
#4. Image(IA) should exist;
artifact = self.artifact.get_reference_info(TestProjects.project_content_trust_name, image, tag, **TestProjects.USER_CONTENT_TRUST_CLIENT)
self.assertEqual(artifact[0].tags[0].name, tag)
self.assertEqual(artifact.tags[0].name, tag)
#5. Pull image(IA) successfully;
pull_harbor_image(harbor_server, admin_name, admin_password, TestProjects.repo_name, tag)

View File

@ -114,19 +114,19 @@ class TestProxyCache(unittest.TestCase):
#10. Manifest index pulled by docker CLI should be cached;
ret_index_by_d = self.artifact.waiting_for_reference_exist(project_name, urllib.parse.quote(index_repo_name,'utf-8'), index_for_docker["tag"], **USER_CLIENT)
print("Index's reference by docker CLI:",ret_index_by_d[0].references)
self.assertTrue(len(ret_index_by_d[0].references) == 1)
print("Index's reference by docker CLI:", ret_index_by_d.references)
self.assertTrue(len(ret_index_by_d.references) == 1)
#11. Manifest index pulled by ctr CLI should be cached;
ret_index_by_c = self.artifact.waiting_for_reference_exist(project_name, urllib.parse.quote(index_repo_name_for_ctr,'utf-8'), index_for_ctr["tag"], **USER_CLIENT)
print("Index's reference by ctr CLI:",ret_index_by_c[0].references)
self.assertTrue(len(ret_index_by_c[0].references) == 1)
print("Index's reference by ctr CLI:", ret_index_by_c.references)
self.assertTrue(len(ret_index_by_c.references) == 1)
def test_proxy_cache_from_harbor(self):
self.do_validate("harbor")
def test_proxy_cache_from_dockerhub(self):
self.do_validate("docker-hub")
#def test_proxy_cache_from_dockerhub(self):
# self.do_validate("docker-hub")
def suite():
suite = unittest.TestSuite(unittest.makeSuite(TestProxyCache))

View File

@ -76,8 +76,8 @@ class TestProjects(unittest.TestCase):
#5.1 Get chart(CA) by reference successfully;
artifact = self.artifact.get_reference_info(TestProjects.project_push_chart_name, self.repo_name, self.verion, **TestProjects.USER_CLIENT)
self.assertEqual(artifact[0].type, 'CHART')
self.assertEqual(artifact[0].tags[0].name, self.verion)
self.assertEqual(artifact.type, 'CHART')
self.assertEqual(artifact.tags[0].name, self.verion)
#5.2 Chart bundle can be pulled by ctr successfully;
#oci_ref = harbor_server+"/"+TestProjects.project_push_chart_name+"/"+self.repo_name+":"+self.verion

View File

@ -92,8 +92,8 @@ class TestProjects(unittest.TestCase):
artifact = self.artifact.get_reference_info(TestProjects.project_push_bundle_name, self.cnab_repo_name, reference_sha256, **TestProjects.USER_CLIENT)
#8. Verify artifact information;
self.assertEqual(artifact[0].type, 'CNAB')
self.assertEqual(artifact[0].digest, reference_sha256)
self.assertEqual(artifact.type, 'CNAB')
self.assertEqual(artifact.digest, reference_sha256)
if __name__ == '__main__':
unittest.main()

View File

@ -67,7 +67,7 @@ class TestProjects(unittest.TestCase):
#5. Get and verify artifacts by tag;
artifact = self.artifact.get_reference_info(TestProjects.project_name, self.repo_name, self.tag, **TestProjects.USER_CLIENT)
self.assertEqual(artifact[0].tags[0].name, self.tag)
self.assertEqual(artifact.tags[0].name, self.tag)
#6. ORAS CLI pull artifacts index by tag;
md5_list_pull = library.oras.oras_pull(harbor_server, user_name, user_001_password, TestProjects.project_name, self.repo_name, self.tag)

View File

@ -80,7 +80,7 @@ class TestProjects(unittest.TestCase):
full_name = urllib.parse.quote(profix+"/"+image,'utf-8')
artifact = self.artifact.get_reference_info(TestProjects.project_sign_image_name, full_name, tag, **TestProjects.USER_sign_image_CLIENT)
self.assertEqual(artifact[0].type, 'IMAGE')
self.assertEqual(artifact.type, 'IMAGE')
if __name__ == '__main__':
unittest.main()

View File

@ -95,10 +95,10 @@ class TestProjects(unittest.TestCase):
#6. Get index(IA) by reference successfully;
index_data = self.artifact.get_reference_info(TestProjects.project_push_index_name, self.index_name, self.index_tag, **TestProjects.USER_CLIENT)
manifests_sha256_harbor_ret = [index_data[0].references[1].child_digest, index_data[0].references[0].child_digest]
manifests_sha256_harbor_ret = [index_data.references[1].child_digest, index_data.references[0].child_digest]
#7. Verify harbor index is index(IA) pushed by docker manifest CLI;
self.assertEqual(index_data[0].digest, index_sha256_cli_ret)
self.assertEqual(index_data.digest, index_sha256_cli_ret)
self.assertEqual(manifests_sha256_harbor_ret.count(manifests_sha256_cli_ret[0]), 1)
self.assertEqual(manifests_sha256_harbor_ret.count(manifests_sha256_cli_ret[1]), 1)

View File

@ -67,7 +67,7 @@ class TestProjects(unittest.TestCase):
#5. Get and verify artifacts by tag;
artifact = self.artifact.get_reference_info(TestProjects.project_name, self.repo_name, self.tag, **TestProjects.USER_CLIENT)
self.assertEqual(artifact[0].tags[0].name, self.tag)
self.assertEqual(artifact.tags[0].name, self.tag)
#6. Pull sif file from harbor by singularity;
library.singularity.singularity_pull(TestProjects.project_name + ".sif", "oras://"+harbor_server + "/" + TestProjects.project_name + "/" + self.repo_name+":"+ self.tag)

View File

@ -112,12 +112,12 @@ class TestProjects(unittest.TestCase):
#List artifacts successfully, and untagged artifact in test1 should be the only one retained;
artifacts_1 = self.artifact.list_artifacts(TestProjects.project_src_repo_name, self.repo_name_1, **TestProjects.USER_RA_CLIENT)
self.assertTrue(len(artifacts_1)==1)
self.assertEqual(artifacts_1[0].digest, tag_data_artifact3_image1[0].digest)
self.assertEqual(artifacts_1[0].digest, tag_data_artifact3_image1.digest)
#List artifacts successfully, and artifact with latest tag in test2 should be the only one retained;
artifacts_2 = self.artifact.list_artifacts(TestProjects.project_src_repo_name, self.repo_name_2, **TestProjects.USER_RA_CLIENT)
self.assertTrue(len(artifacts_2)==1)
self.assertEqual(artifacts_2[0].digest, tag_data_artifact2_image2[0].digest)
self.assertEqual(artifacts_2[0].digest, tag_data_artifact2_image2.digest)
@classmethod
def tearDownClass(self):

View File

@ -1,5 +1,6 @@
from __future__ import absolute_import
import unittest
import sys
from testutils import harbor_server
from testutils import TEARDOWN
@ -11,7 +12,9 @@ from library.repository import push_image_to_project
from library.artifact import Artifact
from library.scan import Scan
from library.scanner import Scanner
class TestProjects(unittest.TestCase):
from library.sign import sign_image
class TestScan(unittest.TestCase):
@classmethod
def setUp(self):
self.project= Project()
@ -21,6 +24,19 @@ class TestProjects(unittest.TestCase):
self.scan = Scan()
self.scanner = Scanner()
self.url = ADMIN_CLIENT["endpoint"]
self.user_password = "Aa123456"
self.project_id, self.project_name, self.user_id, self.user_name = [None] * 4
self.user_id, self.user_name = self.user.create_user(user_password = self.user_password, **ADMIN_CLIENT)
self.USER_CLIENT = dict(with_signature = True, with_immutable_status = True, endpoint = self.url, username = self.user_name, password = self.user_password, with_scan_overview = True)
#2. Create a new private project(PA) by user(UA);
self.project_id, self.project_name = self.project.create_project(metadata = {"public": "false"}, **ADMIN_CLIENT)
#3. Add user(UA) as a member of project(PA) with project-admin role;
self.project.add_project_members(self.project_id, user_id = self.user_id, **ADMIN_CLIENT)
@classmethod
def tearDown(self):
print("Case completed")
@ -28,13 +44,13 @@ class TestProjects(unittest.TestCase):
@unittest.skipIf(TEARDOWN == True, "Test data won't be erased.")
def test_ClearData(self):
#1. Delete repository(RA) by user(UA);
self.repo.delete_repoitory(TestProjects.project_scan_image_name, TestProjects.repo_name.split('/')[1], **TestProjects.USER_SCAN_IMAGE_CLIENT)
self.repo.delete_repoitory(self.project_name, TestScan.repo_name.split('/')[1], **self.USER_CLIENTT)
#2. Delete project(PA);
self.project.delete_project(TestProjects.project_scan_image_id, **TestProjects.USER_SCAN_IMAGE_CLIENT)
self.project.delete_project(self.project_id, **self.USER_CLIENT)
#3. Delete user(UA);
self.user.delete_user(TestProjects.user_scan_image_id, **ADMIN_CLIENT)
self.user.delete_user(self.user_id, **ADMIN_CLIENT)
def testScanImageArtifact(self):
"""
@ -54,34 +70,21 @@ class TestProjects(unittest.TestCase):
2. Delete project(PA);
3. Delete user(UA);
"""
url = ADMIN_CLIENT["endpoint"]
user_001_password = "Aa123456"
#1. Create user-001
TestProjects.user_scan_image_id, user_scan_image_name = self.user.create_user(user_password = user_001_password, **ADMIN_CLIENT)
TestProjects.USER_SCAN_IMAGE_CLIENT=dict(endpoint = url, username = user_scan_image_name, password = user_001_password, with_scan_overview = True)
#2. Create a new private project(PA) by user(UA);
TestProjects.project_scan_image_id, TestProjects.project_scan_image_name = self.project.create_project(metadata = {"public": "false"}, **ADMIN_CLIENT)
#3. Add user(UA) as a member of project(PA) with project-admin role;
self.project.add_project_members(TestProjects.project_scan_image_id, user_id=TestProjects.user_scan_image_id, **ADMIN_CLIENT)
#4. Get private project of user(UA), user(UA) can see only one private project which is project(PA);
self.project.projects_should_exist(dict(public=False), expected_count = 1,
expected_project_id = TestProjects.project_scan_image_id, **TestProjects.USER_SCAN_IMAGE_CLIENT)
expected_project_id = self.project_id, **self.USER_CLIENT)
#Note: Please make sure that this Image has never been pulled before by any other cases,
# so it is a not-scanned image right after repository creation.
image = "docker"
src_tag = "1.13"
#5. Create a new repository(RA) and tag(TA) in project(PA) by user(UA);
TestProjects.repo_name, tag = push_image_to_project(TestProjects.project_scan_image_name, harbor_server, user_scan_image_name, user_001_password, image, src_tag)
TestScan.repo_name, tag = push_image_to_project(self.project_name, harbor_server, self.user_name, self.user_password, image, src_tag)
#6. Send scan image command and get tag(TA) information to check scan result, it should be finished;
self.scan.scan_artifact(TestProjects.project_scan_image_name, TestProjects.repo_name.split('/')[1], tag, **TestProjects.USER_SCAN_IMAGE_CLIENT)
self.artifact.check_image_scan_result(TestProjects.project_scan_image_name, image, tag, **TestProjects.USER_SCAN_IMAGE_CLIENT)
self.scan.scan_artifact(self.project_name, TestScan.repo_name.split('/')[1], tag, **self.USER_CLIENT)
self.artifact.check_image_scan_result(self.project_name, image, tag, **self.USER_CLIENT)
#7. Swith Scanner;
uuid = self.scanner.scanners_get_uuid(**ADMIN_CLIENT)
@ -89,10 +92,45 @@ class TestProjects(unittest.TestCase):
image = "tomcat"
src_tag = "latest"
TestProjects.repo_name, tag = push_image_to_project(TestProjects.project_scan_image_name, harbor_server, user_scan_image_name, user_001_password, image, src_tag)
TestScan.repo_name, tag = push_image_to_project(self.project_name, harbor_server, self.user_name, self.user_password, image, src_tag)
#8. Send scan another image command and get tag(TA) information to check scan result, it should be finished.
self.scan.scan_artifact(TestProjects.project_scan_image_name, TestProjects.repo_name.split('/')[1], tag, **TestProjects.USER_SCAN_IMAGE_CLIENT)
self.artifact.check_image_scan_result(TestProjects.project_scan_image_name, image, tag, **TestProjects.USER_SCAN_IMAGE_CLIENT)
self.scan.scan_artifact(self.project_name, TestScan.repo_name.split('/')[1], tag, **self.USER_CLIENT)
self.artifact.check_image_scan_result(self.project_name, image, tag, **self.USER_CLIENT)
def testScanSignedImage(self):
"""
Test case:
Scan A Signed Image
Test step and expected result:
1. Create a new user(UA);
2. Create a new private project(PA) by user(UA);
3. Add user(UA) as a member of project(PA) with project-admin role;
4. Get private project of user(UA), user(UA) can see only one private project which is project(PA);
5. Create a new repository(RA) and tag(TA) in project(PA) by user(UA);
6. Send scan image command and get tag(TA) information to check scan result, it should be finished;
7. Swith Scanner;
8. Send scan another image command and get tag(TA) information to check scan result, it should be finished.
Tear down:
1. Delete repository(RA) by user(UA);
2. Delete project(PA);
3. Delete user(UA);
"""
#Note: Please make sure that this Image has never been pulled before by any other cases,
# so it is a not-scanned image right after repository creation.
image = "redis"
tag = "latest"
#5. Create a new repository(RA) and tag(TA) in project(PA) by user(UA);
TestScan.repo_name_1, tag = push_image_to_project(self.project_name, harbor_server, self.user_name, self.user_password, image, tag)
sign_image(harbor_server, self.project_name, image, tag)
#6. Send scan image command and get tag(TA) information to check scan result, it should be finished;
self.scan.scan_artifact(self.project_name, TestScan.repo_name_1.split('/')[1], tag, **self.USER_CLIENT)
self.artifact.check_image_scan_result(self.project_name, image, tag, **self.USER_CLIENT)
if __name__ == '__main__':
unittest.main()
suite = unittest.TestSuite(unittest.makeSuite(TestScan))
result = unittest.TextTestRunner(sys.stdout, verbosity=2, failfast=True).run(suite)
if not result.wasSuccessful():
raise Exception(r"Tag immutability test failed: {}".format(result))

View File

@ -80,7 +80,7 @@ class TestProjects(unittest.TestCase):
#7. Get signature of image with tag(TA), it should be exist.
artifact = self.artifact.get_reference_info(TestProjects.project_sign_image_name, image, tag, **TestProjects.USER_sign_image_CLIENT)
self.assertEqual(artifact[0].tags[0].signed, True)
self.assertEqual(artifact.tags[0].signed, True)
push_special_image_to_project(TestProjects.project_sign_image_name, harbor_server, user_sign_image_name, user_001_password, self.repo_name_1, ['1.0'])
self.repo.delete_repoitory(TestProjects.project_sign_image_name, self.repo_name_1, **TestProjects.USER_sign_image_CLIENT)

View File

@ -79,8 +79,8 @@ class TestProjects(unittest.TestCase):
artifact = self.artifact.get_reference_info(TestProjects.project_name, self.repo_name, tag, **TestProjects.USER_CLIENT)
#6. Verify the image(IA) contains tag named 1.0;
self.assertEqual(artifact[0].tags[0].name, "1.0")
self.assertEqual(artifact[0].tags[1].name, tag)
self.assertEqual(artifact.tags[0].name, "1.0")
self.assertEqual(artifact.tags[1].name, tag)
#7. Delete the tag(1.0) from image(IA);
self.artifact.delete_tag(TestProjects.project_name, self.repo_name, tag, "1.0",**TestProjects.USER_CLIENT)
@ -89,7 +89,7 @@ class TestProjects(unittest.TestCase):
artifact = self.artifact.get_reference_info(TestProjects.project_name, self.repo_name, tag, **TestProjects.USER_CLIENT)
#9. Verify the image(IA) contains no tag named 1.0;
self.assertEqual(artifact[0].tags[0].name, tag)
self.assertEqual(artifact.tags[0].name, tag)

View File

@ -0,0 +1,300 @@
from __future__ import absolute_import
import unittest
import sys
from testutils import ADMIN_CLIENT
from testutils import harbor_server
from library.project import Project
from library.user import User
from library.repository import Repository
from library.repository import push_image_to_project
from library.registry import Registry
from library.artifact import Artifact
from library.tag_immutability import Tag_Immutability
from library.repository import push_special_image_to_project
class TestTagImmutability(unittest.TestCase):
@classmethod
def setUpClass(self):
self.url = ADMIN_CLIENT["endpoint"]
self.user_password = "Aa123456"
self.project= Project()
self.user= User()
self.repo= Repository()
self.registry = Registry()
self.artifact = Artifact()
self.tag_immutability = Tag_Immutability()
self.project_id, self.project_name, self.user_id, self.user_name = [None] * 4
self.user_id, self.user_name = self.user.create_user(user_password = self.user_password, **ADMIN_CLIENT)
self.USER_CLIENT = dict(with_signature = True, with_immutable_status = True, endpoint = self.url, username = self.user_name, password = self.user_password)
self.exsiting_rule = dict(selector_repository="rel*", selector_tag="v2.*")
self.project_id, self.project_name = self.project.create_project(metadata = {"public": "false"}, **self.USER_CLIENT)
def check_tag_immutability(self, artifact, tag_name, status = True):
for tag in artifact.tags:
if tag.name == tag_name:
self.assertTrue(tag.immutable == status)
return
raise Exception("No tag {} found in artifact {}".format(tag, artifact))
def test_disability_of_rules(self):
"""
Test case:
Test Disability Of Rules
Test step and expected result:
1. Create a new project;
2. Push image A to the project with 2 tags A and B;
3. Create a disabled rule matched image A with tag A;
4. Both tags of image A should not be immutable;
5. Enable this rule;
6. image A with tag A should be immutable.
"""
image_a = dict(name="image_disability_a", tag1="latest", tag2="6.2.2")
#1. Create a new project;
project_id, project_name = self.project.create_project(metadata = {"public": "false"}, **self.USER_CLIENT)
#2. Push image A to the project with 2 tags;
push_special_image_to_project(project_name, harbor_server, self.user_name, self.user_password, image_a["name"], [image_a["tag1"], image_a["tag2"]])
#3. Create a disabled rule matched image A;
rule_id = self.tag_immutability.create_rule(project_id, disabled = True, selector_repository=image_a["name"], selector_tag=str(image_a["tag1"])[0:2] + "*", **self.USER_CLIENT)
#4. Both tags of image A should not be immutable;
artifact_a = self.artifact.get_reference_info(project_name, image_a["name"], image_a["tag2"], **self.USER_CLIENT)
print("[test_disability_of_rules] - artifact:{}".format(artifact_a))
self.assertTrue(artifact_a)
self.check_tag_immutability(artifact_a, image_a["tag1"], status = False)
self.check_tag_immutability(artifact_a, image_a["tag2"], status = False)
#5. Enable this rule;
self.tag_immutability.update_tag_immutability_policy_rule(project_id, rule_id, disabled = False, **self.USER_CLIENT)
#6. image A with tag A should be immutable.
artifact_a = self.artifact.get_reference_info(project_name, image_a["name"], image_a["tag2"], **self.USER_CLIENT)
print("[test_disability_of_rules] - artifact:{}".format(artifact_a))
self.assertTrue(artifact_a)
self.check_tag_immutability(artifact_a, image_a["tag1"], status = True)
self.check_tag_immutability(artifact_a, image_a["tag2"], status = False)
def test_artifact_and_repo_is_undeletable(self):
"""
Test case:
Test Artifact And Repo is Undeleteable
Test step and expected result:
1. Create a new project;
2. Push image A to the project with 2 tags A and B;
3. Create a enabled rule matched image A with tag A;
4. Tag A should be immutable;
5. Artifact is undeletable;
6. Repository is undeletable.
"""
image_a = dict(name="image_repo_undeletable_a", tag1="latest", tag2="1.3.2")
#1. Create a new project;
project_id, project_name = self.project.create_project(metadata = {"public": "false"}, **self.USER_CLIENT)
#2. Push image A to the project with 2 tags A and B;
push_special_image_to_project(project_name, harbor_server, self.user_name, self.user_password, image_a["name"], [image_a["tag1"], image_a["tag2"]])
#3. Create a enabled rule matched image A with tag A;
self.tag_immutability.create_rule(project_id, selector_repository=image_a["name"], selector_tag=str(image_a["tag1"])[0:2] + "*", **self.USER_CLIENT)
#4. Tag A should be immutable;
artifact_a = self.artifact.get_reference_info(project_name, image_a["name"], image_a["tag2"], **self.USER_CLIENT)
print("[test_artifact_and_repo_is_undeletable] - artifact:{}".format(artifact_a))
self.assertTrue(artifact_a)
self.check_tag_immutability(artifact_a, image_a["tag1"], status = True)
self.check_tag_immutability(artifact_a, image_a["tag2"], status = False)
#5. Artifact is undeletable;
self.artifact.delete_artifact(project_name, image_a["name"], image_a["tag1"], expect_status_code = 412,expect_response_body = "configured as immutable, cannot be deleted", **self.USER_CLIENT)
#6. Repository is undeletable.
self.repo.delete_repoitory(project_name, image_a["name"], expect_status_code = 412, expect_response_body = "configured as immutable, cannot be deleted", **self.USER_CLIENT)
def test_tag_is_undeletable(self):
"""
Test case:
Test Tag is Undeleteable
Test step and expected result:
1. Push image A to the project with 2 tags A and B;
2. Create a enabled rule matched image A with tag A;
3. Tag A should be immutable;
4. Tag A is undeletable;
5. Tag B is deletable.
"""
image_a = dict(name="image_undeletable_a", tag1="latest", tag2="9.3.2")
#1. Push image A to the project with 2 tags A and B;
push_special_image_to_project(self.project_name, harbor_server, self.user_name, self.user_password, image_a["name"], [image_a["tag1"], image_a["tag2"]])
#2. Create a enabled rule matched image A with tag A;
self.tag_immutability.create_rule(self.project_id, selector_repository=image_a["name"], selector_tag=str(image_a["tag2"])[0:2] + "*", **self.USER_CLIENT)
#3. Tag A should be immutable;
artifact_a = self.artifact.get_reference_info(self.project_name, image_a["name"], image_a["tag2"], **self.USER_CLIENT)
print("[test_tag_is_undeletable] - artifact:{}".format(artifact_a))
self.assertTrue(artifact_a)
self.check_tag_immutability(artifact_a, image_a["tag2"], status = True)
#4. Tag A is undeletable;
self.artifact.delete_tag(self.project_name, image_a["name"], image_a["tag1"], image_a["tag2"], expect_status_code = 412, **self.USER_CLIENT)
#5. Tag B is deletable.
self.artifact.delete_tag(self.project_name, image_a["name"], image_a["tag1"], image_a["tag1"], **self.USER_CLIENT)
def test_image_is_unpushable(self):
"""
Test case:
Test Image is Unpushable
Test step and expected result:
1. Create a new project;
2. Push image A to the project with 2 tags A and B;
3. Create a enabled rule matched image A with tag A;
4. Tag A should be immutable;
5. Can not push image with the same image name and with the same tag name.
"""
image_a = dict(name="image_unpushable_a", tag1="latest", tag2="1.3.2")
#1. Create a new project;
project_id, project_name = self.project.create_project(metadata = {"public": "false"}, **self.USER_CLIENT)
#2. Push image A to the project with 2 tags A and B;
push_special_image_to_project(project_name, harbor_server, self.user_name, self.user_password, image_a["name"], [image_a["tag1"], image_a["tag2"]])
#3. Create a enabled rule matched image A with tag A;
self.tag_immutability.create_rule(project_id, selector_repository=image_a["name"], selector_tag=str(image_a["tag1"])[0:2] + "*", **self.USER_CLIENT)
#4. Tag A should be immutable;
artifact_a = self.artifact.get_reference_info(project_name, image_a["name"], image_a["tag2"], **self.USER_CLIENT)
print("[test_image_is_unpushable] - artifact:{}".format(artifact_a))
self.assertTrue(artifact_a)
self.check_tag_immutability(artifact_a, image_a["tag1"], status = True)
self.check_tag_immutability(artifact_a, image_a["tag2"], status = False)
#5. Can not push image with the same image name and with the same tag name.
push_image_to_project(project_name, harbor_server, self.user_name, self.user_password, "tomcat", image_a["tag1"],
new_image = image_a["name"], expected_error_message = "configured as immutable")
def test_copy_disability(self):
"""
Test case:
Test Copy Disability
Test step and expected result:
1. Create 2 projects;
2. Push image A with tag A and B to project A, push image B which has the same image name and tag name to project B;
3. Create a enabled rule matched image A with tag A;
4. Tag A should be immutable;
5. Can not copy artifact from project A to project B with the same repository name.
"""
image_a = dict(name="image_copy_disability_a", tag1="latest", tag2="1.3.2")
#1. Create 2 projects;
project_id, project_name = self.project.create_project(metadata = {"public": "false"}, **self.USER_CLIENT)
_, project_name_src = self.project.create_project(metadata = {"public": "false"}, **self.USER_CLIENT)
#2. Push image A with tag A and B to project A, push image B which has the same image name and tag name to project B;
push_special_image_to_project(project_name, harbor_server, self.user_name, self.user_password, image_a["name"], [image_a["tag1"], image_a["tag2"]])
push_special_image_to_project(project_name_src, harbor_server, self.user_name, self.user_password, image_a["name"], [image_a["tag1"], image_a["tag2"]])
#3. Create a enabled rule matched image A with tag A;
self.tag_immutability.create_rule(project_id, selector_repository=image_a["name"], selector_tag=str(image_a["tag1"])[0:2] + "*", **self.USER_CLIENT)
#4. Tag A should be immutable;
artifact_a = self.artifact.get_reference_info(project_name, image_a["name"], image_a["tag2"], **self.USER_CLIENT)
print("[test_copy_disability] - artifact:{}".format(artifact_a))
self.assertTrue(artifact_a)
self.check_tag_immutability(artifact_a, image_a["tag1"], status = True)
self.check_tag_immutability(artifact_a, image_a["tag2"], status = False)
#5. Can not copy artifact from project A to project B with the same repository name.
artifact_a_src = self.artifact.get_reference_info(project_name_src, image_a["name"], image_a["tag2"], **self.USER_CLIENT)
print("[test_copy_disability] - artifact_a_src:{}".format(artifact_a_src))
self.artifact.copy_artifact(project_name, image_a["name"], project_name_src+"/"+ image_a["name"] + "@" + artifact_a_src.digest, expect_status_code=412, expect_response_body = "configured as immutable, cannot be updated", **self.USER_CLIENT)
#def test_replication_disability(self):
# pass
def test_priority_of_rules(self):
"""
Test case:
Test Priority Of Rules(excluding rule will not affect matching rule)
Test step and expected result:
1. Push image A, B and C, image A has only 1 tag named tag1;
2. Create a matching rule that matches image A and tag named tag2 which is not exist;
3. Create a excluding rule to exlude image A and B;
4. Add a tag named tag2 to image A, tag2 should be immutable;
5. Tag2 should be immutable;
6. All tags in image B should be immutable;
7. All tags in image C should not be immutable;
8. Disable all rules.
"""
image_a = dict(name="image_priority_a", tag1="latest", tag2="6.3.2")
image_b = dict(name="image_priority_b", tag1="latest", tag2="0.12.0")
image_c = dict(name="image_priority_c", tag1="latest", tag2="3.12.0")
#1. Push image A, B and C, image A has only 1 tag named tag1;
push_special_image_to_project(self.project_name, harbor_server, self.user_name, self.user_password, image_a["name"], [image_a["tag1"]])
push_special_image_to_project(self.project_name, harbor_server, self.user_name, self.user_password, image_b["name"], [image_b["tag1"],image_b["tag2"]])
push_special_image_to_project(self.project_name, harbor_server, self.user_name, self.user_password, image_c["name"], [image_c["tag1"],image_c["tag2"]])
#2. Create a matching rule that matches image A and tag named tag2 which is not exist;
rule_id_1 = self.tag_immutability.create_rule(self.project_id, selector_repository=image_a["name"], selector_tag=image_a["tag2"], **self.USER_CLIENT)
#3. Create a excluding rule to exlude image A and B;
rule_id_2 = self.tag_immutability.create_rule(self.project_id, selector_repository_decoration = "repoExcludes",
selector_repository="{image_priority_a,image_priority_b}", selector_tag="**", **self.USER_CLIENT)
#4. Add a tag named tag2 to image A, tag2 should be immutable;
self.artifact.create_tag(self.project_name, image_a["name"], image_a["tag1"], image_a["tag2"], **self.USER_CLIENT)
#5. Tag2 should be immutable;
artifact_a = self.artifact.get_reference_info(self.project_name, image_a["name"], image_a["tag2"], **self.USER_CLIENT)
print("[test_priority_of_rules] - artifact:{}".format(artifact_a))
self.assertTrue(artifact_a)
self.check_tag_immutability(artifact_a, image_a["tag2"], status = True)
self.check_tag_immutability(artifact_a, image_a["tag1"], status = False)
#6. All tags in image B should be immutable;
artifact_b = self.artifact.get_reference_info(self.project_name, image_b["name"], image_b["tag2"], **self.USER_CLIENT)
print("[test_priority_of_rules] - artifact:{}".format(artifact_b))
self.assertTrue(artifact_b)
self.check_tag_immutability(artifact_b, image_b["tag2"], status = False)
self.check_tag_immutability(artifact_b, image_b["tag1"], status = False)
#7. All tags in image C should not be immutable;
artifact_c = self.artifact.get_reference_info(self.project_name, image_c["name"], image_c["tag2"], **self.USER_CLIENT)
print("[test_priority_of_rules] - artifact:{}".format(artifact_c))
self.assertTrue(artifact_c)
self.check_tag_immutability(artifact_c, image_c["tag2"], status = True)
self.check_tag_immutability(artifact_c, image_c["tag1"], status = True)
#8. Disable all rules.
self.tag_immutability.update_tag_immutability_policy_rule(self.project_id, rule_id_1, disabled = True, **self.USER_CLIENT)
self.tag_immutability.update_tag_immutability_policy_rule(self.project_id, rule_id_2, disabled = True, **self.USER_CLIENT)
def test_add_exsiting_rule(self):
"""
Test case:
Test Priority Of Rules(excluding rule will not affect matching rule)
Test step and expected result:
1. Push image A and B with no tag;
2. Create a immutability policy rule A;
3. Fail to create rule B which has the same config as rule A;
"""
self.tag_immutability.create_tag_immutability_policy_rule(self.project_id, **self.exsiting_rule, **self.USER_CLIENT)
self.tag_immutability.create_tag_immutability_policy_rule(self.project_id, **self.exsiting_rule, expect_status_code = 409, **self.USER_CLIENT)
@classmethod
def tearDownClass(self):
print("Case completed")
if __name__ == '__main__':
suite = unittest.TestSuite(unittest.makeSuite(TestTagImmutability))
result = unittest.TextTestRunner(sys.stdout, verbosity=2, failfast=True).run(suite)
if not result.wasSuccessful():
raise Exception(r"Tag immutability test failed: {}".format(result))

View File

@ -88,7 +88,7 @@ Wait Until Element Is Visible And Enabled
Retry Action Keyword
[Arguments] ${keyword} @{param}
Retry Keyword N Times When Error 3 ${keyword} @{param}
Retry Keyword N Times When Error 8 ${keyword} @{param}
Retry Wait Element
[Arguments] ${element_xpath}

View File

@ -105,16 +105,16 @@ Test Case - Push Cnab Bundle
[Tags] push_cnab
Harbor API Test ./tests/apitests/python/test_push_cnab_bundle.py
Test Case - Create/Delete tag
[Tags] tag_cuid
Harbor API Test ./tests/apitests/python/test_create_delete_tag.py
Test Case - Tag CRUD
[Tags] tag_crud
Harbor API Test ./tests/apitests/python/test_tag_crud.py
Test Case - Scan Image
[Tags] scan
Harbor API Test ./tests/apitests/python/test_scan_image_artifact.py
Test Case - Scan Image In Public Project
[Tags] scan
[Tags] scan_public_project
Harbor API Test ./tests/apitests/python/test_scan_image_artifact_in_public_project.py
Test Case - Scan All Images
@ -148,3 +148,7 @@ Test Case - Replication From Dockerhub
Test Case - Proxy Cache
[Tags] proxy_cache
Harbor API Test ./tests/apitests/python/test_proxy_cache.py
Test Case - Tag Immutability
[Tags] tag_immutability
Harbor API Test ./tests/apitests/python/test_tag_immutability.py