def AddDockerTag(args): """Adds a Docker tag.""" src_image, version_or_tag = _ParseDockerImage(args.DOCKER_IMAGE, _INVALID_DOCKER_IMAGE_ERROR) if version_or_tag is None: raise ar_exceptions.InvalidInputValueError(_INVALID_DOCKER_IMAGE_ERROR) dest_image, tag = _ParseDockerTag(args.DOCKER_TAG) if src_image.GetPackageName() != dest_image.GetPackageName(): raise ar_exceptions.InvalidInputValueError( "Image {}\ndoes not match image {}".format( src_image.GetDockerString(), dest_image.GetDockerString())) client = ar_requests.GetClient() messages = ar_requests.GetMessages() docker_version = version_or_tag if isinstance(version_or_tag, DockerTag): docker_version = DockerVersion( version_or_tag.image, ar_requests.GetVersionFromTag(client, messages, version_or_tag.GetTagName())) try: ar_requests.GetTag(client, messages, tag.GetTagName()) except api_exceptions.HttpNotFoundError: ar_requests.CreateDockerTag(client, messages, tag, docker_version) else: ar_requests.DeleteTag(client, messages, tag.GetTagName()) ar_requests.CreateDockerTag(client, messages, tag, docker_version) log.status.Print("Added tag [{}] to image [{}].".format( tag.GetDockerString(), args.DOCKER_IMAGE))
def _ValidateAndGetDockerVersion(version_or_tag): """Validates a version_or_tag and returns the validated DockerVersion object. Args: version_or_tag: a docker version or a docker tag. Returns: a DockerVersion object. Raises: ar_exceptions.InvalidInputValueError if version_or_tag is not valid. """ try: if isinstance(version_or_tag, DockerVersion): # We have all the information about the docker digest. # Call the API to make sure it exists. ar_requests.GetVersion(ar_requests.GetClient(), ar_requests.GetMessages(), version_or_tag.GetVersionName()) return version_or_tag elif isinstance(version_or_tag, DockerTag): digest = ar_requests.GetVersionFromTag(ar_requests.GetClient(), ar_requests.GetMessages(), version_or_tag.GetTagName()) docker_version = DockerVersion(version_or_tag.image, digest) return docker_version else: raise ar_exceptions.InvalidInputValueError( _INVALID_DOCKER_IMAGE_ERROR) except api_exceptions.HttpNotFoundError: raise ar_exceptions.InvalidInputValueError(_DOCKER_IMAGE_NOT_FOUND)
def _ParseDockerImage(img_str, err_msg): """Validates and parses an image string into a DockerImage. Args: img_str: str, User input docker formatted string. err_msg: str, Error message to return to user. Raises: ar_exceptions.InvalidInputValueError if user input is invalid. ar_exceptions.UnsupportedLocationError if provided location is invalid. Returns: A DockerImage, and a DockerTag or a DockerVersion. """ try: docker_repo = _ParseInput(img_str) except ar_exceptions.InvalidInputValueError: raise ar_exceptions.InvalidInputValueError(_INVALID_DOCKER_IMAGE_ERROR) img_by_digest_match = re.match(DOCKER_IMG_BY_DIGEST_REGEX, img_str) if img_by_digest_match: docker_img = DockerImage(docker_repo, img_by_digest_match.group("img")) return docker_img, DockerVersion(docker_img, img_by_digest_match.group("digest")) img_by_tag_match = re.match(DOCKER_IMG_BY_TAG_REGEX, img_str) if img_by_tag_match: docker_img = DockerImage(docker_repo, img_by_tag_match.group("img")) return docker_img, DockerTag(docker_img, img_by_tag_match.group("tag")) whole_img_match = re.match(DOCKER_IMG_REGEX, img_str) if whole_img_match: return DockerImage(docker_repo, whole_img_match.group("img").strip("/")), None raise ar_exceptions.InvalidInputValueError(err_msg)
def ValidateGcrRepo(repo_name, repo_format, location, docker_format): """Validates input for a gcr.io repository.""" expected_location = _ALLOWED_GCR_REPO_LOCATION.get(repo_name, "") if location != expected_location: raise ar_exceptions.InvalidInputValueError( _INVALID_REPO_LOCATION_ERROR.format(repo_name, expected_location)) if repo_format != docker_format: raise ar_exceptions.InvalidInputValueError( _INVALID_GCR_REPO_FORMAT_ERROR.format(repo_name))
def _ParseDockerImagePath(img_path): """Validates and parses an image path into a DockerImage or a DockerRepo.""" if not img_path: return _GetDefaultResources() resource_val_list = list(filter(None, img_path.split("/"))) try: docker_repo = _ParseInput(img_path) except ar_exceptions.InvalidInputValueError: raise ar_exceptions.InvalidInputValueError(_INVALID_IMAGE_PATH_ERROR) if len(resource_val_list) == 3: return docker_repo elif len(resource_val_list) > 3: return DockerImage(docker_repo, "/".join(resource_val_list[3:])) raise ar_exceptions.InvalidInputValueError(_INVALID_IMAGE_PATH_ERROR)
def _ValidateDockerRepo(repo_name): repo = ar_requests.GetRepository(repo_name) messages = ar_requests.GetMessages() if repo.format != messages.Repository.FormatValueValuesEnum.DOCKER: raise ar_exceptions.InvalidInputValueError( "Invalid repository type {}. The `artifacts docker` command group can " "only be used on Docker repositories.".format(repo.format))
def GetNpmSettingsSnippet(args): """Forms an npm settings snippet to add to the .npmrc file. Args: args: an argparse namespace. All the arguments that were provided to this command invocation. Returns: An npm settings snippet. """ messages = ar_requests.GetMessages() location, repo_path = _GetLocationAndRepoPath( args, messages.Repository.FormatValueValuesEnum.NPM) registry_path = "{location}-npm.pkg.dev/{repo_path}/".format( **{ "location": location, "repo_path": repo_path }) configured_registry = "registry" if args.scope: if not args.scope.startswith("@") or len(args.scope) <= 1: raise ar_exceptions.InvalidInputValueError( "Scope name must start with \"@\" and be longer than 1 character." ) configured_registry = args.scope + ":" + configured_registry data = { "configured_registry": configured_registry, "registry_path": registry_path, "repo_path": repo_path } sa_creds = _GetServiceAccountCreds(args) if sa_creds: npm_setting_template = """\ # Insert following snippet into your .npmrc {configured_registry}=https://{registry_path} //{registry_path}:_password="******" //{registry_path}:username=_json_key_base64 //{registry_path}:[email protected] //{registry_path}:always-auth=true """ data["password"] = base64.b64encode( sa_creds.encode("utf-8")).decode("utf-8") else: npm_setting_template = """\ # Insert following snippet into your .npmrc {configured_registry}=https://{registry_path} //{registry_path}:_authToken="" //{registry_path}:always-auth=true """ return npm_setting_template.format(**data)
def AppendRepoDataToRequest(repo_ref, repo_args, request): """Adds repository data to CreateRepositoryRequest.""" repo_name = repo_ref.repositoriesId if repo_name in _ALLOWED_GCR_REPO_LOCATION: location = _ALLOWED_GCR_REPO_LOCATION.get(repo_name, "") if location != GetLocation(repo_args): raise ar_exceptions.InvalidInputValueError( _INVALID_REPO_LOCATION_ERROR.format(repo_name, location)) elif not _IsValidRepoName(repo_ref.repositoriesId): raise ar_exceptions.InvalidInputValueError(_INVALID_REPO_NAME_ERROR) messages = _GetMessagesForResource(repo_ref) repo_format = messages.Repository.FormatValueValuesEnum( repo_args.repository_format.upper()) if repo_format != messages.Repository.FormatValueValuesEnum.DOCKER: log.status.Print("Note: Language package support is in Alpha.\n") request.repository.name = repo_ref.RelativeName() request.repositoryId = repo_ref.repositoriesId return request
def _GetDefaultResources(): """Gets default config values for project, location, and repository.""" project = properties.VALUES.core.project.Get() location = properties.VALUES.artifacts.location.Get() repo = properties.VALUES.artifacts.repository.Get() if not project or not location or not repo: raise ar_exceptions.InvalidInputValueError( _INVALID_DEFAULT_DOCKER_STRING_ERROR.format(**{ "project": project, "location": location, "repo": repo, })) return DockerRepo(project, location, repo)
def _GetLocationRepoPathAndMavenConfig(args, repo_format): """Get resource values and validate user input.""" repo = _GetRequiredRepoValue(args) project = _GetRequiredProjectValue(args) location = _GetRequiredLocationValue(args) repo_path = project + "/" + repo repo = ar_requests.GetRepository( "projects/{}/locations/{}/repositories/{}".format(project, location, repo)) if repo.format != repo_format: raise ar_exceptions.InvalidInputValueError( "Invalid repository type {}. Valid type is {}.".format( repo.format, repo_format)) return location, repo_path, repo.mavenConfig
def _GetDockerDigest(version_or_tag): """Retrieves the docker digest information. Args: version_or_tag: an object of DockerVersion or DockerTag Returns: A dictionary of information about the given docker image. """ docker_version = version_or_tag try: if isinstance(version_or_tag, DockerVersion): # We have all the information about the docker digest. # Call the API to make sure it exists. ar_requests.GetVersion(ar_requests.GetClient(), ar_requests.GetMessages(), version_or_tag.GetVersionName()) elif isinstance(version_or_tag, DockerTag): digest = ar_requests.GetVersionFromTag(ar_requests.GetClient(), ar_requests.GetMessages(), version_or_tag.GetTagName()) docker_version = DockerVersion(version_or_tag.image, digest) else: raise ar_exceptions.InvalidInputValueError( _INVALID_DOCKER_IMAGE_ERROR) except api_exceptions.HttpNotFoundError: raise ar_exceptions.InvalidInputValueError(_DOCKER_IMAGE_NOT_FOUND) return { "digest": docker_version.digest, "fully_qualified_digest": docker_version.GetDockerString(), "registry": "{}-docker.pkg.dev".format(docker_version.image.docker_repo.location), "repository": docker_version.image.docker_repo.repo, }
def _ParseDockerTag(tag): """Validates and parses a tag string. Args: tag: str, User input Docker tag string. Raises: ar_exceptions.InvalidInputValueError if user input is invalid. ar_exceptions.UnsupportedLocationError if provided location is invalid. Returns: A DockerImage and a DockerTag. """ try: docker_repo = _ParseInput(tag) except ar_exceptions.InvalidInputValueError: raise ar_exceptions.InvalidInputValueError(_INVALID_DOCKER_TAG_ERROR) img_by_tag_match = re.match(DOCKER_IMG_BY_TAG_REGEX, tag) if img_by_tag_match: docker_img = DockerImage(docker_repo, img_by_tag_match.group("img")) return docker_img, DockerTag(docker_img, img_by_tag_match.group("tag")) else: raise ar_exceptions.InvalidInputValueError(_INVALID_DOCKER_TAG_ERROR)
def AppendRepoDataToRequest(repo_ref, repo_args, request): """Adds repository data to CreateRepositoryRequest.""" repo_name = repo_ref.repositoriesId location = GetLocation(repo_args) messages = _GetMessagesForResource(repo_ref) docker_format = messages.Repository.FormatValueValuesEnum.DOCKER repo_format = messages.Repository.FormatValueValuesEnum( repo_args.repository_format.upper()) if repo_name in _ALLOWED_GCR_REPO_LOCATION: ValidateGcrRepo(repo_name, repo_format, location, docker_format) elif not _IsValidRepoName(repo_ref.repositoriesId): raise ar_exceptions.InvalidInputValueError(_INVALID_REPO_NAME_ERROR) request.repository.name = repo_ref.RelativeName() request.repositoryId = repo_ref.repositoriesId return request
def AppendRepoDataToRequest(repo_ref, repo_args, request): """Adds repository data to CreateRepositoryRequest.""" if not _IsValidRepoName(repo_ref.repositoriesId): raise ar_exceptions.InvalidInputValueError(_INVALID_REPO_NAME_ERROR) if not _IsValidLocation(repo_args.location): raise ar_exceptions.UnsupportedLocationError( "{} is not a valid location. Valid locations are [{}].".format( repo_args.location, ", ".join(_VALID_LOCATIONS))) messages = _GetMessagesForResource(repo_ref) repo = messages.Repository( name=repo_ref.RelativeName(), description=repo_args.description, format=messages.Repository.FormatValueValuesEnum( repo_args.repository_format.upper())) request.repository = repo request.repositoryId = repo_ref.repositoriesId return request
def AppendRepoDataToRequest(repo_ref, repo_args, request): """Adds repository data to CreateRepositoryRequest.""" if not _IsValidRepoName(repo_ref.repositoriesId): raise ar_exceptions.InvalidInputValueError(_INVALID_REPO_NAME_ERROR) messages = _GetMessagesForResource(repo_ref) repo_format = messages.Repository.FormatValueValuesEnum( repo_args.repository_format.upper()) if repo_format in [ messages.Repository.FormatValueValuesEnum.MAVEN, messages.Repository.FormatValueValuesEnum.NPM, ]: log.status.Print("Note: Language package support is in Alpha.\n") if repo_format == messages.Repository.FormatValueValuesEnum.APT: log.status.Print("Note: APT package support is in Alpha.\n") request.repository.name = repo_ref.RelativeName() request.repositoryId = repo_ref.repositoriesId return request
def GetNpmSettingsSnippet(args): """Forms an npm settings snippet to add to the .npmrc file. Args: args: an argparse namespace. All the arguments that were provided to this command invocation. Returns: An npm settings snippet. """ messages = ar_requests.GetMessages() location, repo_path = _GetLocationAndRepoPath( args, messages.Repository.FormatValueValuesEnum.NPM) registry_path = "{location}-npm.pkg.dev/{repo_path}/".format( **{ "location": location, "repo_path": repo_path }) configured_registry = "registry" if args.scope: if not args.scope.startswith("@") or len(args.scope) <= 1: raise ar_exceptions.InvalidInputValueError( "Scope name must start with '@' and be longer than 1 character." ) configured_registry = args.scope + ":" + configured_registry npm_setting_template = """\ Please insert following snippet into your .npmrc ====================================================== {configured_registry}=https://{registry_path} //{registry_path}:_password="" //{registry_path}:username=oauth2accesstoken //{registry_path}:[email protected] //{registry_path}:always-auth=true ====================================================== """ data = { "configured_registry": configured_registry, "registry_path": registry_path, "repo_path": repo_path, } return npm_setting_template.format(**data)
def _GetLocationAndRepoPath(args, repo_format): """Get resource values and validate user input.""" repo = _GetRequiredRepoValue(args) project = _GetRequiredProjectValue(args) location = _GetRequiredLocationValue(args) repo_path = project + "/" + repo location_list = ar_requests.ListLocations(project) if location.lower() not in location_list: raise ar_exceptions.UnsupportedLocationError( "{} is not a valid location. Valid locations are [{}].".format( location, ", ".join(location_list))) repo = ar_requests.GetRepository( "projects/{}/locations/{}/repositories/{}".format( project, location, repo)) if repo.format != repo_format: raise ar_exceptions.InvalidInputValueError( "Invalid repository type {}. Valid type is {}.".format( repo.format, repo_format)) return location, repo_path
def _ParseInput(input_str): """Parses user input into project, location, and repository values. Args: input_str: str, user input. Ex: us-docker.pkg.dev/my-proj/my-repo/my-img Raises: ar_exceptions.InvalidInputValueError if user input is invalid. ar_exceptions.UnsupportedLocationError if provided location is invalid. Returns: A DockerRepo. """ matches = re.match(DOCKER_REPO_REGEX, input_str) if not matches: raise ar_exceptions.InvalidInputValueError() location = matches.group("location") project_id = matches.group("project") return DockerRepo(project_id, location, matches.group("repo"))
def GetNpmSettingsSnippet(args): """Forms an npm settings snippet to add to the .npmrc file. Args: args: an argparse namespace. All the arguments that were provided to this command invocation. Returns: An npm settings snippet. """ messages = ar_requests.GetMessages() location, repo_path = _GetLocationAndRepoPath( args, messages.Repository.FormatValueValuesEnum.NPM) registry_path = "{location}-npm.pkg.dev/{repo_path}/".format(**{ "location": location, "repo_path": repo_path }) configured_registry = "registry" if args.scope: if not args.scope.startswith("@") or len(args.scope) <= 1: raise ar_exceptions.InvalidInputValueError( "Scope name must start with \"@\" and be longer than 1 character.") configured_registry = args.scope + ":" + configured_registry data = { "configured_registry": configured_registry, "registry_path": registry_path, "repo_path": repo_path } sa_creds = _GetServiceAccountCreds(args) if sa_creds: npm_setting_template = npm.SERVICE_ACCOUNT_TEMPLATE data["password"] = base64.b64encode( sa_creds.encode("utf-8")).decode("utf-8") else: npm_setting_template = npm.NO_SERVICE_ACCOUNT_TEMPLATE return npm_setting_template.format(**data)
def _ParseInput(input_str): """Parses user input into project, location, and repository values. Args: input_str: str, user input. Ex: us-docker.pkg.dev/my-proj/my-repo/my-img Raises: ar_exceptions.InvalidInputValueError if user input is invalid. ar_exceptions.UnsupportedLocationError if provided location is invalid. Returns: A DockerRepo. """ matches = re.match(DOCKER_REPO_REGEX, input_str) if not matches: raise ar_exceptions.InvalidInputValueError() location = matches.group("location") if not util.IsValidLocation(location): raise ar_exceptions.UnsupportedLocationError( "{} is not a valid location. Valid locations are [{}].".format( location, ", ".join(util.GetLocationList()))) return DockerRepo(matches.group("project"), location, matches.group("repo"))
def _GetRequiredLocationValue(args): if not args.location and not properties.VALUES.artifacts.location.Get(): raise ar_exceptions.InvalidInputValueError(_LOCATION_NOT_FOUND_ERROR) return ar_util.GetLocation(args)
def _GetRequiredRepoValue(args): if not args.repository and not properties.VALUES.artifacts.repository.Get( ): raise ar_exceptions.InvalidInputValueError(_REPO_NOT_FOUND_ERROR) return ar_util.GetRepo(args)
def _GetRequiredProjectValue(args): if not args.project and not properties.VALUES.core.project.Get(): raise ar_exceptions.InvalidInputValueError(_PROJECT_NOT_FOUND_ERROR) return ar_util.GetProject(args)
def ListFiles(args): """Lists files in a given project. Args: args: User input arguments. Returns: List of files. """ client = ar_requests.GetClient() messages = ar_requests.GetMessages() project = GetProject(args) location = args.location or properties.VALUES.artifacts.location.Get() repo = GetRepo(args) package = args.package version = args.version tag = args.tag page_size = args.page_size arg_filters = "" # Parse fully qualified path in package argument if package: if re.match( r"projects\/.*\/locations\/.*\/repositories\/.*\/packages\/.*", package): params = package.replace("projects/", "", 1).replace( "/locations/", " ", 1).replace("/repositories/", " ", 1).replace("/packages/", " ", 1).split(" ") project, location, repo, package = [ params[i] for i in range(len(params)) ] # Escape slashes in package name if package: package = package.replace("/", "%2F") # Retrieve version from tag name if version and tag: raise ar_exceptions.InvalidInputValueError( "Specify either --version or --tag with --package argument.") if package and tag: tag_path = resources.Resource.RelativeName( resources.REGISTRY.Create( "artifactregistry.projects.locations.repositories.packages.tags", projectsId=project, locationsId=location, repositoriesId=repo, packagesId=package, tagsId=tag)) version = ar_requests.GetVersionFromTag(client, messages, tag_path) if package and version: version_path = resources.Resource.RelativeName( resources.REGISTRY.Create( "artifactregistry.projects.locations.repositories.packages.versions", projectsId=project, locationsId=location, repositoriesId=repo, packagesId=package, versionsId=version)) arg_filters = 'owner="{}"'.format(version_path) elif package: package_path = resources.Resource.RelativeName( resources.REGISTRY.Create( "artifactregistry.projects.locations.repositories.packages", projectsId=project, locationsId=location, repositoriesId=repo, packagesId=package)) arg_filters = 'owner="{}"'.format(package_path) elif version or tag: raise ar_exceptions.InvalidInputValueError( "Package name is required when specifying version or tag.") repo_path = resources.Resource.RelativeName( resources.REGISTRY.Create( "artifactregistry.projects.locations.repositories", projectsId=project, locationsId=location, repositoriesId=repo)) files = ar_requests.ListFiles(client, messages, repo_path, arg_filters, page_size) for file in files: file.name = resources.REGISTRY.ParseRelativeName( file.name, collection="artifactregistry.projects.locations.repositories.files" ).filesId.replace("%2F", "/") return files