Skip to content

Commit 70cc7e3

Browse files
committed
Merge remote-tracking branch 'upstream/main' into davidpz/custom-validation-exceptions-via-decorators
2 parents 2668087 + f8a799d commit 70cc7e3

File tree

192 files changed

+1303
-428
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

192 files changed

+1303
-428
lines changed

.github/actions/docker-build/action.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4-
# Use this action to execute the action scripts in tools/ci-build/scripts within the Docker build image.
4+
# Use this action to execute the action scripts in tools/ci-scripts.
55
name: smithy-rs Docker Build
66
description: Run Docker build command for smithy-rs
77
inputs:
8-
# The name of the script in tools/ci-build/scripts to run
8+
# The name of the script in tools/ci-scripts to run
99
action:
1010
description: What action to run in the Docker build
1111
required: true
@@ -43,7 +43,7 @@ runs:
4343
# from attempting to download an image from ECR since it will already exist,
4444
# which enables testing build image modifications as part of the pull request.
4545
if [[ -d smithy-rs-base-image ]]; then
46-
IMAGE_TAG="$(./smithy-rs/tools/ci-build/tools-hash)"
46+
IMAGE_TAG="$(./smithy-rs/.github/scripts/docker-image-hash)"
4747
docker load -i smithy-rs-base-image/smithy-rs-base-image
4848
docker tag "smithy-rs-base-image:${IMAGE_TAG}" "smithy-rs-base-image:local"
4949
fi
@@ -52,7 +52,7 @@ runs:
5252
# or from ECR. We disable building the image from scratch so that any mistakes in the CI
5353
# configuration won't cause each individual action to build its own image, which would
5454
# drastically increase the total CI time. Fail fast!
55-
ALLOW_LOCAL_BUILD=false ./smithy-rs/tools/ci-build/acquire-build-image
55+
ALLOW_LOCAL_BUILD=false ./smithy-rs/.github/scripts/acquire-build-image
5656
# This runs the commands from the matrix strategy
5757
- name: Run ${{ inputs.action }}
5858
shell: bash

tools/ci-build/acquire-build-image renamed to .github/scripts/acquire-build-image

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import subprocess
1111
import sys
1212
import time
1313
import unittest
14+
import base64
1415

1516
REMOTE_BASE_IMAGE_NAME = "public.ecr.aws/w0m4q9l7/github-awslabs-smithy-rs-ci"
1617
LOCAL_BASE_IMAGE_NAME = "smithy-rs-base-image"
@@ -41,32 +42,41 @@ class Platform(Enum):
4142

4243
# Script context
4344
class Context:
44-
def __init__(self, start_path, script_path, tools_path, user_id, image_tag, allow_local_build, github_actions):
45+
def __init__(self, start_path, script_path, tools_path, user_id, image_tag, allow_local_build, github_actions,
46+
encrypted_docker_password, docker_passphrase):
4547
self.start_path = start_path
4648
self.script_path = script_path
4749
self.tools_path = tools_path
50+
self.docker_image_path = tools_path + "/ci-build"
4851
self.user_id = user_id
4952
self.image_tag = image_tag
5053
self.allow_local_build = allow_local_build
5154
self.github_actions = github_actions
55+
self.encrypted_docker_password = encrypted_docker_password
56+
self.docker_passphrase = docker_passphrase
5257

