feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,485 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Anthos command library functions and utilities for the anthoscli binary."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import base64
import copy
import json
import os
from googlecloudsdk.command_lib.anthos import flags
from googlecloudsdk.command_lib.anthos.common import file_parsers
from googlecloudsdk.command_lib.anthos.common import messages
from googlecloudsdk.command_lib.util.anthos import binary_operations
from googlecloudsdk.core import exceptions as c_except
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.credentials import store as c_store
from googlecloudsdk.core.util import files
from googlecloudsdk.core.util import platforms
import requests
import six
from six.moves import urllib
DEFAULT_ENV_ARGS = {'COBRA_SILENCE_USAGE': 'true'}
DEFAULT_LOGIN_CONFIG_PATH = {
platforms.OperatingSystem.LINUX.id:
'~/.config/google/anthos/kubectl-anthos-config.yaml',
platforms.OperatingSystem.MACOSX.id:
'~/Library/Preferences/google/anthos/kubectl-anthos-config.yaml',
platforms.OperatingSystem.WINDOWS.id:
os.path.join('%APPDATA%', 'google', 'anthos',
'kubectl-anthos-config.yaml')
}
def GetEnvArgsForCommand(extra_vars=None, exclude_vars=None):
"""Return an env dict to be passed on command invocation."""
env = copy.deepcopy(os.environ)
env.update(DEFAULT_ENV_ARGS)
if extra_vars:
env.update(extra_vars)
if exclude_vars:
for k in exclude_vars:
env.pop(k)
return env
class AnthosAuthException(c_except.Error):
"""Base Exception for auth issues raised by gcloud anthos surface."""
def RelativePkgPathFromFullPath(path):
"""Splits full path into relative(basename) path and parent dir."""
normpath = os.path.normpath(path)
rel_path = os.path.basename(normpath)
parent_dir = os.path.dirname(normpath) or rel_path
return rel_path, parent_dir
class AnthosCliWrapper(binary_operations.StreamingBinaryBackedOperation):
"""Binary operation wrapper for anthoscli commands."""
def __init__(self, **kwargs):
custom_errors = {
'MISSING_EXEC': messages.MISSING_BINARY.format(binary='anthoscli')
}
super(AnthosCliWrapper, self).__init__(
binary='anthoscli', custom_errors=custom_errors, **kwargs)
def _ParseGetArgs(self, repo_uri, local_dest, file_pattern=None, **kwargs):
del kwargs # Not Used Here
exec_args = ['get', repo_uri, local_dest]
if file_pattern:
exec_args.extend(['--pattern', file_pattern])
return exec_args
def _ParseUpdateArgs(self,
local_dir,
repo_uri=None,
strategy=None,
dry_run=False,
verbose=False,
**kwargs):
del kwargs # Not Used here
exec_args = ['update', local_dir]
if repo_uri:
exec_args.extend(['--repo', repo_uri])
if dry_run:
exec_args.append('--dry-run')
if strategy:
exec_args.extend(['--strategy', strategy])
if verbose:
exec_args.append('--verbose')
return exec_args
def _ParseDescribeArgs(self, local_dir, **kwargs):
del kwargs # Not Used here
return ['desc', local_dir]
def _ParseTags(self, tags):
return ','.join(['{}={}'.format(x, y) for x, y in six.iteritems(tags)])
def _ParseInitArgs(self,
local_dir,
description=None,
name=None,
tags=None,
info_url=None,
**kwargs):
del kwargs # Not Used here
package_path = local_dir
if not package_path.endswith('/'):
package_path += '/'
exec_args = ['init', package_path]
if description:
exec_args.extend(['--description', description])
if name:
exec_args.extend(['--name', name])
if tags:
exec_args.extend(['--tag', self._ParseTags(tags)])
if info_url:
exec_args.extend(['--url', info_url])
return exec_args
def _ParseApplyArgs(self, apply_dir, project, **kwargs):
del kwargs # Not Used Here
exec_args = ['apply', '-f', apply_dir, '--project', project]
return exec_args
def _ParseExportArgs(self, cluster, project, location, output_dir, **kwargs):
del kwargs # Not Used here
exec_args = ['export', '-c', cluster, '--project', project]
if location:
exec_args.extend(['--location', location])
if output_dir:
exec_args.extend(['--output-directory', output_dir])
return exec_args
def _ParseArgsForCommand(self, command, **kwargs):
if command == 'get':
return self._ParseGetArgs(**kwargs)
if command == 'update':
return self._ParseUpdateArgs(**kwargs)
if command == 'desc':
return self._ParseDescribeArgs(**kwargs)
if command == 'init':
return self._ParseInitArgs(**kwargs)
if command == 'apply':
return self._ParseApplyArgs(**kwargs)
if command == 'export':
return self._ParseExportArgs(**kwargs)
raise binary_operations.InvalidOperationForBinary(
'Invalid Operation [{}] for anthoscli'.format(command))
def GetAuthToken(account, operation, impersonated=False):
"""Generate a JSON object containing the current gcloud auth token."""
try:
access_token = c_store.GetFreshAccessToken(
account, allow_account_impersonation=impersonated)
output = {
'auth_token': access_token,
}
except Exception as e: # pylint: disable=broad-except
raise AnthosAuthException(
'Error retrieving auth credentials for {operation}: {error}. '.format(
operation=operation, error=e))
return json.dumps(output, sort_keys=True)
class AnthosAuthWrapper(binary_operations.StreamingBinaryBackedOperation):
"""Binary operation wrapper for anthoscli commands."""
def __init__(self, **kwargs):
custom_errors = {
'MISSING_EXEC':
messages.MISSING_AUTH_BINARY.format(binary='kubectl-anthos')
}
super(AnthosAuthWrapper, self).__init__(
binary='kubectl-anthos', custom_errors=custom_errors, **kwargs)
@property
def default_config_path(self):
return files.ExpandHomeAndVars(
DEFAULT_LOGIN_CONFIG_PATH[platforms.OperatingSystem.Current().id])
def _ParseLoginArgs(
self,
cluster,
kube_config=None,
login_config=None,
login_config_cert=None,
user=None,
ldap_user=None,
ldap_pass=None,
dry_run=None,
preferred_auth=None,
server_url=None,
no_browser=None,
remote_bootstrap=None,
**kwargs
):
del kwargs # Not Used Here
exec_args = ['login']
if cluster:
exec_args.extend(['--cluster', cluster])
if kube_config:
exec_args.extend(['--kubeconfig', kube_config])
if login_config:
exec_args.extend(['--login-config', login_config])
if login_config_cert:
exec_args.extend(['--login-config-cert', login_config_cert])
if user:
exec_args.extend(['--user', user])
if dry_run:
exec_args.extend(['--dry-run'])
if ldap_pass and ldap_user:
exec_args.extend(
['--ldap-username', ldap_user, '--ldap-password', ldap_pass])
if preferred_auth:
exec_args.extend(['--preferred-auth', preferred_auth])
if server_url:
exec_args.extend(['--server', server_url])
if no_browser:
exec_args.extend(['--remote-login'])
if remote_bootstrap:
exec_args.extend(['--remote-bootstrap', remote_bootstrap])
return exec_args
def _ParseCreateLoginConfigArgs(self,
kube_config,
output_file=None,
merge_from=None,
**kwargs):
del kwargs # Not Used Here
exec_args = ['create-login-config']
exec_args.extend(['--kubeconfig', kube_config])
if output_file:
exec_args.extend(['--output', output_file])
if merge_from:
exec_args.extend(['--merge-from', merge_from])
return exec_args
def _ParseTokenArgs(self, token_type, cluster, aws_sts_region, id_token,
access_token, access_token_expiry, refresh_token,
client_id, client_secret, idp_certificate_authority_data,
idp_issuer_url, kubeconfig_path, user, **kwargs):
del kwargs # Not Used Here
exec_args = ['token']
if token_type:
exec_args.extend(['--type', token_type])
if cluster:
exec_args.extend(['--cluster', cluster])
if aws_sts_region:
exec_args.extend(['--aws-sts-region', aws_sts_region])
if id_token:
exec_args.extend(['--id-token', id_token])
if access_token:
exec_args.extend(['--access-token', access_token])
if access_token_expiry:
exec_args.extend(['--access-token-expiry', access_token_expiry])
if refresh_token:
exec_args.extend(['--refresh-token', refresh_token])
if client_id:
exec_args.extend(['--client-id', client_id])
if client_secret:
exec_args.extend(['--client-secret', client_secret])
if idp_certificate_authority_data:
exec_args.extend(
['--idp-certificate-authority-data', idp_certificate_authority_data])
if idp_issuer_url:
exec_args.extend(['--idp-issuer-url', idp_issuer_url])
if kubeconfig_path:
exec_args.extend(['--kubeconfig-path', kubeconfig_path])
if user:
exec_args.extend(['--user', user])
return exec_args
def _ParseArgsForCommand(self, command, **kwargs):
if command == 'login':
return self._ParseLoginArgs(**kwargs)
elif command == 'create-login-config':
return self._ParseCreateLoginConfigArgs(**kwargs)
elif command == 'version':
return ['version']
elif command == 'token':
return self._ParseTokenArgs(**kwargs)
else:
raise binary_operations.InvalidOperationForBinary(
'Invalid Operation [{}] for kubectl-anthos'.format(command))
def _GetClusterConfig(all_configs, cluster):
found_clusters = all_configs.FindMatchingItem(
file_parsers.LoginConfigObject.CLUSTER_NAME_KEY, cluster)
if len(found_clusters) != 1:
raise AnthosAuthException(
'Cluster [{}] not found for config path [{}]'.format(
cluster, all_configs.file_path))
return found_clusters.pop()
def _Base64EncodeLdap(username, passwd):
"""Base64 Encode Ldap username and password."""
enc = lambda s: six.ensure_text(base64.b64encode(six.ensure_binary(s)))
return enc(username), enc(passwd)
def _GetLdapUserAndPass(cluster_config, auth_name, cluster):
"""Prompt User for Ldap Username and Password."""
ldap_user = None
ldap_pass = None
if not cluster_config.IsLdap():
return None, None
# do the prompting
user_message = ('Please enter the ldap user for '
'[{}] on cluster [{}]: '.format(auth_name, cluster))
pass_message = ('Please enter the ldap password for '
'[{}] on cluster [{}]: '.format(auth_name, cluster))
ldap_user = console_io.PromptWithValidator(
validator=lambda x: len(x) > 1,
error_message='Error: Invalid username, please try again.',
prompt_string=user_message)
ldap_pass = console_io.PromptPassword(
pass_message, validation_callable=lambda x: len(x) > 1)
return _Base64EncodeLdap(ldap_user, ldap_pass)
def GetFileOrURL(cluster_config, certificate_file=True):
"""Parses config input to determine whether URL or File logic should execute.
Determines whether the cluster_config is a file or URL. If it's a URL, it
then pulls the contents of the file using a GET request. If it's a
file, then it expands the file path and returns its contents.
Args:
cluster_config: str, A file path or URL for the login-config.
certificate_file: str, Optional file path to the CA certificate to use with
the GET request to the URL.
Raises:
AnthosAuthException: If the data could not be pulled from the URL.
Returns:
parsed_config_fileOrURL, config_contents, and is_url
parsed_config_fileOrURL: str, returns either the URL that was passed or an
expanded file path if a file was passed.
config_contents: str, returns the contents of the file or URL.
is_url: bool, True if the provided cluster_config input was a URL.
"""
if not cluster_config:
return None, None, None
# Handle if input is URL.
config_url = urllib.parse.urlparse(cluster_config)
is_url = config_url.scheme == 'http' or config_url.scheme == 'https'
if is_url:
response = requests.get(cluster_config, verify=certificate_file or True)
if response.status_code != requests.codes.ok:
raise AnthosAuthException('Request to login-config URL failed with'
'response code [{}] and text [{}]: '.format(
response.status_code, response.text))
return cluster_config, response.text, is_url
# Handle if input is file.
expanded_config_path = flags.ExpandLocalDirAndVersion(cluster_config)
contents = files.ReadFileContents(expanded_config_path)
return expanded_config_path, contents, is_url
def GetPreferredAuthForCluster(cluster,
login_config,
config_contents=None,
force_update=False,
is_url=False):
"""Get preferredAuthentication value for cluster."""
if not (cluster and login_config):
return None, None, None
configs = None
# If URL, then pass contents directly.
if is_url:
if not config_contents:
raise AnthosAuthException(
'Config contents were not passed with URL [{}]'.format(login_config))
configs = file_parsers.YamlConfigFile(
file_contents=config_contents, item_type=file_parsers.LoginConfigObject)
# If file, pass contents and location for updating.
else:
configs = file_parsers.YamlConfigFile(
file_contents=config_contents,
file_path=login_config,
item_type=file_parsers.LoginConfigObject)
cluster_config = _GetClusterConfig(configs, cluster)
try:
auth_method = cluster_config.GetPreferredAuth()
except KeyError:
auth_method = None
except file_parsers.YamlConfigObjectFieldError:
# gracefully quit for config versions older than v2alpha1 that
# do not support 'preferredAuthentication' field.
return None, None, None
if not auth_method or force_update:
providers = cluster_config.GetAuthProviders()
if not providers:
raise AnthosAuthException(
'No Authentication Providers found in [{}]'.format(login_config))
if len(providers) == 1:
auth_method = providers.pop()
else: # do the prompting
prompt_message = ('Please select your preferred authentication option '
'for cluster [{}]'.format(cluster))
override_warning = ('. Note: This will overwrite current preferred auth '
'method [{}] in config file.')
# Only print override warning in certain cases.
if auth_method and force_update and not is_url:
prompt_message = prompt_message + override_warning.format(auth_method)
index = console_io.PromptChoice(
providers, message=prompt_message, cancel_option=True)
auth_method = providers[index]
log.status.Print(
'Setting Preferred Authentication option to [{}]'.format(auth_method))
cluster_config.SetPreferredAuth(auth_method)
# Only save to disk if file is specified. Don't want URL failure.
if login_config and not is_url:
configs.WriteToDisk()
ldap_user, ldap_pass = _GetLdapUserAndPass(cluster_config, auth_method,
cluster)
return auth_method, ldap_user, ldap_pass
def LoginResponseHandler(response, list_clusters_only=False):
"""Handle Login Responses."""
if response.stdout:
log.status.Print(response.stdout)
if response.stderr:
log.status.Print(response.stderr)
if response.failed:
log.error(messages.LOGIN_CONFIG_FAILED_MESSAGE.format(response.stderr))
return None
if not list_clusters_only:
log.status.Print(messages.LOGIN_CONFIG_SUCCESS_MESSAGE)
return response.stdout

