Skip to content

Commit

Permalink
Add Swift support for CodeArtifact login command
Browse files Browse the repository at this point in the history
This is the initial commit that introduces Swift support for CodeArtifact login commmand.
  • Loading branch information
dafangc-amazon committed Sep 14, 2023
1 parent 13b61bb commit 95733fd
Show file tree
Hide file tree
Showing 5 changed files with 678 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "enhancement",
"category": "``codeartifact login``",
"description": "Add Swift support for CodeArtifact login command"
}
2 changes: 2 additions & 0 deletions awscli/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@

is_windows = sys.platform == 'win32'

is_macos = sys.platform == 'darwin'


if is_windows:
default_pager = 'more'
Expand Down
141 changes: 140 additions & 1 deletion awscli/customizations/codeartifact/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
from dateutil.relativedelta import relativedelta
from botocore.utils import parse_timestamp

from awscli.compat import is_windows, urlparse, RawConfigParser, StringIO, get_stderr_encoding
from awscli.compat import (
is_windows, urlparse, RawConfigParser, StringIO,
get_stderr_encoding, is_macos
)
from awscli.customizations import utils as cli_utils
from awscli.customizations.commands import BasicCommand
from awscli.customizations.utils import uni_print
Expand Down Expand Up @@ -115,6 +118,137 @@ def get_commands(cls, endpoint, auth_token, **kwargs):
raise NotImplementedError('get_commands()')


class SwiftLogin(BaseLogin):

DEFAULT_NETRC_FMT = \
u'machine {hostname} login token password {auth_token}'

NETRC_REGEX_FMT = \
r'(?P<entry_start>\bmachine\s+{escaped_hostname}\s+login\s+\S+\s+password\s+)' \
r'(?P<token>\S+)'

def login(self, dry_run=False):
scope = self.get_scope(
self.namespace
)
commands = self.get_commands(
self.repository_endpoint, self.auth_token, scope=scope
)

if not is_macos:
hostname = urlparse.urlparse(self.repository_endpoint).hostname
new_entry = self.DEFAULT_NETRC_FMT.format(
hostname=hostname,
auth_token=self.auth_token
)
if dry_run:
self._display_new_netrc_entry(new_entry, self.get_netrc_path())
else:
self._update_netrc_entry(hostname, new_entry, self.get_netrc_path())

self._run_commands('swift', commands, dry_run)

def _display_new_netrc_entry(self, new_entry, netrc_path):
sys.stdout.write('Dryrun mode is enabled, not writing to netrc.')
sys.stdout.write(os.linesep)
sys.stdout.write(
f'The following line would have been written to {netrc_path}:'
)
sys.stdout.write(os.linesep)
sys.stdout.write(os.linesep)
sys.stdout.write(new_entry)
sys.stdout.write(os.linesep)
sys.stdout.write(os.linesep)
sys.stdout.write('And would have run the following commands:')
sys.stdout.write(os.linesep)
sys.stdout.write(os.linesep)

def _update_netrc_entry(self, hostname, new_entry, netrc_path):
pattern = re.compile(
self.NETRC_REGEX_FMT.format(escaped_hostname=re.escape(hostname)),
re.M
)
if not os.path.isfile(netrc_path):
self._create_netrc_file(netrc_path, new_entry)
else:
with open(netrc_path, 'r') as f:
contents = f.read()
escaped_auth_token = self.auth_token.replace('\\', r'\\')
new_contents = re.sub(
pattern,
rf"\g<entry_start>{escaped_auth_token}",
contents
)

if new_contents == contents:
new_contents = self._append_netrc_entry(new_contents, new_entry)

with open(netrc_path, 'w') as f:
f.write(new_contents)

def _create_netrc_file(self, netrc_path, new_entry):
dirname = os.path.split(netrc_path)[0]
if not os.path.isdir(dirname):
os.makedirs(dirname)
with os.fdopen(os.open(netrc_path,
os.O_WRONLY | os.O_CREAT, 0o600), 'w') as f:
f.write(new_entry + '\n')

def _append_netrc_entry(self, contents, new_entry):
if contents.endswith('\n'):
return contents + new_entry + '\n'
else:
return contents + '\n' + new_entry + '\n'

@classmethod
def get_netrc_path(cls):
return os.path.join(os.path.expanduser("~"), ".netrc")

@classmethod
def get_scope(cls, namespace):
# Regex for valid scope name
valid_scope_name = re.compile(
r'\A[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}\Z'
)

if namespace is None:
return namespace

if not valid_scope_name.match(namespace):
raise ValueError(
'Invalid scope name, scope must contain URL-safe '
'characters, no leading dots or underscores and no '
'more than 39 characters'
)

return namespace

@classmethod
def get_commands(cls, endpoint, auth_token, **kwargs):
commands = []
scope = kwargs.get('scope')

# Set up the codeartifact repository as the swift registry.
set_registry_command = [
'swift', 'package-registry', 'set', endpoint
]
if scope is not None:
set_registry_command.extend(['--scope', scope])
commands.append(set_registry_command)

# Authenticate against the repository.
# We will write token to .netrc for Linux and Windows
# MacOS will store the token from command line option to Keychain
login_registry_command = [
'swift', 'package-registry', 'login', f'{endpoint}login'
]
if is_macos:
login_registry_command.extend(['--token', auth_token])
commands.append(login_registry_command)

return commands


class NuGetBaseLogin(BaseLogin):
_NUGET_INDEX_URL_FMT = '{endpoint}v3/index.json'

Expand Down Expand Up @@ -511,6 +645,11 @@ class CodeArtifactLogin(BasicCommand):
'''Log in to the idiomatic tool for the requested package format.'''

TOOL_MAP = {
'swift': {
'package_format': 'swift',
'login_cls': SwiftLogin,
'namespace_support': True,
},
'nuget': {
'package_format': 'nuget',
'login_cls': NuGetLogin,
Expand Down
Loading

0 comments on commit 95733fd

Please sign in to comment.