5358
@staticmethod
5459
def default():
5560
start_path = os.path.realpath(os.curdir)
5661
script_path = os.path.dirname(os.path.realpath(__file__))
5762
tools_path = get_cmd_output("git rev-parse --show-toplevel", cwd=script_path)[1] + "/tools"
5863
user_id = get_cmd_output("id -u")[1]
59-
image_tag = get_cmd_output("./ci-build/tools-hash", cwd=tools_path)[1]
64+
image_tag = get_cmd_output("./docker-image-hash", cwd=script_path)[1]
6065
allow_local_build = os.getenv("ALLOW_LOCAL_BUILD") != "false"
6166
github_actions = os.getenv("GITHUB_ACTIONS") == "true"
67+
encrypted_docker_password = os.getenv("ENCRYPTED_DOCKER_PASSWORD") or None
68+
docker_passphrase = os.getenv("DOCKER_LOGIN_TOKEN_PASSPHRASE") or None
69+
6270
print(f"Start path: {start_path}")
6371
print(f"Script path: {script_path}")
6472
print(f"Tools path: {tools_path}")
6573
print(f"User ID: {user_id}")
6674
print(f"Required base image tag: {image_tag}")
6775
print(f"Allow local build: {allow_local_build}")
6876
print(f"Running in GitHub Actions: {github_actions}")
69-
return Context(start_path, script_path, tools_path, user_id, image_tag, allow_local_build, github_actions)
77+
return Context(start_path=start_path, script_path=script_path, tools_path=tools_path, user_id=user_id,
78+
image_tag=image_tag, allow_local_build=allow_local_build, github_actions=github_actions,
79+
encrypted_docker_password=encrypted_docker_password, docker_passphrase=docker_passphrase)
7080

7181

7282
def output_contains_any(stdout, stderr, messages):
@@ -75,7 +85,6 @@ def output_contains_any(stdout, stderr, messages):
7585
return True
7686
return False
7787

78-
7988
# Mockable shell commands
8089
class Shell:
8190
# Returns the platform that this script is running on
@@ -90,6 +99,9 @@ class Shell:
9099
(status, _, _) = get_cmd_output(f"docker inspect \"{image_name}:{image_tag}\"", check=False)
91100
return status == 0
92101

102+
def docker_login(self, password):
103+
get_cmd_output("docker login --username AWS --password-stdin public.ecr.aws", input=password.encode('utf-8'))
104+
93105
# Pulls the requested `image_name` with `image_tag`. Returns `DockerPullResult`.
94106
def docker_pull(self, image_name, image_tag):
95107
(status, stdout, stderr) = get_cmd_output(f"docker pull \"{image_name}:{image_tag}\"", check=False)
@@ -101,7 +113,7 @@ class Shell:
101113
print("-------------------")
102114

103115
not_found_messages = ["not found: manifest unknown"]
104-
throttle_messages = ["toomanyrequests: Rate exceeded", "toomanyrequests: Data limit exceeded"]
116+
throttle_messages = ["toomanyrequests:"]
105117
retryable_messages = ["net/http: TLS handshake timeout"]
106118
if status == 0:
107119
return DockerPullResult.SUCCESS
@@ -118,10 +130,10 @@ class Shell:
118130
run(f"docker build -t \"smithy-rs-base-image:{image_tag}\" .", cwd=path)
119131

120132
# Builds the local build image
121-
def docker_build_build_image(self, user_id, script_path):
133+
def docker_build_build_image(self, user_id, docker_image_path):
122134
run(
123135
f"docker build -t smithy-rs-build-image --file add-local-user.dockerfile --build-arg=USER_ID={user_id} .",
124-
cwd=script_path
136+
cwd=docker_image_path
125137
)
126138

127139
# Saves the Docker image named `image_name` with `image_tag` to `output_path`
@@ -134,10 +146,10 @@ class Shell:
134146

135147

136148
# Pulls a Docker image and retries if it gets throttled
137-
def docker_pull_with_retry(shell, image_name, image_tag, throttle_sleep_time=45, retryable_error_sleep_time=1):
149+
def docker_pull_with_retry(shell, image_name, image_tag, throttle_sleep_time=120, retryable_error_sleep_time=1):
138150
if shell.platform() == Platform.ARM_64:
139151
return DockerPullResult.REMOTE_ARCHITECTURE_MISMATCH
140-
for attempt in range(1, 5):
152+
for attempt in range(1, 6):
141153
announce(f"Attempting to pull remote image {image_name}:{image_tag} (attempt {attempt})...")
142154
result = shell.docker_pull(image_name, image_tag)
143155
if result == DockerPullResult.ERROR_THROTTLED:
@@ -159,17 +171,39 @@ def run(command, cwd=None):
159171

160172