View File

@@ -0,0 +1,358 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Classes for reading and writing Anthos related files."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import collections
import copy
import io
from googlecloudsdk.core import exceptions as core_exceptions
from googlecloudsdk.core import yaml
from googlecloudsdk.core.util import files
try: # Fallback for <= PY3.2
collections_abc = collections.abc
except AttributeError:
collections_abc = collections
AUTH_VERSION_1_ALPHA = 'authentication.gke.io/v1alpha1'
AUTH_VERSION_2_ALPHA = 'authentication.gke.io/v2alpha1'
API_VERSION = 'apiVersion'
class YamlConfigObjectError(core_exceptions.Error):
"""Raised when an invalid Operation is invoked on YamlConfigObject."""
class YamlConfigFileError(core_exceptions.Error):
"""Base class for YamlConfigFile Errors."""
class YamlConfigObjectFieldError(YamlConfigObjectError):
"""Raised when an invalid field is used on a YamlConfigObject."""
def __init__(self, field, object_type, custom_message=None):
error_msg = 'Invalid field [{}] for YamlConfigObject type [{}]'.format(
field, object_type)
if custom_message:
error_msg = '{}: {}'.format(error_msg, custom_message)
super(YamlConfigObjectFieldError, self).__init__(error_msg)
def FindOrSetItemInDict(item, item_path, item_sep='.', set_value=None):
"""Finds (potentially) nested value based on specified node_path.
If set_value is passed will set the value at item_path,
creating if needed.
Args:
item: Dict, Map like object to search.
item_path: str, An item_sep separated path to nested item in map.
item_sep: str, Path item separator, default is '.'.
set_value: object, value to set at item_path. If path is not found
create a new item at item_path with value of set_value.
Returns:
Object, data found in input item at item_path or None.
Raises:
KeyError: If item_path not found or empty.
"""
if not item_path:
raise KeyError(item_path)
parts = item_path.split(item_sep)
parts.reverse()
context = item
while parts:
part = parts.pop()
if part in context and yaml.dict_like(context): # path element exists in
# in context AND context
# is dict() like.
if set_value and not parts: # e.g. at bottom of path with a value to set
context[part] = set_value
context = context.get(part) # continue down the path
else: # part not found
if set_value and yaml.dict_like(context): # Upsert New Value if possible,
# otherwise, Error
if parts: # more of the path remains, so insert empty containers
context[part] = collections.OrderedDict()
context = context.get(part)
else: # e.g. at bottom of path
context[part] = set_value
else:
raise KeyError('Path [{}] not found'.format(item_path))
return context
def DeleteItemInDict(item, item_path, item_sep='.'):
"""Finds and deletes (potentially) nested value based on specified node_path.
Args:
item: Dict, Map like object to search.
item_path: str, An item_sep separated path to nested item in map.
item_sep: str, Path item separator, default is '.'.
Raises:
KeyError: If item_path not found or empty.
"""
if not item_path:
raise KeyError('Missing Path')
parts = item_path.split(item_sep)
parts.reverse()
context = item
while parts:
part = parts.pop()
if part in context and yaml.dict_like(context):
elem = context.get(part)
if not parts:
if elem:
del context[part]
else:
raise KeyError('Path [{}] not found'.format(item_path))
else:
context = elem
else:
raise KeyError('Path [{}] not found'.format(item_path))
class YamlConfigObject(collections_abc.MutableMapping):
"""Abstraction for managing resource configuration Object.
Attributes:
content: YAMLObject, The parsed underlying config data.
"""
def __init__(self, content):
self._content = content
@property
def content(self):
return copy.deepcopy(self._content)
def _FindOrSetItem(self, item_path, item_sep='.', set_value=None):
"""Finds (potentially) nested value based on specified item_path.
Args:
item_path: str, An item_sep separated path to nested item in map.
item_sep: str, Path item separator, default is '.'.
set_value: object, value to set at item_path. If path is not found
create a new item at item_path with value of set_value.
Returns:
Object, item found in map at item_path or None.
"""
return FindOrSetItemInDict(self._content, item_path, item_sep, set_value)
def __str__(self):
yaml.convert_to_block_text(self._content)
return yaml.dump(self._content, round_trip=True)
def __setitem__(self, key, value):
self._FindOrSetItem(key, set_value=value)
def __getitem__(self, key):
return self._FindOrSetItem(key)
def __delitem__(self, key):
DeleteItemInDict(self._content, key)
def __iter__(self):
return iter(self._content)
def __len__(self):
return len(self._content)
def __contains__(self, key_path):
try:
self._FindOrSetItem(key_path)
except KeyError:
return False
return True
class LoginConfigObject(YamlConfigObject):
"""Auth Login Config file abstraction."""
PREFERRED_AUTH_KEY = 'spec.preferredAuthentication'
AUTH_PROVIDERS_KEY = 'spec.authentication'
CLUSTER_NAME_KEY = 'spec.name'
@property
def version(self):
return self[API_VERSION]
def _FindMatchingAuthMethod(self, method_name, method_type):
providers = self.GetAuthProviders(name_only=False)
found = [
x for x in providers
if x['name'] == method_name and x[method_type] is not None
]
if found: # return first matching item
return found.pop()
return None
def IsLdap(self):
"""Returns true is the current preferredAuth Method is ldap."""
try:
auth_name = self.GetPreferredAuth()
found_auth = self._FindMatchingAuthMethod(auth_name, 'ldap')
if found_auth:
return True
except (YamlConfigObjectFieldError, KeyError):
pass # Fall through to False return
return False
def GetPreferredAuth(self):
if self.version == AUTH_VERSION_2_ALPHA:
return self[self.PREFERRED_AUTH_KEY]
else:
raise YamlConfigObjectFieldError(self.PREFERRED_AUTH_KEY,
self.__class__.__name__,
'requires config version [{}]'.format(
AUTH_VERSION_2_ALPHA))
def SetPreferredAuth(self, auth_value):
if self.version == AUTH_VERSION_2_ALPHA:
self[self.PREFERRED_AUTH_KEY] = auth_value
else:
raise YamlConfigObjectFieldError(self.PREFERRED_AUTH_KEY,
self.__class__.__name__,
'requires config version [{}]'.format(
AUTH_VERSION_2_ALPHA))
def GetAuthProviders(self, name_only=True):
try:
providers = self[self.AUTH_PROVIDERS_KEY]
except KeyError:
return None
if not providers:
return None
if name_only:
return [provider['name'] for provider in providers]
return providers
class YamlConfigFile(object):
"""Utility class for searching and editing collections of YamlObjects.
Attributes:
item_type: class, YamlConfigObject class type of the items in file
file_contents: str, YAML contents used to load YamlConfigObjects
file_path: str, file path that YamlConfigObjects were loaded from
data: [YamlObject], data loaded from file path. Could be 1 or more objects.
yaml: str, yaml string representation of object.
"""
def __init__(self, item_type, file_contents=None, file_path=None):
self._file_contents = file_contents
self._file_path = file_path
self._item_type = item_type
if not self._file_contents and not self._file_path:
raise YamlConfigFileError('Could Not Initialize YamlConfigFile:'
'file_contents And file_path Are Both Empty')
# Priority is to try to load from contents if specified. Else from file.
if self._file_contents:
try:
items = yaml.load_all(self._file_contents, round_trip=True)
self._data = [item_type(x) for x in items]
except yaml.YAMLParseError as fe:
raise YamlConfigFileError('Error Parsing Config File: [{}]'.format(fe))
elif self._file_path:
try:
items = yaml.load_all_path(self._file_path, round_trip=True)
self._data = [item_type(x) for x in items]
except yaml.FileLoadError as fe:
raise YamlConfigFileError('Error Loading Config File: [{}]'.format(fe))
@property
def item_type(self):
return self._item_type
@property
def data(self):
return self._data
@property
def yaml(self):
if len(self._data) == 1:
return str(self._data[0])
return '---\n'.join([str(x) for x in self._data])
@property
def file_contents(self):
return self._file_contents
@property
def file_path(self):
return self._file_path
def __str__(self):
return self.yaml
def __eq__(self, other):
if isinstance(other, YamlConfigFile):
return (len(self.data) == len(other.data) and
all(x == y for x, y in zip(self.data, other.data)))
return False
def FindMatchingItem(self, search_path, value):
"""Find all YamlObjects with matching data at search_path."""
results = []
for obj in self.data:
if obj[search_path] == value:
results.append(obj)
return results
def FindMatchingItemData(self, search_path):
"""Find all data in YamlObjects at search_path."""
results = []
for obj in self.data:
value = obj[search_path]
if value:
results.append(value)
return results
def SetMatchingItemData(self, object_path, object_value,
item_path, item_value, persist=True):
"""Find all matching YamlObjects and set values."""
results = []
found_items = self.FindMatchingItem(object_path, object_value)
for ymlconfig in found_items:
ymlconfig[item_path] = item_value
results.append(ymlconfig)
if persist:
self.WriteToDisk()
return results
def WriteToDisk(self):
"""Overwrite Original Yaml File."""
# Only write if file_path is specified.
if not self.file_path:
raise YamlConfigFileError('Could Not Write To Config File: Path Is Empty')
out_file_buf = io.BytesIO()
tmp_yaml_buf = io.TextIOWrapper(out_file_buf, newline='\n',
encoding='utf-8')
yaml.dump_all_round_trip([x.content for x in self.data],
stream=tmp_yaml_buf)
with files.BinaryFileWriter(self.file_path) as f:
tmp_yaml_buf.seek(0)
f.write(out_file_buf.getvalue())

