From 36d639950fbae6ba90ed885591ecc21de2eb7ecb Mon Sep 17 00:00:00 2001 From: Nolan Nichols Date: Tue, 15 Jul 2025 13:57:28 -0700 Subject: [PATCH 1/4] add: env var support --- README.md | 14 ++++++++++++++ keyrings/codeartifact.py | 7 +++++++ tests/test_backend.py | 22 +++++++++++++++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15ac016..c05805e 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,20 @@ hosted within CodeArtifact. It will use any appropriate AWS credentials provided --index-url https://${DOMAIN}-${ACCOUNT}.d.codeartifact.${REGION}.amazonaws.com/pypi/${REPOSITORY}/simple/ ``` +### Environment Variable + +You can bypass AWS API calls by setting the `CODEARTIFACT_AUTH_TOKEN` environment variable: + +```bash +# Set the environment variable with a valid token +export CODEARTIFACT_AUTH_TOKEN=your-auth-token-here + +# Then use pip/twine as usual, and the token will be used automatically +pip install package-name --index-url https://${DOMAIN}-${ACCOUNT}.d.codeartifact.${REGION}.amazonaws.com/pypi/${REPOSITORY}/simple/ +``` + +This is especially useful in CI/CD environments or when you want to avoid making AWS API calls. + Config ------ This backend provides a number of configuration options to modify the behaviour of the AWS client. diff --git a/keyrings/codeartifact.py b/keyrings/codeartifact.py index 907333d..b1813ee 100644 --- a/keyrings/codeartifact.py +++ b/keyrings/codeartifact.py @@ -1,5 +1,6 @@ # codeartifact.py -- keyring backend +import os import re import logging @@ -177,6 +178,12 @@ def get_password(self, service, username): name=repository_name, ) + # Check for token in environment variable + token_from_env = os.getenv("CODEARTIFACT_AUTH_TOKEN") + if token_from_env: + logging.info("Using token from environment variable: CODEARTIFACT_AUTH_TOKEN") + return token_from_env + # Options for the client callback. options = { # Pass in the region name. diff --git a/tests/test_backend.py b/tests/test_backend.py index 549ac1b..d52eebb 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -170,4 +170,24 @@ def make_client(options): config = CodeArtifactKeyringConfig(config_file=config_file.name) backend = CodeArtifactBackend(config=config, make_client=make_client) url = codeartifact_pypi_url("domain", "000000000000", "region", "name") - credentials = backend.get_credential(url, None) + backend.get_credential(url, None) + + +def test_get_credential_from_env(monkeypatch): + """ + Tests that the backend can retrieve a token from an environment variable. + """ + monkeypatch.setenv("CODEARTIFACT_AUTH_TOKEN", "ENV_TOKEN") + + def make_client(options): + # This should not be called when token is in env + pytest.fail("make_client should not be called when token is in env") + + config = CodeArtifactKeyringConfig(config_file=StringIO()) + backend = CodeArtifactBackend(config=config, make_client=make_client) + + url = codeartifact_pypi_url("domain", "000000000000", "region", "name") + credentials = backend.get_credential(url, None) + + assert credentials.username == "aws" + assert credentials.password == "ENV_TOKEN" From a74bfaee5017dac8c563daeea6b8607ba27dd336 Mon Sep 17 00:00:00 2001 From: Nolan Nichols Date: Wed, 20 Aug 2025 18:04:18 -0700 Subject: [PATCH 2/4] rf: move to top of get_password to bypass logic --- keyrings/codeartifact.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/keyrings/codeartifact.py b/keyrings/codeartifact.py index b1813ee..39cbbea 100644 --- a/keyrings/codeartifact.py +++ b/keyrings/codeartifact.py @@ -145,6 +145,13 @@ def get_credential(self, service, username): return credentials.SimpleCredential("aws", authorization_token) def get_password(self, service, username): + + # Check for token in environment variable + token_from_env = os.getenv("CODEARTIFACT_AUTH_TOKEN") + if token_from_env: + logging.info("Using token from environment variable: CODEARTIFACT_AUTH_TOKEN") + return token_from_env + url = urlparse(service) # Do a quick check to see if this service URL applies to us. @@ -178,12 +185,6 @@ def get_password(self, service, username): name=repository_name, ) - # Check for token in environment variable - token_from_env = os.getenv("CODEARTIFACT_AUTH_TOKEN") - if token_from_env: - logging.info("Using token from environment variable: CODEARTIFACT_AUTH_TOKEN") - return token_from_env - # Options for the client callback. options = { # Pass in the region name. From de2f93bda6c72bbae35876c21b5ce1c3b2be6f0c Mon Sep 17 00:00:00 2001 From: Nolan Nichols Date: Wed, 20 Aug 2025 18:29:27 -0700 Subject: [PATCH 3/4] rf: move env var into a constant with config setting --- keyrings/codeartifact.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/keyrings/codeartifact.py b/keyrings/codeartifact.py index 39cbbea..4b692cf 100644 --- a/keyrings/codeartifact.py +++ b/keyrings/codeartifact.py @@ -146,10 +146,13 @@ def get_credential(self, service, username): def get_password(self, service, username): + # Get the environment variable name from config, default to CODEARTIFACT_AUTH_TOKEN + env_variable_name = self.config.get("env_variable", "CODEARTIFACT_AUTH_TOKEN") + # Check for token in environment variable - token_from_env = os.getenv("CODEARTIFACT_AUTH_TOKEN") + token_from_env = os.getenv(env_variable_name) if token_from_env: - logging.info("Using token from environment variable: CODEARTIFACT_AUTH_TOKEN") + logging.info(f"Using token from environment variable: {env_variable_name}") return token_from_env url = urlparse(service) From 834702dae352b5fc9ea4d71ca8907e9aed4ab162 Mon Sep 17 00:00:00 2001 From: Nolan Nichols Date: Wed, 20 Aug 2025 18:35:04 -0700 Subject: [PATCH 4/4] rf: self.config is not a dict --- keyrings/codeartifact.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/keyrings/codeartifact.py b/keyrings/codeartifact.py index 4b692cf..54ff795 100644 --- a/keyrings/codeartifact.py +++ b/keyrings/codeartifact.py @@ -146,8 +146,13 @@ def get_credential(self, service, username): def get_password(self, service, username): - # Get the environment variable name from config, default to CODEARTIFACT_AUTH_TOKEN - env_variable_name = self.config.get("env_variable", "CODEARTIFACT_AUTH_TOKEN") + # Get the environment variable name from config defaults (if provided), + # otherwise fall back to the standard CODEARTIFACT_AUTH_TOKEN. + # Note: self.config is a CodeArtifactKeyringConfig, not a dict. + env_variable_name = ( + getattr(self.config, "defaults", {}) + .get("env_variable", "CODEARTIFACT_AUTH_TOKEN") + ) # Check for token in environment variable token_from_env = os.getenv(env_variable_name)