Skip to content

Commit 3a7ce7c

Browse files
authored
Merge pull request #202 from shanejbrown/secrets
Add support for buildx build secrets
2 parents 0c5640f + 2940d15 commit 3a7ce7c

File tree

10 files changed

+165
-40
lines changed

10 files changed

+165
-40
lines changed

README.rst

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,60 @@ shows the different configuration options available:
350350
# image is passed to subsequent steps.
351351
import: path/to/image/archive.tar
352352
353+
# Specify the secrets that should be used when building your image,
354+
# similar to the --secret option used by Docker
355+
# More info about secrets: https://docs.docker.com/build/building/secrets/
356+
secrets:
357+
# Example of a secret that is a file
358+
- id=secret1,src=<path to the secret file>
359+
# Example of a secret that is an environment variable
360+
- id=secret2,env=<environment variable name>
361+
362+
.. _Build Secrets:
363+
364+
Build Secrets
365+
=============
366+
367+
Buildrunner supports specifying secrets that should be used when building your image,
368+
similar to the --secret option used by Docker. This is done by adding the ``secrets``
369+
section to the ``build`` section. This is a list of secrets that should be used when
370+
building the image. The string should be in the format of ``id=secret1,src=<location of the file>``
371+
when the secret is a file or ``id=secret2,env=<environment variable name>`` when the secret is an environment variable.
372+
This syntax is the same as the syntax used by Docker to build with secrets.
373+
More info about building with secrets in docker and the syntax of the secret string
374+
see https://docs.docker.com/build/building/secrets/.
375+
376+
In order to use secrets in buildrunner, you need to do the following:
377+
378+
#. Update the buildrunner configuration file
379+
* Set ``use-legacy-builder`` to ``false`` or add ``platforms`` to the ``build`` section
380+
* Add the secrets to the ``secrets`` section in the ``build`` section
381+
#. Update the Dockerfile to use the secrets
382+
* Add the ``--mount`` at the beginning of each RUN command that needs the secret
383+
384+
.. code:: yaml
385+
386+
use-legacy-builder: false
387+
steps:
388+
build-my-container:
389+
build:
390+
dockerfile: |
391+
FROM alpine:latest
392+
# Using secrets inline
393+
RUN --mount=type=secret,id=secret1 \
394+
--mount=type=secret,id=secret2 \
395+
echo Using secrets in my build - secret1 file located at /run/secrets/secret1 with contents $(cat /run/secrets/secret1) and secret2=$(cat /run/secrets/secret2)
396+
# Using secrets in environment variables
397+
RUN --mount=type=secret,id=secret1 \
398+
--mount=type=secret,id=secret2 \
399+
SECRET1_FILE=/run/secrets/secret1 \
400+
SECRET2_VARIABLE=$(cat /run/secrets/secret2) \
401+
&& echo Using secrets in my build - secret1 file located at $SECRET1_FILE with contents $(cat $SECRET1_FILE) and secret2=$SECRET2_VARIABLE
402+
secrets:
403+
# Example of a secret that is a file
404+
- id=secret1,src=examples/build/secrets/secret1.txt
405+
# Example of a secret that is an environment variable
406+
- id=secret2,env=SECRET2
353407
354408
.. _Running Containers:
355409

buildrunner/config/models.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ class Config(BaseModel, extra="forbid"):
150150