View File

@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command line flags for parsing kubectl config files commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
from googlecloudsdk.api_lib.container import kubeconfig as kconfig
from googlecloudsdk.calliope import base
from googlecloudsdk.core import exceptions as core_exceptions
class MissingEnvVarError(core_exceptions.Error):
"""An exception raised when required environment variables are missing."""
class ConfigParsingError(core_exceptions.Error):
"""An exception raised when parsing kubeconfig file."""
class MissingConfigError(core_exceptions.Error):
"""An exception raised when kubeconfig file is missing."""
def GetKubeConfigFlag(
help_txt='The path to the Kubeconfig file to use.',
required=False):
return base.Argument(
'--kubeconfig',
required=required,
help=help_txt)
def GetKubeContextFlag(help_txt='The Kubernetes context to use.'):
return base.Argument(
'--context', required=False, help=help_txt)
def GetKubeconfigAndContext(kubeconfig=None, context=None):
"""Get the Kubeconfig path and context."""
config = kubeconfig or kconfig.Kubeconfig.DefaultPath()
if not config or not os.access(config, os.R_OK):
raise MissingConfigError(
'kubeconfig file not found or is not readable : [{}]'.format(config))
context_name = context or 'current-context'
kc = kconfig.Kubeconfig.LoadFromFile(config)
# Validate that passed context exists in specified kubeconfig
if context_name == 'current-context':
context_name = kc.current_context
elif context_name not in kc.contexts:
raise ConfigParsingError(
'context [{}] does not exist in kubeconfig [{}]'.format(
context_name, kubeconfig))
return config, context_name

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Shared messages for anthos surface."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
MISSING_BINARY = ('Could not locate anthos executable [{binary}]'
' on the system PATH. '
'Please ensure gcloud anthos component is properly '
'installed. '
'See https://cloud.google.com/sdk/docs/components for '
'more details.')
MISSING_AUTH_BINARY = ('Could not locate anthos auth executable [{binary}]'
' on the system PATH. '
'Please ensure gcloud anthos-auth component is properly '
'installed. '
'See https://cloud.google.com/sdk/docs/components for '
'more details.')
LOGIN_CONFIG_MESSAGE = 'Configuring Anthos authentication '
LOGIN_CONFIG_SUCCESS_MESSAGE = LOGIN_CONFIG_MESSAGE + 'success.'
LOGIN_CONFIG_FAILED_MESSAGE = LOGIN_CONFIG_MESSAGE + 'failed\n {}'

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

