keyrings.codeartifact 2.0.0__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. keyrings_codeartifact-2.1.0/.github/dependabot.yml +11 -0
  2. keyrings_codeartifact-2.1.0/.github/workflows/release.yml +93 -0
  3. {keyrings_codeartifact-2.0.0/keyrings.codeartifact.egg-info → keyrings_codeartifact-2.1.0}/PKG-INFO +3 -3
  4. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/README.md +0 -1
  5. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/keyrings/codeartifact.py +64 -16
  6. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0/keyrings.codeartifact.egg-info}/PKG-INFO +3 -3
  7. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/keyrings.codeartifact.egg-info/SOURCES.txt +1 -0
  8. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/tests/config/multiple_sections_with_default.cfg +1 -1
  9. keyrings_codeartifact-2.1.0/tests/config/single_section.cfg +5 -0
  10. keyrings_codeartifact-2.1.0/tests/test_backend.py +225 -0
  11. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/tests/test_config.py +3 -3
  12. keyrings_codeartifact-2.0.0/.github/workflows/release.yml +0 -47
  13. keyrings_codeartifact-2.0.0/tests/config/single_section.cfg +0 -5
  14. keyrings_codeartifact-2.0.0/tests/test_backend.py +0 -96
  15. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/.gitignore +0 -0
  16. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/LICENSE +0 -0
  17. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/keyrings/__init__.py +0 -0
  18. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/keyrings.codeartifact.egg-info/dependency_links.txt +0 -0
  19. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/keyrings.codeartifact.egg-info/entry_points.txt +0 -0
  20. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/keyrings.codeartifact.egg-info/requires.txt +0 -0
  21. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/keyrings.codeartifact.egg-info/top_level.txt +0 -0
  22. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/pyproject.toml +0 -0
  23. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/requirements-dev.txt +0 -0
  24. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/requirements-test.txt +0 -0
  25. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/requirements.txt +0 -0
  26. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/setup.cfg +0 -0
  27. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/tests/__init__.py +0 -0
  28. {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.0}/tests/config/multiple_sections_no_default.cfg +0 -0
@@ -0,0 +1,11 @@
1
+ ---
2
+ version: 2
3
+ updates:
4
+ - package-ecosystem: "pip"
5
+ directory: "/"
6
+ schedule:
7
+ interval: "weekly"
8
+ - package-ecosystem: "github-actions"
9
+ directory: "/"
10
+ schedule:
11
+ interval: "weekly"
@@ -0,0 +1,93 @@
1
+ # .github/workflows/release.yml
2
+
3
+ name: 📦 Release 'keyrings.codeartifact' package.
4
+
5
+ on: push
6
+
7
+ jobs:
8
+ build:
9
+ name: 📦 Build package.
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: 🛒 Checkout repository source code.
14
+ uses: actions/checkout@v4
15
+ with:
16
+ persist-credentials: false
17
+ fetch-depth: 0
18
+
19
+ - name: ✨ Setup Python 3.x environment.
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.x"
23
+ cache: 'pip'
24
+
25
+ - name: 🔬 Execute all unit tests.
26
+ run: |
27
+ python3 -m pip install .[testing]
28
+ python3 -m pytest
29
+
30
+ - name: 🛠️ Build package and source distribution.
31
+ run: |
32
+ python3 -m pip install --user build
33
+ python3 -m build
34
+
35
+ - name: 💾 Store the built package distributions.
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: python-package-distributions
39
+ path: dist/
40
+
41
+ publish-to-testpypi:
42
+ name: 🚀 Publish distributions to test PyPI index.
43
+ runs-on: ubuntu-latest
44
+
45
+ needs:
46
+ - build
47
+
48
+ environment:
49
+ name: testpypi
50
+ url: https://test.pypi.org/p/keyrings.codeartifact
51
+
52
+ permissions:
53
+ # IMPORTANT: mandatory for trusted publishing
54
+ id-token: write
55
+
56
+ steps:
57
+ - name: 📥 Download the built package distributions.
58
+ uses: actions/download-artifact@v4
59
+ with:
60
+ name: python-package-distributions
61
+ path: dist/
62
+
63
+ - name: 🚀 Publish distributions to TestPyPI.
64
+ uses: pypa/gh-action-pypi-publish@release/v1
65
+ with:
66
+ repository-url: https://test.pypi.org/legacy/
67
+ skip-existing: true
68
+
69
+ publish-to-pypi:
70
+ name: 🚀 Publish distributions to main PyPI index.
71
+ if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
72
+ runs-on: ubuntu-latest
73
+
74
+ needs:
75
+ - build
76
+
77
+ environment:
78
+ name: pypi
79
+ url: https://pypi.org/p/keyrings.codeartifact
80
+
81
+ permissions:
82
+ # IMPORTANT: mandatory for trusted publishing
83
+ id-token: write
84
+
85
+ steps:
86
+ - name: 📥 Download the built package distributions.
87
+ uses: actions/download-artifact@v4
88
+ with:
89
+ name: python-package-distributions
90
+ path: dist/
91
+
92
+ - name: 🚀 Publish package to main PyPI index.
93
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: keyrings.codeartifact
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Automatically retrieve credentials for AWS CodeArtifact.
5
5
  Author-email: "Joshua M. Keyes" <joshua.michael.keyes@gmail.com>