151151
@field_validator("steps")
152152
@classmethod
153-
def validate_steps(cls, vals) -> None:
153+
def validate_steps(cls, vals, info) -> None:
154154
"""
155155
Validate the config file
156156
@@ -161,13 +161,26 @@ def validate_steps(cls, vals) -> None:
161161
if not vals:
162162
raise ValueError('The "steps" configuration was not provided')
163163

164-
# Checks to see if there is a mutli-platform build step in the config
164+
# Checks steps for mutli-platform or secrets
165165
has_multi_platform_build = False
166+
167+
# Check for multi-platform builds and secrets validation
166168
for step in vals.values():
167169
has_multi_platform_build = (
168170
has_multi_platform_build or step.is_multi_platform()
169171
)
170172

173+
# If the step has secrets and the builder is legacy or no platforms are set for the step, raise an error
174+
if (
175+
step.has_secrets()
176+
and not step.is_multi_platform()
177+
and info.data.get("use_legacy_builder")
178+
):
179+
raise ValueError(
180+
"Build secrets are not supported with the legacy builder. Please set use-legacy-builder to false"
181+
" or add platforms to the build section in order to use secrets in your build."
182+
)
183+
171184
if has_multi_platform_build:
172185
mp_push_tags = set()
173186
validate_multiplatform_build(vals, mp_push_tags)

buildrunner/config/models_step.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class StepBuild(StepTask):
8080
cache_to: Optional[Union[str, Dict[str, str]]] = None
8181
# import is a python reserved keyword so we need to alias it
8282
import_param: Optional[str] = Field(alias="import", default=None)
83+
secrets: Optional[List[str]] = None
8384

8485

8586
class RunAndServicesBase(StepTask):
@@ -242,3 +243,9 @@ def is_multi_platform(self):
242243
Check if the step is a multi-platform build step
243244
"""
244245
return self.build and self.build.platforms is not None
246+
247+
def has_secrets(self):
248+
"""
249+
Check if the step has secrets
250+
"""
251+
return self.build and self.build.secrets is not None