View File

@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Instance creation request modifier."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import base
def Messages(api_version):
return apis.GetMessagesModule('krmapihosting', api_version)
def CreateUpdateRequest(release_track, ref, args):
"""Returns an updated request formatted to the right URI endpoint."""
messages = Messages(ref.GetCollectionInfo().api_version)
# krmapihosting create endpoint uses a different uri from the one generated,
# we will need to construct it manually
custom_uri = 'projects/{project_id}/locations/{location}'.format(
project_id=ref.projectsId, location=args.location)
# We don't expose the bundle in this surface.
bundles_config = messages.BundlesConfig(
configControllerConfig=messages.ConfigControllerConfig(enabled=True))
if release_track == base.ReleaseTrack.ALPHA and args.IsSpecified(
'experimental_features'):
bundles_config.configControllerConfig.experimentalFeatures = args.experimental_features
krm_api_host = messages.KrmApiHost(
bundlesConfig=bundles_config)
if args.use_private_endpoint:
# Omit on False to make wired format cleaner.
krm_api_host.usePrivateEndpoint = args.use_private_endpoint
# Populate man blocks if provided
multiple_cidr_blocks = []
if args.IsSpecified('man_block') and args.IsSpecified('man_blocks'):
raise Exception('man_block and man_blocks can not be set at the same time')
if args.IsSpecified('man_blocks'):
for cidr_block in args.man_blocks:
cur_cidr_block = messages.CidrBlock(cidrBlock=cidr_block)
multiple_cidr_blocks.append(cur_cidr_block)
man_blocks = messages.MasterAuthorizedNetworksConfig(
cidrBlocks=multiple_cidr_blocks)
if args.full_management:
full_mgmt_config = messages.FullManagementConfig(
clusterCidrBlock=args.cluster_ipv4_cidr_block,
clusterNamedRange=args.cluster_named_range,
manBlock=args.man_block,
masterIpv4CidrBlock=args.master_ipv4_cidr_block,
network=args.network,
subnet=args.subnet,
servicesCidrBlock=args.services_ipv4_cidr_block,
servicesNamedRange=args.services_named_range)
if args.IsSpecified('man_blocks'):
full_mgmt_config.masterAuthorizedNetworksConfig = man_blocks
mgmt_config = messages.ManagementConfig(
fullManagementConfig=full_mgmt_config)
krm_api_host.managementConfig = mgmt_config
else:
# BUG(xiaoweim): moved the default value up to be a const
# Default master ipv4 cidr block address if not provided
master_ipv4_cidr_block = '172.16.0.128/28'
if args.master_ipv4_cidr_block is not None:
master_ipv4_cidr_block = args.master_ipv4_cidr_block
std_mgmt_config = messages.StandardManagementConfig(
clusterCidrBlock=args.cluster_ipv4_cidr_block,
clusterNamedRange=args.cluster_named_range,
manBlock=args.man_block,
masterIpv4CidrBlock=master_ipv4_cidr_block,
network=args.network,
subnet=args.subnet,
servicesCidrBlock=args.services_ipv4_cidr_block,
servicesNamedRange=args.services_named_range)
if args.IsSpecified('man_blocks'):
std_mgmt_config.masterAuthorizedNetworksConfig = man_blocks
mgmt_config = messages.ManagementConfig(
standardManagementConfig=std_mgmt_config)
krm_api_host.managementConfig = mgmt_config
request = (
messages.KrmapihostingProjectsLocationsKrmApiHostsCreateRequest(
parent=custom_uri,
krmApiHostId=ref.krmApiHostsId,
krmApiHost=krm_api_host))
return request

