Skip to content

Commit

Permalink
Add support for awscli V2 (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
ddriddle authored Sep 30, 2024
1 parent b5cbd75 commit 0c152c7
Show file tree
Hide file tree
Showing 14 changed files with 313 additions and 41 deletions.
70 changes: 70 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,76 @@ jobs:
- name: Integration Tests
run: make integration-tests

awscliv2_test:
runs-on: ${{ matrix.os }}
needs: build

strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.8","3.9","3.10","3.11","3.12"]

steps:
- uses: actions/checkout@v3
with:
submodules: recursive

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build

# setuptools_scm sometimes will regenerate _version.py which causes
# Makefile to rebuild things. To avoid unnecessary rebuilds we set the
# following environment variable.
- name: Set setuptools_scm environment variable
shell: bash
run: |
PYTHONPATH=src python -c 'from awscli_login._version import __version__; print(f"SETUPTOOLS_SCM_PRETEND_VERSION_FOR_AWSCLI_LOGIN={__version__}")' > "$RUNNER_TEMP/_awscli_version"
cat "$RUNNER_TEMP/_awscli_version" >> "$GITHUB_ENV"
- name: Touch build artifacts
run: |
touch docs/readme.rst
touch src/awscli_login/_version.py
touch dist/* # Must be newer than docs/readme.rst
touch .twinecheck # Must be newer than build
- name: Get pip cache dir
id: pip-cache
run: echo "::set-output name=dir::$(python -m pip cache dir)"

- name: Save pip cache
uses: actions/cache@v3
with:
path: ${{ steps.pip-cache.outputs.dir }}
key: ${{ runner.os }}-pip-${{ secrets.CACHE_VERSION }}

- name: Get date
id: date
run: echo "::set-output name=week::$(date '+%U')"

- name: Upgrade bash (macOS)
if: matrix.os == 'macOS-latest'
run: brew install bash

- name: Install awscli-login package
shell: bash
run: |
make venv.v2
- name: Integration Tests
shell: bash
run: |
make integration-tests-v2
env:
PYTHONWARNINGS: "ignore" # Disable Python warnings

publish:
needs: [unit_tests, integration_tests]
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ cache
dist
htmlcov
venv
venv.v2
docs/src/

.*.docker
Expand Down
28 changes: 24 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ deps-test:

# Python packages needed to run integration_tests tests
deps-integration-test:
$(PIP) vcrpy
$(PIP) awscli vcrpy

# Python packages needed to publish a production or test release
deps-publish:
Expand Down Expand Up @@ -121,17 +121,37 @@ install-build: .install-build
# https://github.com/actions/runner-images/issues/2216
ifeq ($(RUNNER_OS),Windows)
idp_integration_deps=
integration-tests: export AWSCLI_LOGIN_SKIP_DOCKER_TESTS=1
integration-tests: export AWSCLI_LOGIN_WINDOWS_TEST=1
integration-tests integration-tests-v2: export AWSCLI_LOGIN_SKIP_DOCKER_TESTS=1
integration-tests integration-tests-v2: export AWSCLI_LOGIN_WINDOWS_TEST=1
# As of 9/13/2024, macos-latest does not support docker. This may
# change by the end of the year. See issue #208.
else ifeq ($(RUNNER_OS),macOS)
idp_integration_deps=
integration-tests: export AWSCLI_LOGIN_SKIP_DOCKER_TESTS=1
integration-tests integration-tests-v2: export AWSCLI_LOGIN_SKIP_DOCKER_TESTS=1
else
idp_integration_deps=.idp.docker
endif
integration-tests: $(idp_integration_deps)
aws --version 2>/dev/null | grep '^aws-cli/1.' || (echo "Not running awscli V1!"; exit 1)
make -C src/integration_tests/

ifeq ($(RUNNER_OS),Windows)
VBIN := Scripts
VPKG := lib/site-packages*
else
VBIN := bin
VPKG := lib/python*/site-packages
endif
venv.v2: $(RELEASE)
rm -rf $@
python -m venv $@
$@/$(VBIN)/python -m pip install vcrpy $<

integration-tests-v2: export AWSCLI_TEST_V2:=1
integration-tests-v2: export AWSCLI_TEST_PLUGIN_PATH=$(wildcard $(PWD)/venv.v2/$(VPKG))
integration-tests-v2: export PATH:=$(PWD)/venv.v2/$(VBIN):$(PATH)
integration-tests-v2: $(idp_integration_deps) venv.v2
aws --version 2>/dev/null | grep '^aws-cli/2.' || (echo "Not running awscli V2!"; exit 1)
make -C src/integration_tests/

test_fast: export AWSCLI_LOGIN_FAST_TEST_ONLY=1
Expand Down
133 changes: 133 additions & 0 deletions docs/readme/body.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,136 @@
Configuration
=============

After awscli-login has been installed, run the following command
to enable the plugin::

$ aws configure set plugins.login awscli_login

If you receive a bad interpreter error or other error please see
the `Known Issues`_ section below. If it succeeds the AWS CLI
configuration file ``~/.aws/config`` should be updated with the
following section::

[plugins]
login = awscli_login

AWS CLI V2 Configuration
------------------------

If you are configuring `AWS CLI`_ V2, The path to the site
packages directory where ``awscli-login`` resides must be supplied
as well. This can be looked up using the following command::

$ pip show awscli-login
Name: awscli-login
Version: 1.0
Summary: Plugin for the AWS CLI that retrieves and rotates credentials using SAML ECP and STS.
Home-page:
Author:
Author-email: "David D. Riddle" <[email protected]>
License: MIT License
Location: /usr/lib/python3.12/site-packages
Requires: botocore, keyring, lxml, requests
Required-by:

The ``Location`` field has the required path information, and must be passed to ``aws configure``::

$ aws configure set plugins.cli_legacy_plugin_path <<PASTE ``Location`` HERE>>

Note: If your output matched the example above, you would paste in
``/usr/lib/python3.12/site-packages``.

On POSIX systems such as macOS and Linux the preceding can be set
more easily using the following one-liner::

$ aws configure set plugins.cli_legacy_plugin_path $(pip show awscli-login | sed -nr 's/^Location: (.*)/\1/p')

If it succeeds the AWS CLI configuration file ``~/.aws/config``
should be updated with the following section::

[plugins]
login = awscli_login
cli_legacy_plugin_path = /usr/lib/python3.12/site-packages

Note that ``cli_legacy_plugin_path`` should point to the same value
as given in the ``Location`` field given by ``pip show awscli-login``
above.

System Configuration
--------------------

The command or script ``aws-login`` must be on your ``$PATH``. If it
is not on your path you can use the following command to determine
its location::

$ pip show awscli-login --files

On Windows look for the file ``aws-login.exe``, on POSIX systems
look for ``bin/aws-login``. Then add the path to the environment variable
``PATH``. If the path is a relative path, note that it is relative
to the ``Location`` field. You may set your PATH using the following
examples:

* Linux/Mac::

$ export PATH="$PATH:/usr/local/bin"

* Windows::

$ $env:PATH+=';C:\Users\USERNAME\AppData\Roaming\Python\Python312\Scripts'

Verify that ``aws-login`` appears on your PATH:

* Linux/Mac::

$ which aws-login
/usr/local/bin/aws-login

* Windows::

$ Get-Command aws-login

CommandType Name Version Source
----------- ---- ------- ------
Application aws-login.exe 0.0.0.0 C:\Users\USERNAME\AppData\Roaming\Python\Python312\Scripts\aws-login.exe

Upgrade
=======

If you are upgrading from ``awscli-login`` version ``0.2b1`` or
earlier, please follow the `Installation`_ instructions above, then
proceed to the `Getting Started`_ section below to reconfigure your
profiles which is required.

Reconfiguration is required because in previous versions of
``awscli-login`` credentials were directly stored in AWS CLI's
credentials file ``~/.aws/credentials``. This is no longer the case.
Now each profile contains a reference to the ``aws-login`` script.

Previously ``~/.aws/credentials`` would have looked looked like this
after a log out::

[default]
aws_access_key_id = abc
aws_secret_access_key = def
aws_session_token = ghi
aws_security_token = ghi

After a reconfiguration, the example ``~/.aws/credentials`` file
above should look like this::

[default]
credential_process = aws-login --profile default

If you attempt to log into a profile that has not been reconfigured
you will receive the following error message::

$ aws login
Credential process is not set for current profile "foo".
Reconfigure using:

aws login configure

Getting Started
===============

Expand Down
19 changes: 12 additions & 7 deletions docs/readme/readme.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@
Installation
============

The simplest way to install the ``awscli-login`` plugin is to use pip.
If you are upgrading from ``awscli-login`` version ``0.2b1`` or earlier,
please logout then uninstall ``awscli-login`` and ``awscli`` to
ensure a smooth upgrade::

.. include:: readme/install.{{ ENV }}.rst
$ aws logout
$ pip uninstall awscli-login awscli

Before installing ``awscli-login`` you need to install `AWS CLI`_ V1 or V2.
Instructions for installing V2 can be found on Amazon's website `here`_:

After ``awscli-login`` has been installed, run the following command
to enable the plugin::
.. _here: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
.. _AWS CLI: https://aws.amazon.com/cli/

$ aws configure set plugins.login awscli_login
AWS CLI V1 can be installed using ``pip install awscli``.

If you receive a bad interpreter error or other error please see
the `Known Issues`_ section below.
.. include:: readme/install.{{ ENV }}.rst

.. include:: readme/body.rst
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ build-backend = "setuptools.build_meta"
name = "awscli-login"
dynamic = ["version"]
dependencies = [
"awscli",
"botocore",
"keyring",
"lxml",
Expand Down Expand Up @@ -42,6 +41,7 @@ Changelog = "https://github.com/techservicesillinois/awscli-login/blob/master/CH

[project.optional-dependencies]
test = [
"awscli",
"tblib",
"wurlitzer",
"vcrpy",
Expand Down
38 changes: 30 additions & 8 deletions src/awscli_login/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Rudimentary documentation for the aws-cli plugin API can be found
# here: https://github.com/aws/aws-cli/issues/1261
import copy
import json
import logging
import subprocess

from argparse import Namespace
from tempfile import NamedTemporaryFile, TemporaryDirectory

try:
from awscli.customizations.commands import BasicCommand
Expand All @@ -16,8 +20,7 @@ class BasicCommand(): # type: ignore
class Session(): # type: ignore
pass

from .__main__ import main, logout
from .configure import configure
from .configure import configure, exit_if_credential_process_not_set

logger = logging.getLogger(__package__)

Expand All @@ -43,7 +46,25 @@ def inject_subcommands(command_table, session: Session, **kwargs):
command_table['configure'] = Configure(session)


class Login(BasicCommand):
class ExternalCommand(BasicCommand):
"""
Used to run subcommands in the external aws-login script.
"""

def _run_main(self, args: Namespace, parsed_globals):
with TemporaryDirectory() as tmpdir:
tmp = NamedTemporaryFile(dir=tmpdir, delete=False)
tmp.write(bytes(json.dumps(vars(args)), 'utf-8'))
tmp.close()

cmd = ["aws-login", f"--{self.NAME}", tmp.name]
if self._session.profile:
cmd += ["--profile", self._session.profile]

return subprocess.run(cmd).returncode


class Login(ExternalCommand):
NAME = 'login'
DESCRIPTION = ('is a plugin that manages retrieving and rotating'
' Amazon STS keys using the Shibboleth IdP and Duo'
Expand Down Expand Up @@ -144,10 +165,14 @@ class Login(BasicCommand):
UPDATE = False

def _run_main(self, args: Namespace, parsed_globals):
return main(args, self._session)
r = exit_if_credential_process_not_set(copy.copy(args), self._session)
if r:
return r
else:
return super()._run_main(args, self._session)


class Logout(BasicCommand):
class Logout(ExternalCommand):
NAME = 'logout'
DESCRIPTION = ('''
Log out of selected profile by clearing the profile's credentials
Expand All @@ -167,9 +192,6 @@ class Logout(BasicCommand):

UPDATE = False

def _run_main(self, args: Namespace, parsed_globals):
return logout(args, self._session)


class Configure(BasicCommand):
NAME = 'login'
Expand Down
Loading

0 comments on commit 0c152c7

Please sign in to comment.