161173
# Returns (status, output) from a shell command
162-
def get_cmd_output(command, cwd=None, check=True):
174+
def get_cmd_output(command, cwd=None, check=True, **kwargs):
175+
if isinstance(command, str):
176+
command = shlex.split(command)
177+
163178
result = subprocess.run(
164-
shlex.split(command),
179+
command,
165180
capture_output=True,
166-
check=check,
167-
cwd=cwd
181+
check=False,
182+
cwd=cwd,
183+
**kwargs
168184
)
169-
return (result.returncode, result.stdout.decode("utf-8").strip(), result.stderr.decode("utf-8").strip())
185+
stdout = result.stdout.decode("utf-8").strip()
186+
stderr = result.stderr.decode("utf-8").strip()
187+
if check and result.returncode != 0:
188+
raise Exception(f"failed to run '{command}.\n{stdout}\n{stderr}")
189+
190+
return result.returncode, stdout, stderr
191+
192+
193+
def decrypt_and_login(shell, secret, passphrase):
194+
decoded = base64.b64decode(secret, validate=True)
195+
if not passphrase:
196+
raise Exception("a secret was set but no passphrase was set (or it was empty)")
197+
(code, password, err) = get_cmd_output(
198+
["gpg", "--decrypt", "--batch", "--quiet", "--passphrase", passphrase, "--output", "-"],
199+
input=decoded)
200+
shell.docker_login(password)
201+
print("Docker login success!")
170202

171203

172204
def acquire_build_image(context=Context.default(), shell=Shell()):
205+
if context.encrypted_docker_password is not None:
206+
decrypt_and_login(shell, context.encrypted_docker_password, context.docker_passphrase)
173207
# If the image doesn't already exist locally, then look remotely
174208
if not shell.docker_image_exists_locally(LOCAL_BASE_IMAGE_NAME, context.image_tag):
175209
announce("Base image not found locally.")
@@ -188,7 +222,7 @@ def acquire_build_image(context=Context.default(), shell=Shell()):
188222
return 1
189223

190224
announce("Building a new image locally.")
191-
shell.docker_build_base_image(context.image_tag, context.tools_path)
225+
shell.docker_build_base_image(context.image_tag, context.docker_image_path)
192226

193227
if context.github_actions:
194228
announce("Saving base image for use in later jobs...")
@@ -205,20 +239,23 @@ def acquire_build_image(context=Context.default(), shell=Shell()):
205239

206240
announce("Creating local build image...")
207241
shell.docker_tag(LOCAL_BASE_IMAGE_NAME, context.image_tag, LOCAL_BASE_IMAGE_NAME, LOCAL_TAG)
208-
shell.docker_build_build_image(context.user_id, context.script_path)
242+
shell.docker_build_build_image(context.user_id, context.docker_image_path)
209243
return 0
210244

211245

212246
class SelfTest(unittest.TestCase):
213-
def test_context(self, allow_local_build=False, github_actions=False):
247+
def test_context(self, github_actions=False, allow_local_build=False, encrypted_docker_password=None,
248+
docker_passphrase=None):
214249
return Context(
215250
start_path="/tmp/test/start-path",
216251
script_path="/tmp/test/script-path",
217252
tools_path="/tmp/test/tools-path",
218253
user_id="123",
219254
image_tag="someimagetag",
255+
encrypted_docker_password=encrypted_docker_password,
256+
docker_passphrase=docker_passphrase,
257+
github_actions=github_actions,
220258
allow_local_build=allow_local_build,
221-
github_actions=github_actions
222259
)
223260

224261
def mock_shell(self):
@@ -230,6 +267,7 @@ class SelfTest(unittest.TestCase):
230267
shell.docker_pull = MagicMock()
231268
shell.docker_save = MagicMock()
232269
shell.docker_tag = MagicMock()
270+
shell.docker_login = MagicMock()
233271
return shell
234272

235273
def test_retry_architecture_mismatch(self):
@@ -246,6 +284,13 @@ class SelfTest(unittest.TestCase):
246284
)
247285
)
248286