View File

@@ -0,0 +1,160 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Flags for the config controller command group."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import actions
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
def AddAsyncFlag(parser):
"""Adds --async flag."""
base.ASYNC_FLAG.AddToParser(parser)
def AddMasterIPv4CIDRBlock(parser):
"""Adds --master-ipv4-cidr-block flag."""
parser.add_argument(
"--master-ipv4-cidr-block",
help=("The /28 network that the control plane will use. "
"Defaults to '172.16.0.128/28' if flag is not provided."))
def AddNetworkFlag(parser):
"""Adds --network flag."""
parser.add_argument(
"--network",
help=("Existing VPC Network to put the GKE cluster and nodes in. "
"Defaults to 'default' if flag is not provided. If "
"`--subnet=SUBNET` is also specified, subnet must be a subnetwork "
"of the network specified by this `--network=NETWORK` flag."))
def AddSubnetFlag(parser):
"""Adds --subnet flag."""
parser.add_argument(
"--subnet",
help=("Specifies the subnet that the VM instances are a part of. "
"`--network=NETWORK` must also be specified, subnet must be a "
"subnetwork of the network specified by the `--network=NETWORK` "
"flag."))
def AddManBlockFlag(parser):
"""Adds --man-block flag."""
parser.add_argument(
"--man-block",
help=("Master Authorized Network. "
"Allows access to the Kubernetes control plane from this block. "
"Defaults to '0.0.0.0/0' if flag is not provided."))
def AddManBlockFlagDeprecated(parser):
"""Adds --man-block flag."""
parser.add_argument(
"--man-block",
help=("Master Authorized Network. "
"Allows access to the Kubernetes control plane from this block. "
"Defaults to `0.0.0.0/0` if flag is not provided."),
action=actions.DeprecationAction(
"--man-block",
warn="The {flag_name} option is deprecated; use --man-blocks instead.",
removed=False))
def AddManBlocksFlag(parser):
"""Adds --man-blocks flag."""
parser.add_argument(
"--man-blocks",
type=arg_parsers.ArgList(),
metavar="BLOCK",
help=("Master Authorized Network. "
"Allows users to specify multiple blocks to access the Kubernetes"
"control plane from this block. "
"Defaults to `0.0.0.0/0` if flag is not provided."))
def AddClusterIPv4CIDRBlock(parser):
"""Adds --cluster-ipv4-cidr-block flag."""
parser.add_argument(
"--cluster-ipv4-cidr-block",
help=("The IP address range for the cluster pod IPs. "
"Can be specified as a netmask size (e.g. '/14') or as in CIDR "
"notation (e.g. '10.100.0.0/20'). Defaults to '/20' if flag is "
"not provided."))
def AddServicesIPv4CIDRBlack(parser):
"""Adds --services-ipv4-cidr-block flag."""
parser.add_argument(
"--services-ipv4-cidr-block",
help=("The IP address range for the cluster service IPs. Can be "
"specified as a netmask size (e.g. '/14') or as in CIDR "
"notation (e.g. '10.100.0.0/20'). Defaults to '/24' if flag is "
"not provided."))
def AddClusterNamedRangeFlag(parser):
"""Adds --cluster-named-range flag."""
parser.add_argument(
"--cluster-named-range",
help=("The name of the existing secondary range in the clusters "
"subnetwork to use for pod IP addresses. Alternatively, "
"`--cluster_cidr_block` can be used to automatically create a "
"GKE-managed one."))
def AddServicesNamedRange(parser):
"""Adds --services-named-range flag."""
parser.add_argument(
"--services-named-range",
help=("The name of the existing secondary range in the clusters "
"subnetwork to use for service ClusterIPs. Alternatively, "
"`--services_cidr_block` can be used to automatically create a "
"GKE-managed one."))
def AddFullManagement(parser):
"""Adds --full-management flag."""
parser.add_argument(
"--full-management",
# Use store_const so that gcloud doesn't generate a hidden
# --no-full-management flag. See yaqs/4400496223010684928 for more
# details.
action="store_const",
const=True,
help=("Enable full cluster management type."))
def AddUsePrivateEndpoint(parser):
"""Adds --use-private-endpoint flag."""
parser.add_argument(
"--use-private-endpoint",
action="store_true",
help=("Only allow access to the master's private endpoint IP."))
def AddExperimentalFeaturesFlag(parser):
"""Adds --experimental-features flag."""
parser.add_argument(
"--experimental-features",
type=arg_parsers.ArgList(),
metavar="FEATURE",
help=("Enable experimental features. It can only be enabled in ALPHA "
"version."))