6
6
  License: MIT License
@@ -45,6 +45,7 @@ Requires-Dist: black; extra == "devel"
45
45
  Provides-Extra: testing
46
46
  Requires-Dist: pytest>=6; extra == "testing"
47
47
  Requires-Dist: pytest-cov; extra == "testing"
48
+ Dynamic: license-file
48
49
 
49
50
  AWS CodeArtifact Keyring Backend
50
51
  ================================
@@ -103,7 +104,6 @@ profile_name=default
103
104
  # Use the following access keys.
104
105
  aws_access_key_id=xxxxxxxxx
105
106
  aws_secret_access_key=xxxxxxxxx
106
-
107
107
  ```
108
108
 
109
109
  ### Multiple Section Configuration (EXPERIMENTAL)
@@ -55,7 +55,6 @@ profile_name=default
55
55
  # Use the following access keys.
56
56
  aws_access_key_id=xxxxxxxxx
57
57
  aws_secret_access_key=xxxxxxxxx
58
-
59
58
  ```
60
59
 
61
60
  ### Multiple Section Configuration (EXPERIMENTAL)
@@ -1,9 +1,11 @@
1
1
  # codeartifact.py -- keyring backend
2
2
 
3
3
  import re
4
- import boto3
5
4
  import logging
6
5
 
6
+ import boto3
7
+ import boto3.session
8
+
7
9
  from datetime import datetime
8
10
  from urllib.parse import urlparse
9
11
 
@@ -45,7 +47,8 @@ class CodeArtifactKeyringConfig:
45
47
  config_parser.default_section = self.DEFAULT_SECTION
46
48
 
47
49
  # Load the configuration file.
48
- config_parser.read(config_file)
50
+ if not config_parser.read(config_file):
51
+ logging.warning(f"{config_file} does not exist!")
49
52
 
50
53
  # Collect the defaults before we go further.
51
54
  self.defaults = config_parser.defaults()
@@ -103,19 +106,37 @@ class CodeArtifactKeyringConfig:
103
106
  return self.config.get(found_key)
104
107
 
105
108
 
109
+ def make_codeartifact_client(options):
110
+ # Build a session with the provided options.
111
+ session = boto3.session.Session(
112
+ # NOTE: Only the session accepts 'profile_name'.
113
+ profile_name=options.pop("profile_name", None),
114
+ region_name=options.get("region_name"),
115
+ )
116
+
117
+ # Create a client for this new session.
118
+ return session.client("codeartifact", **options)
119
+
120
+
106
121
  class CodeArtifactBackend(backend.KeyringBackend):
107
122
  HOST_REGEX = r"^(.+)-(\d{12})\.d\.codeartifact\.([^\.]+)\.amazonaws\.com$"
