mirror of
https://github.com/goharbor/harbor.git
synced 2026-10-23 10:08:57 +03:00
* feat: custom project roles (hybrid — built-ins in code, custom in DB) Adds admin-defined custom project roles as an alternative to #22815, per maintainer feedback: built-in project roles keep resolving their permissions from the compile-time rolePoliciesMap (no DB, no cache — identical to today), and ONLY custom roles are stored in and loaded from the database. There is no permission cache. Backend + API only (UI ships separately). Highlights: - RBAC (common/rbac/project): projectRBACRole gains an optional custom *role.Role. The built-in path is byte-identical to upstream; the role controller is consulted only for non-built-in role IDs, so built-in authorization incurs no DB lookup. Custom roles expand their DB-loaded permissions. - Role subsystem: pkg/role (model/dao/manager) + controller/role for CRUD and persistence; existing role_permission/permission_policy reused for custom perms. - REST API: /roles endpoints (sysadmin-gated), an effective-permissions endpoint, anti-escalation checks on member/robot assignment, and audit events. - Migration 0191 adds is_builtin/description/audit columns to `role`, widens the name, enforces name uniqueness, and marks seeded roles built-in. Built-in permissions are NOT seeded into the DB. Rebased onto current main; API bindings regenerated via gen_apis. Tracking: #18124 Alternative to: #22815 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * review: make role a system RBAC resource; drop checkSysAdmin & ResourceSelf Addresses stonezdj's review on PR #23804: - Define rbac.ResourceRole (system-level) and add it to the system permission catalog; gate CreateRole/UpdateRole/DeleteRole with RequireSystemAccess(action, ResourceRole) instead of the hand-rolled checkSysAdmin — so the permission is managable while still effectively system-only. - Remove the checkSysAdmin helper and its unit tests; update handler tests to mock Can(). - Drop ResourceSelf from the ScopeRole permission catalog. - Remove tests/integration/.gitignore (per-dir ignore file). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * review: close built-in-role escalation gap; reject nil role payloads Addresses Copilot findings on PR #23804: - checkNoEscalation treated built-in roles as permissionless: it read permissions from role_permission, which has no rows for built-in roles, so a caller with member:create could assign projectAdmin (or any built-in) without holding its permissions. Built-in permissions are now synthesized from the compile-time policy map via the new rbac/project.BuiltinRolePolicies; custom roles still read the DB. Adds TestCheckNoEscalation_BuiltinRoleSynthesized. - role validate() now rejects nil permission/access elements (permissions:[null], access:[null]) with 400 instead of panicking. Adds nil-payload tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * review: validate that a member's custom role exists in the database Addresses stonezdj's review on PR #23804: isValidRole returned true for any integer above the built-in range, so a member could be assigned a non-existent custom role ID. The member controller now holds a role manager and validateRole verifies custom role IDs (>5) exist via roleMgr.Get, rejecting unknown IDs with ErrInvalidRole; built-in roles (1-5) still pass without a DB lookup. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * review: make role Create/Update/Delete atomic via orm.WithTransaction Addresses Copilot findings on PR #23804: - Create: the role row and its permission records are now created in one transaction, so a failed permission insert no longer leaves a named role with missing permissions (whose unique name would block a clean retry). - Update: the field update and the delete+recreate of permissions run in one transaction, so the role's authorization is never observable in a partial state. - Delete: the member-assignment guard and the role/permission deletes run in one transaction, serializing the guard with the delete and ensuring permission rows never outlive the role. (Full prevention of a concurrent assignment needs a DB-level FK on project_member.role — tracked as a follow-up.) Controller unit tests inject a fake ormer (testing/lib/orm) so the transaction wrappers resolve an ormer from context. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * review: group /roles/{role_id} right after /roles in swagger Addresses stonezdj's review on PR #23804: the two role endpoints serve the same feature and should be adjacent in the spec. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * review: split out role audit-log events; drop the standalone integration test Addresses stonezdj's review on PR #23804 (shrink the PR): - Remove the role audit-log event wiring from this PR (controller/event metadata/role.go, topic.go role topics/events, init.go subscriptions, and the notification.AddEvent calls in the role controller). It will be submitted as a separate follow-up PR; the code is preserved on branch 18124-role-audit-events. - Remove tests/integration/test_custom_roles.py: it is not wired into Harbor CI (which uses tests/apitests) and contained the issues Copilot flagged. Feature behavior is covered by the Go unit tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * review: validate role on member UpdateRole; fix stale invalid-role message Addresses stonezdj's follow-up review on PR #23804: - UpdateRole (changing a member's role) now runs the same validateRole check as AddProjectMember, so a member can't be moved to a non-existent role. - ErrInvalidRole no longer claims "role is not in 1,2,3"; it now states the role must be a built-in role (1-5) or an existing custom role. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * review: remove duplicate effective-permissions endpoint Addresses stonezdj's review on PR #23804: GET /projects/{id}/permissions/effective duplicated the existing GET /users/current/permissions, which already supports a project scope and a `relative` flag (scope=/project/{id}&relative=true returns the same set of project permissions the caller holds). Removed the handler and the swagger path (bindings regenerated). The shared mapRobotToHumanResource helper is kept — it is still used by the robot anti-escalation check. The robot-account UI (separate PR) will use /users/current/permissions instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * fix(rbac): keep role management sysadmin-only, not delegable Role write access is checked via RequireSystemAccess(ctx, action, rbac.ResourceRole), but ResourceRole was also added to the grantable ScopeSystem catalog (NolimitProvider). That published role:* as an assignable system permission: it could be granted to a system robot (robots evaluate system-scope policies) and listed in /permissions, even though a human non-admin never evaluates system-scope perms. A system robot granted role:update could then rewrite any custom role's permission set (UpdateRole/updateV2Role has no subset guard) = privilege escalation. Remove the ResourceRole entries from the ScopeSystem catalog. The admin evaluator bypasses the catalog, so sysadmins still pass the RequireSystemAccess check and manage roles exactly as before, but role:* is no longer assignable/enumerable to non-admins. This keeps role management strictly sysadmin-only while still expressing it as a first-class action+resource check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * fix(rbac): grant custom-role members baseline self:read (project visibility) self:read (ResourceSelf) is the permission that gates viewing a project: RequireProjectAccess(ctx, id, ActionRead) with no subresource checks <project-ns>:"" and GetProject/GetSummary use exactly that. Built-in roles carry self:read in rolePoliciesMap, and public projects grant it via publicProjectPolicies, but a custom role in a private project had no way to obtain it -- self:* is intentionally excluded from the ScopeRole catalog, and GetPolicies returned only the role's explicit permissions. Result: a member whose only access to a private project was a custom role got a 403 opening that project. Append {ResourceSelf, ActionRead} to the custom-role branch of projectRBACRole.GetPolicies (de-duplicated), mirroring the built-in roles and the public-project baseline. Only self:read is granted, never self:update/self:delete (project edit/delete stay exclusive to the projectAdmin built-in), and self:* remains out of the user-facing catalog so it is guaranteed by the system rather than a tickable option. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * refactor(rbac): grant self:read at membership level, not per custom role Move the baseline project-visibility grant out of the custom-role branch of projectRBACRole.GetPolicies and into rbacUser.GetPolicies: any user who holds at least one role in a project (len(projectRoles) > 0) is granted self:read on that project. This makes baseline visibility a property of project membership rather than a per-custom-role special case, sitting beside the existing public-project baseline (getPoliciesForPublicProject) and using the same eval-time idiom -- no permission data seeded into the database. Built-in and custom roles now obtain self:read the same way, and the grant stays scoped to the membership project (ListRoles is per-project), so it never exposes projects the user is not a member of. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * chore(deps-dev): bump webpack-cli from 4.10.0 to 7.2.3 in /src/portal/app-swagger-ui (#23850) chore(deps-dev): bump webpack-cli in /src/portal/app-swagger-ui Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 4.10.0 to 7.2.3. - [Release notes](https://github.com/webpack/webpack-cli/releases) - [Changelog](https://github.com/webpack/webpack-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/webpack/webpack-cli/compare/webpack-cli@4.10.0...webpack-cli@7.2.3) --- updated-dependencies: - dependency-name: webpack-cli dependency-version: 7.2.3 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Prasanth Baskar <prasanth@8gears.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * review: consolidate 2.16.0 migration into 0190 and simplify robot resource map Address two review comments from stonezdj on PR #23804: - 2.16.0 is not released yet, so keep all its schema changes in a single migration file. Move the custom-role `role` table additions (columns, name widened to varchar(255), uq_role_name unique index, is_builtin update) from 0191 into 0190 and delete 0191. - Simplify mapRobotToHumanResource: replace the single-entry robotToHumanResourceMap + lookup with a direct `if r == rbac.ResourceProject { return rbac.ResourceSelf }`. Also commit the cross-project isolation test for the membership-level self:read baseline (a member of one private project cannot read another). No behavior change; robot resource-mapping tests pass unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * review: fix bugs from bupd's review (keep roles global + sysadmin-only) Address the correctness findings from bupd's review of PR #23804, keeping the global, sysadmin-managed role model (project-scoped roles remain a separate future feature): - evaluator: a failed custom-role load no longer drops the whole rbacUser. Returning nil stripped every role the member held in the project (built-ins included), turning a transient DB error or a dangling project_member.role into a total 403 / permanent lockout. Skip only the failing role instead. - role validate(): reject any permission Effect other than "" or "allow". It was stored verbatim and handed to casbin, so a mis-cased "Allow" or a bogus value was accepted and silently granted nothing. - read path: GET /roles(/{id}) now returns a built-in role's real permissions (resolved from rolePoliciesMap via BuiltinRolePolicies) instead of an empty set; built-ins have no role_permission rows by design. Removed the dead "Visible" list filter (model.Role has no such field). - controller Delete: a custom role with no permission rows is deletable again (DeletePermissionsByRole's NotFound no longer rolls back the txn); dropped the unused variadic option parameter. - controller Update: persist a role name change instead of silently dropping it (validated in the handler; DB unique index rejects collisions). - permissions.go: restore the sysadmin-or-project-admin gate on GET /permissions (it had been relaxed to any authenticated user), and expose the role-level catalog only to sysadmins. - rolePoliciesMap["projectAdmin"]: add metadata:list, restoring 2.15 behavior where a project admin can create a robot with metadata:list (the new robot anti-escalation check validated against projectAdmin, which lacked it). - swagger: document that role reads are open to any authenticated user while writes are sysadmin-only. - add a drift-guard test asserting the ScopeRole catalog equals projectAdmin minus self:{read,update,delete}, plus tests for the fixes above. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * review: make role name uniqueness case-insensitive Per bupd's review: enforce uniqueness on lower(name) so "Maintainer" and "maintainer" cannot coexist and be confused for one another. 2.16.0 is unreleased, so the index is dropped and recreated in place. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * fix(role): correct stale TestList assertion (roles have no "visible" concept) TestDaoTestSuite/TestList asserted that a freshly created role is NOT returned by List (len == 0), a leftover from a removed "visible" column. model.Role has no such field and the List DAO applies no visibility filter, so the role is returned. Assert it is listed (len == 1) to match the actual, intended behavior. Surfaced now that UTTEST runs on the PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> * review: robot-refresh escalation, deny-effect guard, delete-race FK, swagger 409/412 Address chlins' and Copilot's review of PR #23804: - RefreshSec escalation (chlins #1): refreshing a robot's secret is equivalent to being granted the robot's permissions, so it now runs the same no-escalation check as create/update (load the robot with permissions; human callers checked via validateNoEscalation, robot callers via isValidPermissionScope). A member with robot:update but not the robot's own permissions can no longer take over a more-privileged robot. - Deny-effect guard (chlins #2): member checkNoEscalation now skips Effect==deny accesses; they restrict rather than grant, so requiring the caller to hold them wrongly blocked assigning a role that merely denies something. - Delete-vs-assign race (chlins #3): add FK project_member.role -> role(role_id) ON DELETE RESTRICT so the DB atomically rejects deleting an assigned role, closing the count-then-delete race; the resulting FK error is mapped to 412 in the role Delete controller. - Swagger: document 409 (CreateRole duplicate-name conflict) and 412 (DeleteRole still-assigned) responses. - Tests: FK-race delete -> 412, and a Create failure-injection so dropping the transaction would fail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> --------- Signed-off-by: Max Grau Stenzel <max.grau.stenzel@gmail.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Prasanth Baskar <prasanth@8gears.com>
201 lines
8.5 KiB
Python
201 lines
8.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
import os
|
|
import subprocess
|
|
import time
|
|
|
|
import importlib
|
|
|
|
try:
|
|
from urllib import getproxies
|
|
except ImportError:
|
|
from urllib.request import getproxies
|
|
|
|
def swagger_module():
|
|
module = importlib.import_module("v2_swagger_client")
|
|
return module
|
|
|
|
class Server:
|
|
def __init__(self, endpoint, verify_ssl):
|
|
self.endpoint = endpoint
|
|
self.verify_ssl = verify_ssl
|
|
|
|
class Credential:
|
|
def __init__(self, type, username, password):
|
|
self.type = type
|
|
self.username = username
|
|
self.password = password
|
|
|
|
def get_endpoint():
|
|
harbor_server = os.environ.get("HARBOR_HOST", "localhost:8080")
|
|
return os.environ.get("HARBOR_HOST_SCHEMA", "https")+ "://"+harbor_server+"/api/v2.0"
|
|
|
|
def _create_client(server, credential, debug, api_type):
|
|
v2_swagger_client = swagger_module()
|
|
cfg = v2_swagger_client.Configuration()
|
|
cfg.host = server.endpoint
|
|
cfg.verify_ssl = server.verify_ssl
|
|
# support basic auth only for now
|
|
cfg.username = credential.username
|
|
cfg.password = credential.password
|
|
cfg.debug = debug
|
|
|
|
proxies = getproxies()
|
|
proxy = proxies.get('http', proxies.get('all', None))
|
|
if proxy:
|
|
cfg.proxy = proxy
|
|
|
|
if cfg.username is None and cfg.password is None:
|
|
# returns {} for auth_settings for anonymous access
|
|
import types
|
|
cfg.auth_settings = types.MethodType(lambda self: {}, cfg)
|
|
|
|
return {
|
|
"projectv2":v2_swagger_client.ProjectApi(v2_swagger_client.ApiClient(cfg)),
|
|
"artifact": v2_swagger_client.ArtifactApi(v2_swagger_client.ApiClient(cfg)),
|
|
"preheat": v2_swagger_client.PreheatApi(v2_swagger_client.ApiClient(cfg)),
|
|
"quota": v2_swagger_client.QuotaApi(v2_swagger_client.ApiClient(cfg)),
|
|
"repository": v2_swagger_client.RepositoryApi(v2_swagger_client.ApiClient(cfg)),
|
|
"scan": v2_swagger_client.ScanApi(v2_swagger_client.ApiClient(cfg)),
|
|
"scanall": v2_swagger_client.ScanAllApi(v2_swagger_client.ApiClient(cfg)),
|
|
"scanner": v2_swagger_client.ScannerApi(v2_swagger_client.ApiClient(cfg)),
|
|
"replication": v2_swagger_client.ReplicationApi(v2_swagger_client.ApiClient(cfg)),
|
|
"registry": v2_swagger_client.RegistryApi(v2_swagger_client.ApiClient(cfg)),
|
|
"robot": v2_swagger_client.RobotApi(v2_swagger_client.ApiClient(cfg)),
|
|
"role": v2_swagger_client.RoleApi(v2_swagger_client.ApiClient(cfg)),
|
|
"gc": v2_swagger_client.GcApi(v2_swagger_client.ApiClient(cfg)),
|
|
"retention": v2_swagger_client.RetentionApi(v2_swagger_client.ApiClient(cfg)),
|
|
"immutable": v2_swagger_client.ImmutableApi(v2_swagger_client.ApiClient(cfg)),
|
|
"system_cve_allowlist": v2_swagger_client.SystemCVEAllowlistApi(v2_swagger_client.ApiClient(cfg)),
|
|
"configure": v2_swagger_client.ConfigureApi(v2_swagger_client.ApiClient(cfg)),
|
|
"label": v2_swagger_client.LabelApi(v2_swagger_client.ApiClient(cfg)),
|
|
"user": v2_swagger_client.UserApi(v2_swagger_client.ApiClient(cfg)),
|
|
"member": v2_swagger_client.MemberApi(v2_swagger_client.ApiClient(cfg)),
|
|
"health": v2_swagger_client.HealthApi(v2_swagger_client.ApiClient(cfg)),
|
|
"webhook": v2_swagger_client.WebhookApi(v2_swagger_client.ApiClient(cfg)),
|
|
"purge": v2_swagger_client.PurgeApi(v2_swagger_client.ApiClient(cfg)),
|
|
"audit_log": v2_swagger_client.AuditlogApi(v2_swagger_client.ApiClient(cfg)),
|
|
"scan_data_export": v2_swagger_client.ScanDataExportApi(v2_swagger_client.ApiClient(cfg)),
|
|
"statistic": v2_swagger_client.StatisticApi(v2_swagger_client.ApiClient(cfg)),
|
|
"system_info": v2_swagger_client.SysteminfoApi(v2_swagger_client.ApiClient(cfg)),
|
|
"jobservice": v2_swagger_client.JobserviceApi(v2_swagger_client.ApiClient(cfg)),
|
|
"schedule": v2_swagger_client.ScheduleApi(v2_swagger_client.ApiClient(cfg)),
|
|
"securityhub": v2_swagger_client.SecurityhubApi(v2_swagger_client.ApiClient(cfg)),
|
|
}.get(api_type,'Error: Wrong API type')
|
|
|
|
def _assert_status_code(expect_code, return_code, err_msg = r"HTTPS status code s not as we expected. Expected {}, while actual HTTPS status code is {}."):
|
|
if str(return_code) != str(expect_code):
|
|
raise Exception(err_msg.format(expect_code, return_code))
|
|
|
|
def _assert_status_body(expect_status_body, returned_status_body):
|
|
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):
|
|
return "%s-%d" % (prefix, int(round(time.time() * 1000)))
|
|
|
|
def _get_id_from_header(header):
|
|
try:
|
|
location = header["Location"]
|
|
return int(location.split("/")[-1])
|
|
except Exception:
|
|
return None
|
|
|
|
def _get_string_from_unicode(udata):
|
|
result=''
|
|
for u in udata:
|
|
tmp = u.encode('utf8')
|
|
result = result + tmp.strip('\n\r\t')
|
|
return result
|
|
|
|
def getenv_bool(name: str, default: bool = False) -> bool:
|
|
val = os.getenv(name)
|
|
if val is None:
|
|
return default
|
|
return val.strip().lower() in ( "true", "1")
|
|
|
|
def restart_process(process):
|
|
if "dockerd" in process:
|
|
full_process_name = process
|
|
elif process == "containerd":
|
|
full_process_name = "/usr/local/bin/containerd"
|
|
else:
|
|
raise Exception("Please input dockerd or containerd for process retarting.")
|
|
run_command_with_popen("ps aux |grep " + full_process_name)
|
|
for i in range(10):
|
|
pid = run_command_with_popen(["pidof " + full_process_name])
|
|
if pid in [None, ""]:
|
|
break
|
|
run_command_with_popen(["kill " + str(pid)])
|
|
time.sleep(3)
|
|
|
|
run_command_with_popen("ps aux |grep " + full_process_name)
|
|
run_command_with_popen("rm -rf /var/lib/" + process + "/*")
|
|
run_command_with_popen(full_process_name + " > ./daemon-local.log 2>&1 &")
|
|
time.sleep(3)
|
|
pid = run_command_with_popen(["pidof " + full_process_name])
|
|
if pid in [None, ""]:
|
|
raise Exception("Failed to start process {}.".format(full_process_name))
|
|
run_command_with_popen("ps aux |grep " + full_process_name)
|
|
|
|
def run_command_with_popen(command):
|
|
try:
|
|
proc = subprocess.Popen(command, universal_newlines=True, shell=True,
|
|
stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
|
|
output, errors = proc.communicate()
|
|
except Exception as e:
|
|
output = None
|
|
finally:
|
|
proc.stdout.close()
|
|
return output
|
|
|
|
def run_command(command, expected_error_message = None):
|
|
try:
|
|
output = subprocess.check_output(command,
|
|
stderr=subprocess.STDOUT,
|
|
universal_newlines=True)
|
|
except subprocess.CalledProcessError as e:
|
|
if expected_error_message is not None:
|
|
if str(e.output).lower().find(expected_error_message.lower()) < 0:
|
|
raise Exception(r"Error message is not as expected {}".format(expected_error_message))
|
|
else:
|
|
raise Exception('Error: Exited with error code: %s, error message: %s' % (e.returncode, e.output))
|
|
else:
|
|
return output
|
|
|
|
class Base(object):
|
|
def __init__(self, server=None, credential=None, debug=True, api_type=""):
|
|
if server is None:
|
|
server = Server(endpoint=get_endpoint(), verify_ssl=False)
|
|
if not isinstance(server.verify_ssl, bool):
|
|
server.verify_ssl = server.verify_ssl == "True"
|
|
|
|
if credential is None:
|
|
credential = Credential(type="basic_auth", username="admin", password="Harbor12345")
|
|
|
|
self.server = server
|
|
self.credential = credential
|
|
self.debug = debug
|
|
self.api_type = api_type
|
|
self.client = _create_client(server, credential, debug, api_type=api_type)
|
|
|
|
def _get_client(self, **kwargs):
|
|
if len(kwargs) == 0:
|
|
return self.client
|
|
|
|
server = self.server
|
|
if "endpoint" in kwargs:
|
|
server.endpoint = kwargs.get("endpoint")
|
|
if "verify_ssl" in kwargs:
|
|
if not isinstance(kwargs.get("verify_ssl"), bool):
|
|
server.verify_ssl = kwargs.get("verify_ssl") == "True"
|
|
else:
|
|
server.verify_ssl = kwargs.get("verify_ssl")
|
|
|
|
credential = Credential(
|
|
kwargs.get("type", self.credential.type),
|
|
kwargs.get("username", self.credential.username),
|
|
kwargs.get("password", self.credential.password),
|
|
)
|
|
|
|
return _create_client(server, credential, self.debug, kwargs.get('api_type', self.api_type))
|