View File

@@ -0,0 +1,69 @@
project:
name: project
collection: krmapihosting.projects
attributes:
- &project
parameter_name: projectsId
attribute_name: project
help: The name of the Anthos Config Controller instance project ID.
disable_auto_completers: false
location:
name: location
collection: krmapihosting.projects.locations
attributes:
- &location
parameter_name: locationsId
attribute_name: location
help: The name of the Anthos Config Controller instance location. Currently, only `us-central1`, `us-east1`, `us-east4`, `us-east5`, `us-west2`, `northamerica-northeast1`, `northamerica-northeast2`, `europe-north1`, `europe-west1`, `europe-west3`, `europe-west6`, `australia-southeast1`, `australia-southeast2`, `asia-northeast1`, `asia-northeast2` and `asia-southeast1` are supported.
disable_auto_completers: false
instance:
name: instance
collection: krmapihosting.projects.locations.krmApiHosts
attributes:
- *project
- *location
- &instance
parameter_name: krmApiHostsId
attribute_name: name
help: The name of the Anthos Config Controller instance.
disable_auto_completers: false
location-list:
name: location
collection: krmapihosting.projects.locations
attributes:
- &location-list
parameter_name: locationsId
attribute_name: location
fallthroughs:
- hook: googlecloudsdk.command_lib.anthos.config.controller.utils:SetLocation
hint: use global location
help: The name of the Anthos Config Controller instance location. Currently, only `us-central1`, `us-east1`, `us-east4`, `us-east5`, `us-west2`, `northamerica-northeast1`, `northamerica-northeast2`, `europe-north1`, `europe-west1`, `europe-west3`, `europe-west6`, `australia-southeast1`, `australia-southeast2`, `asia-northeast1`, `asia-northeast2` and `asia-southeast1` are supported.
disable_auto_completers: false
instance-list:
name: instance-list
collection: krmapihosting.projects.locations
attributes:
- *project
- *location-list
operation:
name: operation
collection: krmapihosting.projects.locations.operations
attributes:
- *location
- &operation:
parameter_name: operationsId
attribute_name: operation
help: The name of the Anthos Config Controller operation.
disable_auto_completers: false
operation-list:
name: operation-list
collection: krmapihosting.projects.locations
attributes:
- *project
- *location-list

View File

@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utils for Config Controller commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.container import api_adapter as container_api_adapter
from googlecloudsdk.api_lib.krmapihosting import util
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope.concepts import concepts
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.core import log
NOT_RUNNING_MSG = ('Config Controller {0} is not running. '
'The kubernetes API may not be available.')
def SetLocation():
"""Sets default location to '-' for list command."""
return '-'
def InstanceAttributeConfig():
return concepts.ResourceParameterAttributeConfig(
name='name',
help_text='The name of the Config Controller instance.')
def LocationAttributeConfig():
return concepts.ResourceParameterAttributeConfig(
name='location',
help_text=(
'The name of the Config Controller instance location. Currently, only'
" ``us-central1'', ``us-east1'', ``us-east4'', ``us-east5'',"
" ``us-west2'', ``northamerica-northeast1'',"
" ``northamerica-northeast2'', ``europe-north1'', ``europe-west1'',"
" ``europe-west3'', ``europe-west6'', ``australia-southeast1'',"
" ``australia-southeast2'', ``asia-northeast1'', ``asia-northeast2''"
" and ``asia-southeast1'' are supported."
),
)
def GetInstanceResourceSpec(api_version):
return concepts.ResourceSpec(
'krmapihosting.projects.locations.krmApiHosts',
resource_name='instance',
api_version=api_version,
krmApiHostsId=InstanceAttributeConfig(),
locationsId=LocationAttributeConfig(),
projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG,
disable_auto_completers=False)
def AddInstanceResourceArg(parser, api_version):
concept_parsers.ConceptParser.ForResource(
'name',
GetInstanceResourceSpec(api_version),
'The identifier for a Config Controller instance.',
required=True).AddToParser(parser)
def GetGKECluster(name, location):
"""Fetches the information about the GKE cluster backing the Config Controller."""
cluster_id = 'krmapihost-' + name
location_id = location
project = None
gke_api = container_api_adapter.NewAPIAdapter('v1')
log.status.Print('Fetching cluster endpoint and auth data.')
cluster_ref = gke_api.ParseCluster(cluster_id, location_id, project)
cluster = gke_api.GetCluster(cluster_ref)
if not gke_api.IsRunning(cluster):
log.warning(NOT_RUNNING_MSG.format(cluster_ref.clusterId))
return cluster, cluster_ref
def AsyncLog(operation):
"""Print log messages for async commands."""
log.status.Print(
"""
Check operation [{}] for status.
To describe the operation, run:
$ gcloud anthos config operations describe {}"""
.format(operation.name, operation.name))
return operation
def PatchRequest(args):
"""Construct a patch request based on the args."""
instance = args.CONCEPTS.name.Parse()
messages = apis.GetMessagesModule('krmapihosting',
instance.GetCollectionInfo().api_version)
# Get the current instance to determine whether full management config is
# used.
current = util.GetKrmApiHost(instance.RelativeName())
# Construct the patch instance and the update mask.
update_masks = []
management_config = messages.ManagementConfig()
bundles_config = messages.BundlesConfig(
configControllerConfig=messages.ConfigControllerConfig())
if args.experimental_features:
update_masks.append(
'bundles_config.config_controller_config.experimental_features')
bundles_config.configControllerConfig.experimentalFeatures = args.experimental_features # pylint: disable=line-too-long
if current.managementConfig.fullManagementConfig:
full_management_config = messages.FullManagementConfig()
if args.man_block:
full_management_config.manBlock = args.man_block
update_masks.append('management_config.full_management_config.man_block')
management_config.fullManagementConfig = full_management_config
else:
standard_management_config = messages.StandardManagementConfig()
if args.man_block:
standard_management_config.manBlock = args.man_block
update_masks.append(
'management_config.standard_management_config.man_block')
management_config.standardManagementConfig = standard_management_config
patch = messages.KrmApiHost(managementConfig=management_config,
bundlesConfig=bundles_config)
return messages.KrmapihostingProjectsLocationsKrmApiHostsPatchRequest(
krmApiHost=patch,
name=instance.RelativeName(),
updateMask=','.join(update_masks))