108
123
  PATH_REGEX = r"^/pypi/([^/]+)/?"
109
124
 
110
125
  priority = 9.9
111
126
 
112
- def __init__(self, config_file=None):
127
+ def __init__(self, /, config=None, make_client=make_codeartifact_client):
113
128
  super().__init__()
114
129
 
115
- if not config_file:
130
+ if config:
131
+ # Use the provided configuration.
132
+ self.config = config
133
+ else:
134
+ # Use the global configuration file.
116
135
  config_file = config_root() / "keyringrc.cfg"
136
+ self.config = CodeArtifactKeyringConfig(config_file)
117
137
 
118
- self.config = CodeArtifactKeyringConfig(config_file)
138
+ # Use our default built-in CodeArtifact client producer.
139
+ self.make_client = make_client
119
140
 
120
141
  def get_credential(self, service, username):
121
142
  authorization_token = self.get_password(service, username)
@@ -134,7 +155,7 @@ class CodeArtifactBackend(backend.KeyringBackend):
134
155
 
135
156
  # If it didn't match the regex, it doesn't apply to us.
136
157
  if not host_match:
137
- logging.warning("Not an AWS CodeArtifact repository URL!")
158
+ logging.warning(f"Not an AWS CodeArtifact repository URL: {service}")
138
159
  return
139
160
 
140
161
  # Extract the domain, account and region for this repository.
@@ -148,7 +169,7 @@ class CodeArtifactBackend(backend.KeyringBackend):
148
169
 
149
170
  repository_name = path_match.group(1)
150
171
 
151
- # Load our configuration file.
172
+ # Lookup configuration options.
152
173
  config = self.config.lookup(
153
174
  domain=domain,
154
175
  account=account,
@@ -156,16 +177,43 @@ class CodeArtifactBackend(backend.KeyringBackend):
156
177
  name=repository_name,
157
178
  )
158
179
 
159
- # Create session with any supplied configuration.
160
- session = boto3.Session(
161
- region_name=region,
162
- profile_name=config.get("profile_name"),
163
- aws_access_key_id=config.get("aws_access_key_id"),
164
- aws_secret_access_key=config.get("aws_secret_access_key"),
165
- )
180
+ # Options for the client callback.
181
+ options = {
182
+ # Pass in the region name.
183
+ "region_name": region,
184
+ }
185
+
186
+ # Extract any AWS profile name we should use.
187
+ profile_name = config.get("profile_name")
188
+ if profile_name:
189
+ options.update({"profile_name": profile_name})
190
+
191
+ # Provide option to disable SSL/TLS verification.
192
+ verify = config.get("verify")
193
+ if verify:
194
+ if verify.lower() in {"1", "yes", "on", "true"}:
195
+ # Enable SSL/TLS verification explicitly.
196
+ options.update({"verify": True})
197
+ elif verify.lower() in {"0", "no", "off", "false"}:
198
+ # Disable SSL/TLS verification entirely.
199
+ options.update({"verify": False})
200
+ else:
201
+ # The SSL/TLS certificate to verify against.
202
+ options.update({"verify": verify.strip('"')})
203
+
204
+ # If static access/secret keys were provided, use them.
205
+ aws_access_key_id = config.get("aws_access_key_id")
206
+ aws_secret_access_key = config.get("aws_secret_access_key")
207
+ if aws_access_key_id and aws_secret_access_key:
208
+ options.update(
209
+ {
210
+ "aws_access_key_id": aws_access_key_id,
211
+ "aws_secret_access_key": aws_secret_access_key,
212
+ }
213
+ )
166
214
 
167
- # Create a CodeArtifact client for this repository's region.
168
- client = session.client("codeartifact", region_name=region)
215
+ # Generate a CodeArtifact client using the callback.
216
+ client = self.make_client(options)
169
217
 
170
218
  # Authorization tokens should be good for an hour by default.
