keyrings.codeartifact 2.0.0__tar.gz → 2.1.1__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.
- keyrings_codeartifact-2.1.1/.github/dependabot.yml +11 -0
- keyrings_codeartifact-2.1.1/.github/workflows/release.yml +123 -0
- {keyrings_codeartifact-2.0.0/keyrings.codeartifact.egg-info → keyrings_codeartifact-2.1.1}/PKG-INFO +3 -26
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/README.md +0 -1
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/keyrings/codeartifact.py +64 -16
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1/keyrings.codeartifact.egg-info}/PKG-INFO +3 -26
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/keyrings.codeartifact.egg-info/SOURCES.txt +1 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/pyproject.toml +1 -2
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/tests/config/multiple_sections_with_default.cfg +1 -1
- keyrings_codeartifact-2.1.1/tests/config/single_section.cfg +5 -0
- keyrings_codeartifact-2.1.1/tests/test_backend.py +225 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/tests/test_config.py +3 -3
- keyrings_codeartifact-2.0.0/.github/workflows/release.yml +0 -47
- keyrings_codeartifact-2.0.0/tests/config/single_section.cfg +0 -5
- keyrings_codeartifact-2.0.0/tests/test_backend.py +0 -96
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/.gitignore +0 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/LICENSE +0 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/keyrings/__init__.py +0 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/keyrings.codeartifact.egg-info/dependency_links.txt +0 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/keyrings.codeartifact.egg-info/entry_points.txt +0 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/keyrings.codeartifact.egg-info/requires.txt +0 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/keyrings.codeartifact.egg-info/top_level.txt +0 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/requirements-dev.txt +0 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/requirements-test.txt +0 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/requirements.txt +0 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/setup.cfg +0 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/tests/__init__.py +0 -0
- {keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1}/tests/config/multiple_sections_no_default.cfg +0 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
# .github/workflows/release.yml
|
2
|
+
|
3
|
+
name: 🏗️ Build & release package.
|
4
|
+
|
5
|
+
on: push
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
build:
|
9
|
+
name: 📦 Build and test 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 artifacts.
|
36
|
+
uses: actions/upload-artifact@v4
|
37
|
+
with:
|
38
|
+
name: python-package-artifacts
|
39
|
+
path: dist/
|
40
|
+
|
41
|
+
publish-to-testpypi:
|
42
|
+
name: 🚀 Publish package 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 artifacts.
|
58
|
+
uses: actions/download-artifact@v4
|
59
|
+
with:
|
60
|
+
name: python-package-artifacts
|
61
|
+
path: dist/
|
62
|
+
|
63
|
+
- name: 🚀 Publish package 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 package 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
|
+
- publish-to-testpypi
|
77
|
+
|
78
|
+
environment:
|
79
|
+
name: pypi
|
80
|
+
url: https://pypi.org/p/keyrings.codeartifact
|
81
|
+
|
82
|
+
permissions:
|
83
|
+
# IMPORTANT: mandatory for trusted publishing
|
84
|
+
id-token: write
|
85
|
+
|
86
|
+
steps:
|
87
|
+
- name: 📥 Download the built artifacts.
|
88
|
+
uses: actions/download-artifact@v4
|
89
|
+
with:
|
90
|
+
name: python-package-artifacts
|
91
|
+
path: dist/
|
92
|
+
|
93
|
+
- name: 🚀 Publish package to main PyPI index.
|
94
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
95
|
+
|
96
|
+
publish-github-release:
|
97
|
+
name: 🚀 Publish package to GitHub release.
|
98
|
+
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
|
99
|
+
runs-on: ubuntu-latest
|
100
|
+
|
101
|
+
needs:
|
102
|
+
- build
|
103
|
+
|
104
|
+
permissions:
|
105
|
+
# IMPORTANT: mandatory for github release publishing
|
106
|
+
contents: write
|
107
|
+
|
108
|
+
steps:
|
109
|
+
- name: 📥 Download the built artifacts.
|
110
|
+
uses: actions/download-artifact@v4
|
111
|
+
with:
|
112
|
+
name: python-package-artifacts
|
113
|
+
path: dist/
|
114
|
+
|
115
|
+
- name: 🎁 Publish package to GitHub release.
|
116
|
+
env:
|
117
|
+
GITHUB_REPOSITORY: ${{ github.repository }}
|
118
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
119
|
+
TAG_NAME: ${{ github.ref_name }}
|
120
|
+
run: |
|
121
|
+
gh release create "${TAG_NAME}" --repo="${GITHUB_REPOSITORY}" \
|
122
|
+
--title="${TAG_NAME}" --generate-notes --verify-tag \
|
123
|
+
dist/*
|
{keyrings_codeartifact-2.0.0/keyrings.codeartifact.egg-info → keyrings_codeartifact-2.1.1}/PKG-INFO
RENAMED
@@ -1,37 +1,14 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: keyrings.codeartifact
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.1.1
|
4
4
|
Summary: Automatically retrieve credentials for AWS CodeArtifact.
|
5
5
|
Author-email: "Joshua M. Keyes" <joshua.michael.keyes@gmail.com>
|
6
|
-
License: MIT License
|
7
|
-
|
8
|
-
Copyright (c) 2022 Joshua M. Keyes <joshua.michael.keyes@gmail.com>
|
9
|
-
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
12
|
-
in the Software without restriction, including without limitation the rights
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
15
|
-
furnished to do so, subject to the following conditions:
|
16
|
-
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
18
|
-
copies or substantial portions of the Software.
|
19
|
-
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
26
|
-
SOFTWARE.
|
27
|
-
|
28
6
|
Project-URL: Homepage, https://github.com/jmkeyes/keyrings.codeartifact
|
29
7
|
Project-URL: Issues, https://github.com/jmkeyes/keyrings.codeartifact/issues
|
30
8
|
Project-URL: Repository, https://github.com/jmkeyes/keyrings.codeartifact.git
|
31
9
|
Keywords: aws,codeartifact,keyring
|
32
10
|
Classifier: Development Status :: 4 - Beta
|
33
11
|
Classifier: Intended Audience :: Developers
|
34
|
-
Classifier: License :: OSI Approved :: MIT License
|
35
12
|
Classifier: Programming Language :: Python :: 3
|
36
13
|
Classifier: Programming Language :: Python :: 3 :: Only
|
37
14
|
Requires-Python: >=3.9
|
@@ -45,6 +22,7 @@ Requires-Dist: black; extra == "devel"
|
|
45
22
|
Provides-Extra: testing
|
46
23
|
Requires-Dist: pytest>=6; extra == "testing"
|
47
24
|
Requires-Dist: pytest-cov; extra == "testing"
|
25
|
+
Dynamic: license-file
|
48
26
|
|
49
27
|
AWS CodeArtifact Keyring Backend
|
50
28
|
================================
|
@@ -103,7 +81,6 @@ profile_name=default
|
|
103
81
|
# Use the following access keys.
|
104
82
|
aws_access_key_id=xxxxxxxxx
|
105
83
|
aws_secret_access_key=xxxxxxxxx
|
106
|
-
|
107
84
|
```
|
108
85
|
|
109
86
|
### 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,
|
127
|
+
def __init__(self, /, config=None, make_client=make_codeartifact_client):
|
113
128
|
super().__init__()
|
114
129
|
|
115
|
-
if
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
-
#
|
168
|
-
client =
|
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))
|
{keyrings_codeartifact-2.0.0 → keyrings_codeartifact-2.1.1/keyrings.codeartifact.egg-info}/PKG-INFO
RENAMED
@@ -1,37 +1,14 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: keyrings.codeartifact
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.1.1
|
4
4
|
Summary: Automatically retrieve credentials for AWS CodeArtifact.
|
5
5
|
Author-email: "Joshua M. Keyes" <joshua.michael.keyes@gmail.com>
|
6
|
-
License: MIT License
|
7
|
-
|
8
|
-
Copyright (c) 2022 Joshua M. Keyes <joshua.michael.keyes@gmail.com>
|
9
|
-
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
12
|
-
in the Software without restriction, including without limitation the rights
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
15
|
-
furnished to do so, subject to the following conditions:
|
16
|
-
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
18
|
-
copies or substantial portions of the Software.
|
19
|
-
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
26
|
-
SOFTWARE.
|
27
|
-
|
28
6
|
Project-URL: Homepage, https://github.com/jmkeyes/keyrings.codeartifact
|
29
7
|
Project-URL: Issues, https://github.com/jmkeyes/keyrings.codeartifact/issues
|
30
8
|
Project-URL: Repository, https://github.com/jmkeyes/keyrings.codeartifact.git
|
31
9
|
Keywords: aws,codeartifact,keyring
|
32
10
|
Classifier: Development Status :: 4 - Beta
|
33
11
|
Classifier: Intended Audience :: Developers
|
34
|
-
Classifier: License :: OSI Approved :: MIT License
|
35
12
|
Classifier: Programming Language :: Python :: 3
|
36
13
|
Classifier: Programming Language :: Python :: 3 :: Only
|
37
14
|
Requires-Python: >=3.9
|
@@ -45,6 +22,7 @@ Requires-Dist: black; extra == "devel"
|
|
45
22
|
Provides-Extra: testing
|
46
23
|
Requires-Dist: pytest>=6; extra == "testing"
|
47
24
|
Requires-Dist: pytest-cov; extra == "testing"
|
25
|
+
Dynamic: license-file
|
48
26
|
|
49
27
|
AWS CodeArtifact Keyring Backend
|
50
28
|
================================
|
@@ -103,7 +81,6 @@ profile_name=default
|
|
103
81
|
# Use the following access keys.
|
104
82
|
aws_access_key_id=xxxxxxxxx
|
105
83
|
aws_secret_access_key=xxxxxxxxx
|
106
|
-
|
107
84
|
```
|
108
85
|
|
109
86
|
### Multiple Section Configuration (EXPERIMENTAL)
|
@@ -10,7 +10,7 @@ name = "keyrings.codeartifact"
|
|
10
10
|
description = "Automatically retrieve credentials for AWS CodeArtifact."
|
11
11
|
dynamic = ["version", "dependencies", "optional-dependencies"]
|
12
12
|
keywords = ["aws", "codeartifact", "keyring"]
|
13
|
-
license =
|
13
|
+
license-files = [ "LICENSE" ]
|
14
14
|
requires-python = ">= 3.9"
|
15
15
|
readme = "README.md"
|
16
16
|
authors = [
|
@@ -19,7 +19,6 @@ authors = [
|
|
19
19
|
classifiers = [
|
20
20
|
"Development Status :: 4 - Beta",
|
21
21
|
"Intended Audience :: Developers",
|
22
|
-
"License :: OSI Approved :: MIT License",
|
23
22
|
"Programming Language :: Python :: 3",
|
24
23
|
"Programming Language :: Python :: 3 :: Only",
|
25
24
|
]
|
@@ -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
|
-
|
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,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") == "
|
38
|
-
assert values.get("
|
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
|
-
"
|
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,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"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|