Skip to content

Commit 55f3827

Browse files
authored
feat: allow s3 file to be copied to specific URI (#85)
1 parent 4a40fbb commit 55f3827

File tree

5 files changed

+75
-26
lines changed

5 files changed

+75
-26
lines changed

MODULE.bazel

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ module(
88

99
# Lower-bound dependency versions.
1010
# Do not change unless the rules no longer work with the current version.
11-
bazel_dep(name = "aspect_bazel_lib", version = "2.5.3")
11+
# Needed for #804 Use statically-linked bsdtar on all platforms
12+
bazel_dep(name = "aspect_bazel_lib", version = "2.6.1")
1213
bazel_dep(name = "bazel_skylib", version = "1.5.0")
1314
bazel_dep(name = "platforms", version = "0.0.8")
1415
bazel_dep(name = "rules_oci", version = "1.7.4")

aws/private/s3_sync.bzl

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ _ATTRS = {
1919
doc = "file containing a single line: the S3 path to copy to. Useful because the file content may be stamped.",
2020
allow_single_file = True,
2121
),
22+
"destination_uri_file": attr.label(
23+
doc = """Only permitted when copying a single src file. A file containing a single line:
24+
the full [S3Uri](https://docs.aws.amazon.com/cli/latest/reference/s3/#path-argument-type) to copy the file to.""",
25+
allow_single_file = True,
26+
),
2227
"role": attr.string(
2328
doc = "Assume this role before copying files, using `aws sts assume-role`",
2429
),
@@ -36,15 +41,18 @@ def _s3_sync_impl(ctx):
3641
executable = ctx.actions.declare_file("{}/s3_sync.sh".format(ctx.label.name))
3742
runfiles = [executable] + ctx.files.srcs
3843
vars = []
39-
if not ctx.attr.bucket and not ctx.attr.bucket_file:
40-
fail("Either 'bucket' or 'bucket_file' must be set")
41-
if ctx.attr.bucket and ctx.attr.bucket_file:
42-
fail("At most one of 'bucket' or 'bucket_file' may be set")
44+
if int(bool(ctx.attr.bucket)) + int(bool(ctx.attr.bucket_file)) + int(bool(ctx.attr.destination_uri_file)) != 1:
45+
fail("Exactly one of 'bucket', 'bucket_file', or 'destination_uri_file' must be set")
4346
if ctx.attr.bucket_file:
4447
vars.append("bucket_file=\"{}\"".format(ctx.file.bucket_file.short_path))
4548
runfiles.append(ctx.file.bucket_file)
46-
else:
49+
elif ctx.attr.bucket:
4750
vars.append("bucket=\"{}\"".format(ctx.attr.bucket))
51+
else:
52+
if len(ctx.files.srcs) > 1:
53+
fail("Only one source file may be copied using destination_uri_file")
54+
vars.append("destination_uri_file=\"{}\"".format(ctx.file.destination_uri_file.short_path))
55+
runfiles.append(ctx.file.destination_uri_file)
4856
if ctx.attr.role:
4957
vars.append("role=\"{}\"".format(ctx.attr.role))
5058
ctx.actions.expand_template(

aws/private/s3_sync.sh

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,23 @@ Options:
7676
Default: false
7777
7878
Arguments:
79-
<artifact> The path to a file which will be copied to the S3 bucket.
79+
<artifact> The path to a file or directory which will be copied to the S3 bucket.
8080
One or more artifacts can be specified.
8181
EOF
8282
}
8383

84+
s3_cp() {
85+
local src="${1}"
86+
local dst="${2}"
87+
88+
if [[ "${dry_run}" == "false" ]]; then
89+
warn "Copying ${src} to ${dst}"
90+
"$aws" s3 cp "${src}" "${dst}"
91+
else
92+
warn "[DRY RUN] Would copy ${src} to ${dst}"
93+
fi
94+
}
95+
8496
cp_artifact() {
8597
local artifact="${1}"
8698
local bucket="${2}"
@@ -91,14 +103,7 @@ cp_artifact() {
91103
cp_artifact "${f}" "${bucket}"
92104
done
93105
else
94-
local dst
95-
dst="${bucket}/$(basename "${artifact}")"
96-
if [[ "${dry_run}" == "false" ]]; then
97-
warn "Copying ${artifact} to ${dst}"
98-
"$aws" s3 cp "${artifact}" "${dst}"
99-
else
100-
warn "[DRY RUN] Would copy ${artifact} to ${dst}"
101-
fi
106+
s3_cp "${artifact}" "${bucket}/$(basename "${artifact}")"
102107
fi
103108
}
104109

@@ -120,6 +125,10 @@ while (("$#")); do
120125
bucket_file="${2}"
121126
shift 2
122127
;;
128+
"--destination_uri_file")
129+
destination_uri_file="${2}"
130+
shift 2
131+
;;
123132
"--dry_run")
124133
dry_run="true"
125134
shift 1
@@ -148,15 +157,20 @@ done
148157

149158
# Process Arguments
150159

151-
[[ -n "${bucket_file:-}" ]] && bucket="$(<"${bucket_file}")"
160+
[[ ${#artifacts[@]} -gt 0 ]] || usage_error "No artifacts were specified."
152161

153-
[[ -n "${bucket:-}" ]] || usage_error "Missing value for 'bucket'."
162+
if [[ ! -z "${destination_uri_file}" ]]; then
163+
[[ ${#artifacts[@]} -eq 1 ]] || usage_error "destination_uri_file may be used only with a single artifact to copy"
164+
else
165+
[[ -n "${bucket_file:-}" ]] && bucket="$(<"${bucket_file}")"
154166

155-
protocol="s3"
167+
[[ -n "${bucket:-}" ]] || usage_error "Missing value for 'bucket'."
156168

157-
[[ "${bucket}" =~ ^${protocol}:// ]] || bucket="${protocol}://${bucket}"
169+
# Syntax sugar: append s3:// protocol to bucket URI if absent
170+
protocol="s3"
158171

159-
[[ ${#artifacts[@]} -gt 0 ]] || usage_error "No artifacts were specified."
172+
[[ "${bucket}" =~ ^${protocol}:// ]] || bucket="${protocol}://${bucket}"
173+
fi
160174

161175
[[ "${dry_run}" == "true" ]] &&
162176
warn <<-'EOF'
@@ -190,11 +204,14 @@ fi
190204

191205
# Copy artifacts
192206

193-
msg "Copying the following artifacts to ${bucket}:" "${artifacts[@]}" ""
194-
195-
for artifact in "${artifacts[@]}"; do
196-
cp_artifact "${artifact}" "${bucket}"
197-
done
207+
if [[ ! -z "${destination_uri_file}" ]]; then
208+
s3_cp "${artifacts[0]}" "$(<"${destination_uri_file}")"
209+
else
210+
msg "Copying the following artifacts to ${bucket}:" "${artifacts[@]}" ""
211+
for artifact in "${artifacts[@]}"; do
212+
cp_artifact "${artifact}" "${bucket}"
213+
done
214+
fi
198215

199216
# shellcheck disable=SC2236
200217
if [[ ! -z "${role:-}" && "${dry_run}" == "false" ]]; then

docs/rules.md

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/release_to_s3/BUILD.bazel

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,25 @@ s3_sync(
3030
srcs = ["my_file.txt"],
3131
bucket = "my-bucket-name/sub-folder",
3232
)
33+
34+
##############
35+
# Use case: Copy one file to an exact S3 URI that varies depending on stamping
36+
# See https://github.com/aspect-build/rules_aws/issues/83
37+
destination_uri_file = "dst.txt"
38+
39+
expand_template(
40+
name = "destination_uri_file",
41+
out = destination_uri_file,
42+
# as an example, use the --embed_label flag to choose a destination file, e.g.
43+
# bazel run --stamp --embed_label=prod123 //my:s3_sync
44+
# will sync my_file.txt to myorg-bucket/prod123.txt
45+
stamp_substitutions = {"default": "{{BUILD_EMBED_LABEL}}"},
46+
# unstamped builds will release my_file.txt to myorg-bucket/default.txt
47+
template = ["s3://myorg-bucket/default.txt"],
48+
)
49+
50+
s3_sync(
51+
name = "release_to_stamped_filename",
52+
srcs = ["my_file.txt"],
53+
destination_uri_file = destination_uri_file,
54+
)

0 commit comments

Comments
 (0)