171
219
  token_duration = int(config.get("token_duration", 3600))
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: keyrings.codeartifact
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Automatically retrieve credentials for AWS CodeArtifact.
5
5
  Author-email: "Joshua M. Keyes" <joshua.michael.keyes@gmail.com>
6
6
  License: MIT License
@@ -45,6 +45,7 @@ Requires-Dist: black; extra == "devel"
45
45
  Provides-Extra: testing
46
46
  Requires-Dist: pytest>=6; extra == "testing"
47
47
  Requires-Dist: pytest-cov; extra == "testing"
48
+ Dynamic: license-file
48
49
 
49
50
  AWS CodeArtifact Keyring Backend
50
51
  ================================
@@ -103,7 +104,6 @@ profile_name=default
103
104
  # Use the following access keys.
104
105
  aws_access_key_id=xxxxxxxxx
105
106
  aws_secret_access_key=xxxxxxxxx
106
-
107
107
  ```
108
108
 
109
109
  ### Multiple Section Configuration (EXPERIMENTAL)
@@ -5,6 +5,7 @@ pyproject.toml
5
5
  requirements-dev.txt
6
6
  requirements-test.txt
7
7
  requirements.txt
8
+ .github/dependabot.yml
8
9
  .github/workflows/release.yml
9
10
  keyrings/__init__.py
10
11
  keyrings/codeartifact.py
@@ -8,7 +8,7 @@ token_duration = 1800
8
8
  [codeartifact account="000000000000"]
9
9
  token_duration = 600 # Much secure.
10
10
  aws_access_key_id = not_access_key
11
- aws_secret_key_id = not_secret_key
11
+ aws_secret_access_key = not_secret_key
12
12
 
13
13
  # A section specific to a domain.
14
14
  [codeartifact domain="specific"]
@@ -0,0 +1,5 @@
1
+ [codeartifact]
2
+ token_duration = 1800
3
+ profile_name = default_profile
4
+ aws_access_key_id = default_access_key_id
5
+ aws_secret_access_key = default_access_secret_key
@@ -0,0 +1,225 @@
1
+ # test_backend.py -- backend tests
2
+
3
+ import pytest
4
+
5
+ import os
6
+ import boto3
7
+ import botocore.stub
8
+
9
+ import keyring
10
+
11
+ from io import StringIO
12
+ from pathlib import Path
13
+ from urllib.parse import urlunparse
14
+ from datetime import datetime, timedelta
15
+
16
+ from contextlib import contextmanager
17
+ from tempfile import NamedTemporaryFile
18
+
19
+ from keyrings.codeartifact import CodeArtifactBackend, CodeArtifactKeyringConfig
20
+
21
+ REGION_NAME = "ca-central-1"
22
+ CONFIG_DIR = Path(__file__).parent / "config"
23
+
24
+
25
+ def current_time():
26
+ # Compute time zone information to calculate offset.
27
+ tzinfo = datetime.now().astimezone().tzinfo
28
+ return datetime.now(tz=tzinfo)
29
+
30
+
31
+ def codeartifact_url(domain, owner, region, path):
32
+ netloc = f"{domain}-{owner}.d.codeartifact.{region}.amazonaws.com"
33
+ return urlunparse(("https", netloc, path, "", "", ""))
34
+
35
+
36
+ def codeartifact_pypi_url(domain, owner, region, name):
37
+ return codeartifact_url(domain, owner, region, f"/pypi/{name}/")
38
+
39
+
40
+ @contextmanager
41
+ def config_from_string(content: str):
42
+ """
43
+ Generates a temporary configuration file from a string.
44
+ """
45
+ with NamedTemporaryFile(mode="w+") as cfg:
46
+ cfg.write(content)
47
+ cfg.flush()
48
+ yield cfg
49
+
50
+
51
+ @pytest.fixture
52
+ def default_backend():
53
+ backend = CodeArtifactBackend()
54
+ original = keyring.get_keyring()
55
+
56
+ keyring.set_keyring(backend)
57
+ yield backend
58
+ keyring.set_keyring(original)
59
+
60
+
61
+ def test_set_password_raises(default_backend):
62
+ with pytest.raises(NotImplementedError):
63
+ keyring.set_password("service", "username", "password")
64
+
65
+
66
+ def test_delete_password_raises(default_backend):
67
+ with pytest.raises(NotImplementedError):
68
+ keyring.delete_password("service", "username")
69
+
70
+
71
+ @pytest.mark.parametrize(
72
+ "service",
73
+ [
74
+ "https://example.com/",
75
+ "https://unknown.amazonaws.com/",
76
+ codeartifact_url("domain", "owner", "region", "/maven/repo/"),
77
+ ],
78
+ )
79
+ def test_get_credential_unsupported_host(default_backend, service):
80
+ assert not keyring.get_credential(service, None)
81
+
82
+
83
+ @pytest.mark.parametrize(
84
+ "service",
85
+ [
86
+ codeartifact_url("domain", "000000000000", "region", "/pkg"),
87
+ codeartifact_url("domain", "000000000000", "region", "/pypi/"),
88
+ codeartifact_url("domain", "000000000000", "region", "/pkg/simple/"),
89
+ ],
90
+ )
91
+ def test_get_credential_invalid_path(default_backend, service):
92
+ assert not keyring.get_credential(service, None)
93
+
94
+
95
+ def test_get_credential_supported_host():
96
+ def make_client(options):
97
+ session = boto3.session.Session(
98
+ profile_name=options.pop("profile_name", None),
99
+ region_name=options.get("region_name"),
100
+ )
101
+
102
+ client = session.client("codeartifact", **options)
103
+ stubber = botocore.stub.Stubber(client)
104
+
105
+ parameters = {
106
+ "domain": "domain",
107
+ "domainOwner": "000000000000",
108
+ "durationSeconds": 3600,
109
+ }
110
+
111
+ # The response we expect from the API.
112
+ response = {
113
+ "authorizationToken": "TOKEN",
114
+ # Compute the expiration based on the current timestamp.
115
+ "expiration": current_time() + timedelta(seconds=3600),
116
+ }
117
+
118
+ stubber.add_response("get_authorization_token", response, parameters)
119
+ stubber.activate()
120
+
121
+ return client
122
+
123
+ config = CodeArtifactKeyringConfig(config_file=StringIO())
124
+ backend = CodeArtifactBackend(config=config, make_client=make_client)
125
+
126
+ url = codeartifact_pypi_url("domain", "000000000000", "region", "name")
127
+ credentials = backend.get_credential(url, None)
128
+
129
+ assert credentials.username == "aws"
130
+ assert credentials.password == "TOKEN"
131
+
132
+
133
+ @pytest.mark.parametrize(
134
+ ("configuration", "assertions"),
135
+ [
136
+ # The effective default options.
137
+ (
138
+ """
139
+ # Empty configuration file.
140
+ """,
141
+ {
142
+ "region_name": "region",
143
+ "profile_name": None,
144
+ "aws_access_key_id": None,
145
+ "aws_secret_access_key": None,
146
+ },
147
+ ),
148
+ # Overriding profile and providing access/secret keys.
149
+ (
150
+ """
151
+ [codeartifact]
152
+ profile_name = PROFILE-NAME
153
+ aws_access_key_id = ACCESS-KEY-ID
154
+ aws_secret_access_key = SECRET-ACCESS-KEY
155
+ """,
156
+ {
157
+ "profile_name": "PROFILE-NAME",
158
+ "aws_access_key_id": "ACCESS-KEY-ID",
159
+ "aws_secret_access_key": "SECRET-ACCESS-KEY",
160
+ },
161
+ ),
162
+ # Only accepting both access/secret keys together.
163
+ (
164
+ """
165
+ [codeartifact]
166
+ aws_access_key_id = ACCESS-KEY-ID
167
+ """,
168
+ {
169
+ "aws_access_key_id": None,
170
+ "aws_secret_access_key": None,
171
+ },
172
+ ),
173
+ # Overriding profile name in multi-block configuration.
174
+ (
175
+ """
176
+ [codeartifact]
177
+ profile_name = DEFAULT-PROFILE
178
+
179
+ [codeartifact name="name"]
180
+ profile_name = PROFILE-OVERRIDDEN
181
+ """,
182
+ {
183
+ "profile_name": "PROFILE-OVERRIDDEN",
184
+ },
185
+ ),
186
+ # Turning off SSL verification by default.
187
+ (
188
+ """
189
+ [codeartifact]
190
+ verify = off
191
+ """,
192
+ {
193
+ "verify": False,
194
+ },
195
+ ),
196
+ # Turning on SSL verification using a custom certificate.
197
+ (
198
+ """
199
+ [codeartifact]
200
+ verify = ./path/to/certificate.pem
201
+ """,
202
+ {
203
+ "verify": "./path/to/certificate.pem",
204
+ },
205
+ ),
206
+ ],
207
+ )
208
+ def test_backend_default_options(configuration, assertions):
209
+ class DummyClient:
210
+ def get_authorization_token(self, *args, **kwargs):
211
+ return {}
212
+
213
+ def make_client(options):
214
+ # Assert that we received specific options.
215
+ for key, value in assertions.items():
216
+ assert value == options.get(key)
217
+
218
+ # Ignore the rest.
219
+ return DummyClient()
220
+
221
+ with config_from_string(configuration) as config_file:
222
+ config = CodeArtifactKeyringConfig(config_file=config_file.name)
223
+ backend = CodeArtifactBackend(config=config, make_client=make_client)
224
+ url = codeartifact_pypi_url("domain", "000000000000", "region", "name")
225
+ credentials = backend.get_credential(url, None)
@@ -34,8 +34,8 @@ def test_parse_single_section_only(config_file, parameters):
34
34
 
35
35
  assert values.get("token_duration") == "1800"
36
36
  assert values.get("profile_name") == "default_profile"
37
- assert values.get("aws_access_key_id") == "default_access_key"
38
- assert values.get("aws_secret_key_id") == "default_secret_key"
37
+ assert values.get("aws_access_key_id") == "default_access_key_id"
38
+ assert values.get("aws_secret_access_key") == "default_access_secret_key"
39
39
 
40
40
 
41
41
  @pytest.mark.parametrize(
@@ -68,7 +68,7 @@ def test_bogus_config_returns_empty_configuration(config_data):
68
68
  {
69
69
  "token_duration": "600",
70
70
  "aws_access_key_id": "not_access_key",
71
- "aws_secret_key_id": "not_secret_key",
71
+ "aws_secret_access_key": "not_secret_key",
72
72
  },
73
73
  ),
74
74
  (
@@ -1,47 +0,0 @@
1
- # .github/workflows/release.yml
2
-
3
- name: 📦 Release 'keyrings.codeartifact' package.
4
-
5
- on: push
6
-
7
- jobs:
8
- release:
9
- name: 📦 Build and publish package.
10
-
11
- runs-on: ubuntu-latest
12
-
13
- permissions:
14
- id-token: write
15
-
16
- steps:
17
- - name: 🛒 Checkout repository source code.
18
- uses: actions/checkout@v4
19
- with:
20
- # Always fetch the full repository.
21
- fetch-depth: 0
22
-
23
- - name: ✨ Setup Python 3.x environment.
24
- uses: actions/setup-python@v5
25
- with:
26
- python-version: "3.x"
27
- cache: 'pip'
28
-
29
- - name: 🔬 Execute all unit tests.
30
- run: |
31
- python3 -m pip install .[testing]
32
- python3 -m pytest
33
-
34
- - name: 🛠️ Build package and source distribution.
35
- run: |
36
- python3 -m pip install --user build
37
- python3 -m build --sdist --wheel --outdir dist/
38
-
39
- - name: 🚀 Publish package to test PyPI index.
40
- uses: pypa/gh-action-pypi-publish@release/v1.8
41
- with:
42
- repository-url: https://test.pypi.org/legacy/
43
- skip-existing: true
44
-
45
- - name: 🚀 Publish package to main PyPI index.
46
- uses: pypa/gh-action-pypi-publish@release/v1.8
47
- if: startsWith(github.ref, 'refs/tags')
@@ -1,5 +0,0 @@
1
- [codeartifact]
2
- token_duration = 1800
3
- profile_name = default_profile
4
- aws_access_key_id = default_access_key
5
- aws_secret_key_id = default_secret_key
@@ -1,96 +0,0 @@
1
- # test_backend.py -- backend tests
2
-
3
- import pytest
4
-
5
- import keyring
6
-
7
- from io import StringIO
8
- from urllib.parse import urlunparse
9
- from botocore.client import BaseClient
10
- from datetime import datetime, timedelta
11
- from keyrings.codeartifact import CodeArtifactBackend
12
-
13
-
14
- @pytest.fixture
15
- def backend():
16
- # Find the system-wide keyring.
17
- original = keyring.get_keyring()
18
-
19
- # Use our keyring backend with an empty configuration.
20
- backend = CodeArtifactBackend(config_file=StringIO())
21
-
22
- keyring.set_keyring(backend)
23
- yield backend
24
- keyring.set_keyring(original)
25
-
26
-
27
- def codeartifact_url(domain, owner, region, path):
28
- netloc = f"{domain}-{owner}.d.codeartifact.{region}.amazonaws.com"
29
- return urlunparse(("https", netloc, path, "", "", ""))
30
-
31
-
32
- def codeartifact_pypi_url(domain, owner, region, name):
33
- return codeartifact_url(domain, owner, region, f"/pypi/{name}/")
34
-
35
-
36
- def test_set_password_raises(backend):
37
- with pytest.raises(NotImplementedError):
38
- keyring.set_password("service", "username", "password")
39
-
40
-
41
- def test_delete_password_raises(backend):
42
- with pytest.raises(NotImplementedError):
43
- keyring.delete_password("service", "username")
44
-
45
-
46
- @pytest.mark.parametrize(
47
- "service",
48
- [
49
- "https://example.com/",
50
- "https://unknown.amazonaws.com/",
51
- codeartifact_url("domain", "owner", "region", "/maven/repo/"),
52
- ],
53
- )
54
- def test_get_credential_unsupported_host(backend, service):
55
- assert not keyring.get_credential(service, None)
56
-
57
-
58
- @pytest.mark.parametrize(
59
- "service",
60
- [
61
- codeartifact_url("domain", "000000000000", "region", "/pkg"),
62
- codeartifact_url("domain", "000000000000", "region", "/pypi/"),
63
- codeartifact_url("domain", "000000000000", "region", "/pkg/simple/"),
64
- ],
65
- )
66
- def test_get_credential_invalid_path(backend, service):
67
- assert not keyring.get_credential(service, None)
68
-
69
-
70
- def test_get_credential_supported_host(backend, monkeypatch):
71
- def _make_api_call(client, *args, **kwargs):
72
- # We should only ever call GetAuthorizationToken
73
- assert args[0] == "GetAuthorizationToken"
74
-
75
- # We should only ever supply these parameters.
76
- assert args[1]["domain"] == "domain"
77
- assert args[1]["domainOwner"] == "000000000000"
78
- assert args[1]["durationSeconds"] == 3600
79
-
80
- tzinfo = datetime.now().astimezone().tzinfo
81
- current_time = datetime.now(tz=tzinfo)
82
-
83
- # Compute the expiration based on the current timestamp.
84
- expiration = timedelta(seconds=args[1]["durationSeconds"])
85
-
86
- return {
87
- "authorizationToken": "TOKEN",
88
- "expiration": current_time + expiration,
89
- }
90
-
91
- monkeypatch.setattr(BaseClient, "_make_api_call", _make_api_call)
92
- url = codeartifact_pypi_url("domain", "000000000000", "region", "name")
93
- credentials = backend.get_credential(url, None)
94
-
95
- assert credentials.username == "aws"
96
- assert credentials.password == "TOKEN"