287+
def test_docker_login(self):
288+
shell = self.mock_shell()
289+
acquire_build_image(self.test_context(
290+
encrypted_docker_password="jA0ECQMCvYU/JxsX3g/70j0BxbLLW8QaFWWb/DqY9gPhTuEN/xdYVxaoDnV6Fha+lAWdT7xN0qZr5DHPBalLfVvvM1SEXRBI8qnfXyGI",
291+
docker_passphrase="secret"), shell)
292+
shell.docker_login.assert_called_with("payload")
293+
249294
def test_retry_immediate_success(self):
250295
shell = self.mock_shell()
251296
shell.docker_pull.side_effect = [DockerPullResult.SUCCESS]
@@ -373,7 +418,7 @@ class SelfTest(unittest.TestCase):
373418

374419
shell.docker_image_exists_locally.assert_called_once()
375420
shell.docker_tag.assert_called_with(LOCAL_BASE_IMAGE_NAME, "someimagetag", LOCAL_BASE_IMAGE_NAME, LOCAL_TAG)
376-
shell.docker_build_build_image.assert_called_with("123", "/tmp/test/script-path")
421+
shell.docker_build_build_image.assert_called_with("123", "/tmp/test/tools-path/ci-build")
377422

378423
# When:
379424
# - the base image doesn't exist locally
@@ -390,10 +435,10 @@ class SelfTest(unittest.TestCase):
390435

391436
self.assertEqual(0, acquire_build_image(context, shell))
392437
shell.docker_image_exists_locally.assert_called_once()
393-
shell.docker_build_base_image.assert_called_with("someimagetag", "/tmp/test/tools-path")
438+
shell.docker_build_base_image.assert_called_with("someimagetag", "/tmp/test/tools-path/ci-build")
394439
shell.docker_save.assert_not_called()
395440
shell.docker_tag.assert_called_with(LOCAL_BASE_IMAGE_NAME, "someimagetag", LOCAL_BASE_IMAGE_NAME, LOCAL_TAG)
396-
shell.docker_build_build_image.assert_called_with("123", "/tmp/test/script-path")
441+
shell.docker_build_build_image.assert_called_with("123", "/tmp/test/tools-path/ci-build")
397442

398443
# When:
399444
# - the base image doesn't exist locally
@@ -410,10 +455,10 @@ class SelfTest(unittest.TestCase):
410455

411456
self.assertEqual(0, acquire_build_image(context, shell))
412457
shell.docker_image_exists_locally.assert_called_once()
413-
shell.docker_build_base_image.assert_called_with("someimagetag", "/tmp/test/tools-path")
458+
shell.docker_build_base_image.assert_called_with("someimagetag", "/tmp/test/tools-path/ci-build")
414459
shell.docker_save.assert_not_called()
415460
shell.docker_tag.assert_called_with(LOCAL_BASE_IMAGE_NAME, "someimagetag", LOCAL_BASE_IMAGE_NAME, LOCAL_TAG)
416-
shell.docker_build_build_image.assert_called_with("123", "/tmp/test/script-path")
461+
shell.docker_build_build_image.assert_called_with("123", "/tmp/test/tools-path/ci-build")
417462

418463
# When:
419464
# - the base image doesn't exist locally
@@ -430,14 +475,14 @@ class SelfTest(unittest.TestCase):
430475

431476
self.assertEqual(0, acquire_build_image(context, shell))
432477
shell.docker_image_exists_locally.assert_called_once()
433-
shell.docker_build_base_image.assert_called_with("someimagetag", "/tmp/test/tools-path")
478+
shell.docker_build_base_image.assert_called_with("someimagetag", "/tmp/test/tools-path/ci-build")
434479
shell.docker_save.assert_called_with(
435480
LOCAL_BASE_IMAGE_NAME,
436481
"someimagetag",
437482
"/tmp/test/start-path/smithy-rs-base-image"
438483
)
439484
shell.docker_tag.assert_called_with(LOCAL_BASE_IMAGE_NAME, "someimagetag", LOCAL_BASE_IMAGE_NAME, LOCAL_TAG)
440-
shell.docker_build_build_image.assert_called_with("123", "/tmp/test/script-path")
485+
shell.docker_build_build_image.assert_called_with("123", "/tmp/test/tools-path/ci-build")
441486