buildrunner/docker/multiplatform_image_builder.py

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def __init__(
8080
cache_builders: Optional[List[str]] = None,
8181
cache_from: Optional[Union[dict, str]] = None,
8282
cache_to: Optional[Union[dict, str]] = None,
83+
secrets: Optional[List[str]] = None,
8384
):
8485
self._docker_registry = docker_registry
8586
self._build_registry = build_registry
@@ -89,6 +90,7 @@ def __init__(
8990
self._cache_builders = set(cache_builders if cache_builders else [])
9091
self._cache_from = cache_from
9192
self._cache_to = cache_to
93+
self._secrets = secrets
9294
if self._cache_from or self._cache_to:
9395
LOGGER.info(
9496
f'Configuring multiplatform builds to cache from {cache_from} and to {cache_to} '
@@ -196,14 +198,11 @@ def _build_with_inject(
196198
self,
197199
inject: dict,
198200
image_ref: str,
199-
platform: str,
200201
path: str,
202+
platform: str,
201203
dockerfile: str,
202-
target: str,
203204
build_args: dict,
204-
builder: Optional[str],
205-
cache: bool = False,
206-
pull: bool = False,
205+
build_kwargs: dict,
207206
) -> None:
208207
if not path or not os.path.isdir(path):
209208
LOGGER.warning(
@@ -256,16 +255,13 @@ def _build_with_inject(
256255

257256
logs_itr = docker.buildx.build(
258257
context_dir,
259-
tags=[image_ref],
260-
platforms=[platform],
261-
load=True,
262-
target=target,
263-
builder=builder,
264258
build_args=build_args,
265-
cache=cache,
266-
pull=pull,
259+
load=True,
260+
platforms=[platform],
267261
stream_logs=True,
268-
**self._get_build_cache_options(builder),
262+
tags=[image_ref],
263+
**build_kwargs,
264+
**self._get_build_cache_options(build_kwargs.get("builder")),
269265
)
270266
self._log_buildx(logs_itr, platform)
271267

@@ -294,6 +290,7 @@ def _build_single_image(
294290
inject: dict,
295291
cache: bool = False,
296292
pull: bool = False,
293+
secrets: Optional[List[str]] = None,
297294
) -> None:
298295
"""
299296
Builds a single image for the given platform.
@@ -307,6 +304,7 @@ def _build_single_image(
307304
target (str): The name of the stage to build in a multi-stage Dockerfile
308305
build_args (dict): The build args to pass to docker.
309306
inject (dict): The files to inject into the build context.
307+
secrets (List[str]): The secrets to pass to docker.
310308
"""
311309
assert os.path.isdir(path) and os.path.exists(dockerfile), (
312310
f"Either path {path} ({os.path.isdir(path)}) or file "
@@ -321,18 +319,28 @@ def _build_single_image(
321319
f"Building image for platform {platform} with {builder or 'default'} builder"
322320
)
323321

322+
# Build kwargs for the buildx build command
323+
build_kwargs = {}
324+
if builder:
325+
build_kwargs["builder"] = builder
326+
if cache:
327+
build_kwargs["cache"] = cache
328+
if pull:
329+
build_kwargs["pull"] = pull
330+
if secrets:
331+
build_kwargs["secrets"] = secrets
332+
if target:
333+
build_kwargs["target"] = target
334+
324335
if inject and isinstance(inject, dict):
325336
self._build_with_inject(
337+
path=path,
326338
inject=inject,
327339
image_ref=image_ref,
328340
platform=platform,
329-
path=path,
330341
dockerfile=dockerfile,
331-
target=target,
332342
build_args=build_args,
333-
builder=builder,
334-
cache=cache,
335-
pull=pull,
343+
build_kwargs=build_kwargs,
336344
)
337345
else:
338346
logs_itr = docker.buildx.build(
@@ -341,12 +349,9 @@ def _build_single_image(
341349
platforms=[platform],
342350
load=True,
343351
file=dockerfile,
344-
target=target,
345352
build_args=build_args,
346-
builder=builder,
347-
cache=cache,
348-
pull=pull,
349353
stream_logs=True,
354+
**build_kwargs,
350355
**self._get_build_cache_options(builder),
351356
)
352357
self._log_buildx(logs_itr, platform)
@@ -403,6 +408,7 @@ def build_multiple_images(
403408
inject: dict = None,
404409
cache: bool = False,
405410
pull: bool = False,
411+
secrets: Optional[List[str]] = None,
406412
) -> BuiltImageInfo:
407413
"""
408414
Builds multiple images for the given platforms. One image will be built for each platform.
@@ -498,6 +504,7 @@ def build_multiple_images(
498504
inject,
499505
cache,
500506
pull,
507+
secrets,
501508
)
502509
LOGGER.debug(f"Building {repo} for {platform}")
503510
if use_threading:

buildrunner/steprunner/tasks/build.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ def run(self, context):
238238
inject=self.to_inject,
239239
cache=not self.nocache,
240240
pull=self.pull,
241+
secrets=self.step.secrets,
241242
)
242243

243244
# Set expected number of platforms
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# In order to use secrets, you need to set use-legacy-builder to false in the config file
2+
# To run this example, you need to set the SECRET_PASSWORD environment variable
3+
# and run the example with the following command:
4+
# SECRET2=my_secret ./run-buildrunner.sh -f examples/build/secrets/buildrunner.yaml
5+
# More info about secrets: https://docs.docker.com/build/building/secrets/
6+
use-legacy-builder: false
7+
steps:
8+
simple-build-step:
9+
build:
10+
no-cache: true
11+
dockerfile: |
12+
FROM alpine:latest
13+
# Using secrets inline
14+
RUN --mount=type=secret,id=secret1 \
15+
--mount=type=secret,id=secret2 \
16+
echo Using secrets in my build - secret1 file located at /run/secrets/secret1 with contents $(cat /run/secrets/secret1) and secret2=$(cat /run/secrets/secret2)
17+
# Using secrets in environment variables
18+
RUN --mount=type=secret,id=secret1 \
19+
--mount=type=secret,id=secret2 \
20+
SECRET1_FILE=/run/secrets/secret1 \
21+
SECRET2_VARIABLE=$(cat /run/secrets/secret2) \
22+
&& echo Using secrets in my build - secret1 file located at $SECRET1_FILE with contents $(cat $SECRET1_FILE) and secret2=$SECRET2_VARIABLE
23+
secrets:
24+
# Example of a secret that is a file
25+
- id=secret1,src=examples/build/secrets/secret1.txt
26+
# Example of a secret that is an environment variable
27+
- id=secret2,env=SECRET2
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# In order to use secrets, you need to set use-legacy-builder to false in the config file OR
2+
# add platforms to the build section
3+
# To run this example, you need to set the SECRET_PASSWORD environment variable
4+
# and run the example with the following command:
5+
# SECRET2=my_secret ./run-buildrunner.sh -f examples/build/secrets/platforms-buildrunner.yaml
6+
# More info about secrets: https://docs.docker.com/build/building/secrets/
7+
steps:
8+
simple-build-step:
9+
build:
10+
dockerfile: |
11+
FROM alpine:latest
12+
# Using secrets inline
13+
RUN --mount=type=secret,id=secret1 \
14+
--mount=type=secret,id=secret2 \
15+
echo Using secrets in my build - secret1 file located at /run/secrets/secret1 with contents $(cat /run/secrets/secret1) and secret2=$(cat /run/secrets/secret2)
16+
# Using secrets in environment variables
17+
RUN --mount=type=secret,id=secret1 \
18+
--mount=type=secret,id=secret2 \
19+
SECRET1_FILE=/run/secrets/secret1 \
20+
SECRET2_VARIABLE=$(cat /run/secrets/secret2) \
21+
&& echo Using secrets in my build - secret1 file located at $SECRET1_FILE with contents $(cat $SECRET1_FILE) and secret2=$SECRET2_VARIABLE
22+
secrets:
23+
# Example of a secret that is a file
24+
- id=secret1,src=examples/build/secrets/secret1.txt
25+
# Example of a secret that is an environment variable
26+
- id=secret2,env=SECRET2
27+
platforms:
28+
- linux/amd64
29+
- linux/arm64

examples/build/secrets/secret1.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
testuser123

tests/test_buildrunner_files.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ def _get_example_runs(test_dir: str) -> List[Tuple[str, str, Optional[List[str]]
124124
excluded_example_files = [
125125
"examples/build/import/buildrunner.yaml",
126126
"examples/run/caches/buildrunner.yaml",
127+
# This file is not supported in the github actions runner
128+
"examples/build/secrets/platforms-buildrunner.yaml",
127129
]
128130

129131
# Walk through the examples directory and find all files ending with buildrunner.yaml

tests/test_multiplatform.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -478,12 +478,8 @@ def test_build_multiple_builds(
478478
load=True,
479479
file=f"{test_path}/Dockerfile",
480480
build_args={"DOCKER_REGISTRY": None},
481-
builder=None,
482-
cache=False,
483481
cache_from=None,
484482
cache_to=None,
485-
pull=False,
486-
target=None,
487483
stream_logs=True,
488484
),
489485
call(
@@ -493,12 +489,8 @@ def test_build_multiple_builds(
493489
load=True,
494490
file=f"{test_path}/Dockerfile",
495491
build_args={"DOCKER_REGISTRY": None},
496-
builder=None,
497-
cache=False,
498492
cache_from=None,
499493
cache_to=None,
500-
pull=False,
501-
target=None,
502494
stream_logs=True,
503495
),
504496
call(
@@ -508,12 +500,8 @@ def test_build_multiple_builds(
508500
load=True,
509501
file=f"{test_path}/Dockerfile",
510502
build_args={"DOCKER_REGISTRY": None},
511-
builder=None,
512-
cache=False,
513503
cache_from=None,
514504
cache_to=None,
515-
pull=False,
516-
target=None,
517505
stream_logs=True,
518506
),
519507
call(
@@ -523,12 +511,8 @@ def test_build_multiple_builds(
523511
load=True,
524512
file=f"{test_path}/Dockerfile",
525513
build_args={"DOCKER_REGISTRY": None},
526-
builder=None,
527-
cache=False,
528514
cache_from=None,
529515
cache_to=None,
530-
pull=False,
531-
target=None,
532516
stream_logs=True,
533517
),
534518
]

0 commit comments

Comments
 (0)