View File

@@ -0,0 +1,455 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command line flags for Anthos commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.core.util import files
_MERGE_STRATEGIES = {
'resource-merge': ('perform a structural comparison of the '
'original/updated Resources, and merge the changes '
'into the local package.'),
'fast-forward': ('fail without updating if the local package was modified'
' since it was fetched.'),
'alpha-git-patch': ("use 'git format-patch' and 'git am' to apply a patch "
'of the changes between the source version and '
'destination version. Requires the local package to '
'have been committed to a local git repo.'),
'force-delete-replace': ('This will wipe all local changes to the package. '
'Deletes the contents of local package from '
'PACKAGE_DIR and replace them with the remote '),
}
def GetFlagOrPositional(name, positional=False, **kwargs):
"""Return argument called name as either flag or positional."""
dest = name.replace('-', '_').upper()
if positional:
flag = dest
kwargs.pop('required', None)
else:
flag = '--{}'.format(name.replace('_', '-').lower())
if not positional:
kwargs['dest'] = dest
return base.Argument(flag, **kwargs)
def GetRepoURIFlag(positional=True,
required=True,
help_override=None,
metavar=None):
"""Get REPO_URI flag."""
help_txt = help_override or """\
Git repository URI containing 1 or more packages as where:
* REPO_URI - URI of a git repository containing 1 or more packages as
subdirectories. In most cases the .git suffix should be specified to
delimit the REPO_URI from the PKG_PATH, but this is not required for
widely recognized repo prefixes. If REPO_URI cannot be parsed then
an error will be printed an asking for '.git' to be specified
as part of the argument. e.g. https://github.com/kubernetes/examples.git
* PKG_PATH (optional) - Path to Git subdirectory containing Anthos package files.
Uses '/' as the path separator (regardless of OS). e.g. staging/cockroachdb.
Defaults to the root directory.
* GIT_REF (optional)- A git tag, branch, ref or commit for the remote version of the
package to fetch. Defaults to the repository default branch e.g. @main
"""
if not metavar:
metavar = 'REPO_URI[.git]/[PKG_PATH][@GIT_REF]'
return GetFlagOrPositional(
name='repo_uri',
positional=positional,
required=required,
help=help_txt,
metavar=metavar)
def GetPackagePathFlag(metavar=None):
return GetFlagOrPositional(
name='package_path',
positional=False,
required=False,
help="""\
Path to remote subdirectory containing Kubernetes Resource configuration
files or directories.
Defaults to the root directory.
Uses '/' as the path separator (regardless of OS).
e.g. staging/cockroachdb
""",
metavar=metavar)
def GetLocalDirFlag(positional=True,
required=True,
help_override=None,
metavar=None):
"""Get Local Package directory flag."""
help_txt = help_override or """\
The local directory to fetch the package to.
e.g. ./my-cockroachdb-copy
* If the directory does NOT exist: create the specified directory
and write the package contents to it
* If the directory DOES exist: create a NEW directory under the
specified one, defaulting the name to the Base of REPO/PKG_PATH
* If the directory DOES exist and already contains a directory with
the same name of the one that would be created: fail
"""
return GetFlagOrPositional(
name='LOCAL_DIR',
positional=positional,
required=required,
type=ExpandLocalDirAndVersion,
help=help_txt,
metavar=metavar)
def GetFilePatternFlag():
return GetFlagOrPositional(
name='pattern',
positional=False,
required=False,
help="""\
Pattern to use for writing files. May contain the following formatting
verbs %n: metadata.name, %s: metadata.namespace, %k: kind
(default "%n_%k.yaml")
""")
def GetStrategyFlag():
return base.Argument(
'--strategy',
required=False,
choices=_MERGE_STRATEGIES,
help='Controls how changes to the local package are handled.')
def GetDryRunFlag(help_override=None):
help_txt = help_override or ('If true and command fails print the '
'underlying command that was executed and '
'its exit status.')
return base.Argument(
'--dry-run', action='store_true', required=False, help=help_txt)
def GetDescriptionFlag():
return base.Argument(
'--description', required=False, help='Description of the Package.')
def GetNameFlag():
return base.Argument('--name', required=False, help='Name of the package.')
def GetTagsFlag():
return base.Argument(
'--tags',
required=False,
type=arg_parsers.ArgDict(),
metavar='TAG=VALUE',
help='Tags for the package.')
def GetInfoUrlFlag():
return base.Argument(
'--info-url',
required=False,
help='Url with more info about the package.')
def ExpandLocalDirAndVersion(directory):
"""Expand HOME relative (~) directory with optional git_ref.
Args:
directory: str, directory path in the format PATH[/][@git_ref].
Returns:
str, expanded full directory path with git_ref (if provided)
"""
path = directory.split('@') if directory else ''
full_dir = files.ExpandHomeDir(path[0])
if len(path) == 2:
full_dir += '@' + path[1]
return full_dir
# Anthos Auth
def GetClusterFlag(positional=False,
required=False,
help_override=None,
metavar=None):
"""Anthos operation cluster name flag."""
help_txt = help_override or ('Cluster to authenticate against. If no cluster '
'is specified, the command will print a list '
'of available options.')
return GetFlagOrPositional(
name='CLUSTER',
positional=positional,
required=required,
help=help_txt,
metavar=metavar)
def GetLoginConfigFlag():
return base.Argument(
'--login-config',
required=False,
help='Specifies the configuration yaml '
'file for login. Can be a file path or a URL.')
def GetLoginConfigCertFlag():
return base.Argument(
'--login-config-cert',
required=False,
type=ExpandLocalDirAndVersion,
help='Specifies the CA certificate file to be added to trusted pool '
'for making HTTPS connections to a `--login-config` URL.')
def GetUserFlag():
return base.Argument(
'--user',
required=False,
help='If configuring multiple user accounts in the same kubecconfig '
'file, you can specify a user to differentiate between them.')
def GetSetPreferredAuthenticationFlag():
return base.Argument(
'--set-preferred-auth',
required=False,
action='store_true',
help='If set, forces update of preferred '
'authentication for given cluster')
def GetServerFlag():
return base.Argument(
'--server',
required=False,
help=(
'Specifies the URL of API server of the cluster to authenticate'
' against.'
),
)
def GetOutputDirFlag(positional=False,
required=False,
help_override=None,
metavar='OUTPUT-DIR',
default=None):
"""Anthos operation local output directory flag."""
help_txt = help_override or ('The output directory of the cluster resources.'
' If empty will export files to ./CLUSTER_NAME')
return GetFlagOrPositional(
name='OUTPUT_DIRECTORY',
positional=positional,
required=required,
type=ExpandLocalDirAndVersion,
help=help_txt,
default=default,
metavar=metavar)
def GetLocationFlag():
"""Anthos location flag."""
return base.Argument(
'--location',
required=False,
help='Specifies the Google Cloud location to use. If not'
'specified will use the current compute/zone property.')
def GetMergeFromFlag():
"""Anthos create-login-config Merge-From flag."""
return base.Argument(
'--merge-from',
required=False,
help='Specifies the file path of an existing login '
'configuration file to merge with.')
def GetConfigOutputFileFlag():
"""Anthos create-login-config output flag."""
return base.Argument(
'--output',
required=False,
type=ExpandLocalDirAndVersion,
help='Destination to write login configuration file. '
'Defaults to "kubectl-anthos-config.yaml".')
# Anthos auth token flags.
def GetTypeFlag():
"""Anthos auth token type flag, specifies the type of token to be created."""
return base.ChoiceArgument(
'--type',
required=True,
choices=['aws', 'oidc'],
help_str='Type of token to be created.')
def GetAwsStsRegionFlag():
"""Anthos auth token aws-sts-region flag, specifies the region for AWS STS endpoint for creating AWS token."""
return base.Argument(
'--aws-sts-region', required=False, help='Region for AWS STS endpoint.')
def GetTokenClusterFlag():
"""Anthos auth token cluster flag, specifies cluster name for creating AWS token."""
return base.Argument(
'--cluster',
required=False,
help='Name of the cluster for which token is created.')
def GetIdTokenFlag():
"""Anthos auth token id-token flag, specifies the ID Token received from identity provider after authorization flow."""
return base.Argument(
'--id-token',
required=False,
help='ID Token received from identity provider after authorization flow.')
def GetAccessTokenFlag():
"""Anthos auth token access-token flag, specifies the Access Token received from identity provider after authorization flow."""
return base.Argument(
'--access-token',
required=False,
help=(
'Access Token received from identity provider after authorization'
' flow.'
),
)
def GetAccessTokenExpiryFlag():
"""Anthos auth token access-token-expiry flag, specifies the Expiration time of access token received from identity provider after authorization flow."""
return base.Argument(
'--access-token-expiry',
required=False,
help=(
'Expiration time of access token received from identity provider'
' after authorization flow. The expected format is the number of'
' seconds elapsed since January 1, 1970 UTC.'
),
)
def GetRefreshTokenFlag():
"""Anthos auth token refresh-token flag, specifies the Refresh Token received from identity provider after authorization flow."""
return base.Argument(
'--refresh-token',
required=False,
help=(
'Refresh Token received from identity provider after authorization'
' flow.'
),
)
def GetClientIdFlag():
"""Anthos auth token client-id flag, specifies the ClientID is the id for OIDC client application."""
return base.Argument(
'--client-id',
required=False,
help='ClientID is the id for OIDC client application.')
def GetClientSecretFlag():
"""Anthos auth token client-secret flag, specifies the Client Secret is the shared secret between OIDC client application and OIDC provider."""
return base.Argument(
'--client-secret',
required=False,
help=(
'Client Secret is the shared secret between OIDC client application'
' and OIDC provider.'
),
)
def GetIdpCertificateAuthorityDataFlag():
"""Anthos auth token idp-certificate-authority-data flag, specifies the PEM-encoded certificate authority certificate for OIDC provider."""
return base.Argument(
'--idp-certificate-authority-data',
required=False,
help='PEM-encoded certificate authority certificate for OIDC provider.')
def GetIdpIssuerUrlFlag():
"""Anthos auth token idp-issuer-url flag, specifies the URI for the OIDC provider."""
return base.Argument(
'--idp-issuer-url',
required=False,
help=(
'URI for the OIDC provider. This URI should point to the level below'
' .well-known/openid-configuration.'
),
)
def GetKubeconfigPathFlag():
"""Anthos auth token kubeconfig-path flag, specifies the Path to the kubeconfig path that would be updated with ID and access token on expiry."""
return base.Argument(
'--kubeconfig-path',
required=False,
help=(
'Path to the kubeconfig path that would be updated with ID and access'
' token on expiry.'
),
)
def GetTokenUserFlag():
"""Anthos auth token user flag, specifies the User used in kubeconfig."""
return base.Argument(
'--user', required=False, help='User used in kubeconfig.'
)
def GetNoBrowserFlag():
"""Used to start authentication on a device without a browser in order to perform login on a second device with browser."""
return base.Argument(
'--no-browser',
action='store_true',
required=False,
help=(
'Option to indicate login completion on a second device with browser.'
'Used with `server` option.'
),
)
def GetRemoteBootstrapFlag():
"""Used to complete authentication that was started on a remote device without a browser, on the current device with a browser."""
return base.Argument(
'--remote-bootstrap',
required=False,
help=(
'Option to complete login that was started using `no-browser` option'
'on a remote device that does not have a browser.'
),
)