442487
# When:
443488
# - the base image doesn't exist locally
@@ -477,7 +522,7 @@ class SelfTest(unittest.TestCase):
477522
call(REMOTE_BASE_IMAGE_NAME, "someimagetag", LOCAL_BASE_IMAGE_NAME, "someimagetag"),
478523
call(LOCAL_BASE_IMAGE_NAME, "someimagetag", LOCAL_BASE_IMAGE_NAME, LOCAL_TAG)
479524
])
480-
shell.docker_build_build_image.assert_called_with("123", "/tmp/test/script-path")
525+
shell.docker_build_build_image.assert_called_with("123", "/tmp/test/tools-path/ci-build")
481526

482527

483528
def main():
File renamed without changes.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/bin/bash
2+
#
3+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
set -eux
7+
8+
# Compute the name of the release branch starting from the version that needs to be released ($SEMANTIC_VERSION).
9+
# If it's the beginning of a new release series, the branch is created and pushed to the remote (chosen according to
10+
# the value $DRY_RUN).
11+
# If it isn't the beginning of a new release series, the script makes sure that the commit that will be tagged is at
12+
# the tip of the (pre-existing) release branch.
13+
#
14+
# The script populates an output file with key-value pairs that are needed in the release CI workflow to carry out
15+
# the next steps in the release flow: the name of the release branch and a boolean flag that is set to 'true' if this
16+
# is the beginning of a new release series.
17+
18+
if [ -z "$SEMANTIC_VERSION" ]; then
19+
echo "'SEMANTIC_VERSION' must be populated."
20+
exit 1
21+
fi
22+
23+
if [ -z "$1" ]; then
24+
echo "You need to specify the path of the file where you want to collect the output"
25+
exit 1
26+
else
27+
output_file="$1"
28+
fi
29+
30+
# Split on the dots
31+
version_array=(${SEMANTIC_VERSION//./ })
32+
major=${version_array[0]}
33+
minor=${version_array[1]}
34+
patch=${version_array[2]}
35+
if [[ "${major}" == "" || "${minor}" == "" || "${patch}" == "" ]]; then
36+
echo "'${SEMANTIC_VERSION}' is not a valid semver tag"
37+
exit 1
38+
fi
39+
if [[ $major == 0 ]]; then
40+
branch_name="smithy-rs-release-${major}.${minor}.x"
41+
if [[ $patch == 0 ]]; then
42+
echo "new_release_series=true" >"${output_file}"
43+
fi
44+
else
45+
branch_name="smithy-rs-release-${major}.x.y"
46+
if [[ $minor == 0 && $patch == 0 ]]; then
47+
echo "new_release_series=true" >"${output_file}"
48+
fi
49+
fi
50+
51+
if [[ "${DRY_RUN}" == "true" ]]; then
52+
branch_name="${branch_name}-preview"
53+
fi
54+
echo "release_branch=${branch_name}" >"${output_file}"
55+
56+
if [[ "${DRY_RUN}" == "true" ]]; then
57+
git push --force origin "HEAD:refs/heads/${branch_name}"
58+
else
59+
commit_sha=$(git rev-parse --short HEAD)
60+
if git ls-remote --exit-code --heads origin "${branch_name}"; then
61+
# The release branch already exists, we need to make sure that our commit is its current tip
62+
branch_head_sha=$(git rev-parse --verify --short "refs/heads/${branch_name}")
63+
if [[ "${branch_head_sha}" != "${commit_sha}" ]]; then
64+
echo "The release branch - ${branch_name} - already exists. ${commit_sha}, the commit you chose when "
65+
echo "launching this release, is not its current HEAD (${branch_head_sha}). This is not allowed: you "
66+
echo "MUST release from the HEAD of the release branch if it already exists."
67+
exit 1
68+
fi
69+
else
70+
# The release branch does not exist.
71+
# We need to make sure that the commit SHA that we are releasing is on `main`.
72+
git fetch origin main
73+
if git branch --contains "${commit_sha}" | grep main; then
74+
# We can then create the release branch and set the current commit as its tip
75+
git checkout -b "${branch_name}"
76+
git push origin "${branch_name}"
77+
else
78+
echo "You must choose a commit from main to create a new release series!"
79+
exit 1
80+
fi
81+
fi
82+
fi

0 commit comments

Comments
 (0)