diff --git a/firestarter/cli/parser.py b/firestarter/cli/parser.py index 4b4b238f..e1e35657 100644 --- a/firestarter/cli/parser.py +++ b/firestarter/cli/parser.py @@ -20,25 +20,35 @@ def main(): arg_parser = argparse.ArgumentParser() arg_parser.add_argument('workflow', type=str, help='Name of the workflow to run') - arg_parser.add_argument('--vars', type=str, help='Variables to pass to the workflow, in inline table toml format. Example: --vars=\'vars = { var1= "value1", var2= "value2"}\'') - arg_parser.add_argument('--secrets', type=str, help='Secrets to pass to the workflow, in inline table toml format. Example: --secrets=\'secrets = { secret1= "foo", secret2= "bar"}\'') + arg_parser.add_argument('--vars', type=str, help='Variables to pass to the workflow, as an inline toml format dict. Example: --vars=\'{ var1= "value1", var2= "value2"}\'') + arg_parser.add_argument('--secrets', type=str, help='Secrets to pass to the workflow, as an inline toml format dict. Example: --secrets=\'{ secret1= "foo", secret2= "bar"}\'') + arg_parser.add_argument('--additional_build_args', type=str, help='Additional build_args to pass to the workflow, as an inline toml format dict. Example: --additional_build_args=\'{ build_arg1= "arg1", build_arg2= "arg2"}\'') arg_parser.add_argument('--config_file', type=str, help='Optional configuration file for the workflow, located in the repository') args = arg_parser.parse_args() input_vars = os.environ.get("INPUT_VARS", None) input_secrets = os.environ.get("INPUT_SECRETS", None) + input_additional_build_args = os.environ.get("INPUT_ADDITIONAL_BUILD_ARGS", None) input_config_file = os.environ.get("INPUT_CONFIG_FILE", None) vars = tomllib.loads(input_vars) if input_vars is not None else {} secrets = tomllib.loads(input_secrets) if input_secrets is not None else {} + additional_build_args =\ + tomllib.loads(input_additional_build_args) if input_additional_build_args is not None else {} config_file = input_config_file if input_config_file is not None else args.config_file if args.vars: + args.vars = f"vars = {args.vars}" vars.update(tomllib.loads(args.vars).get("vars")) logger.debug(f"Inline vars: {vars}") if args.secrets: + args.secrets = f"secrets = {args.secrets}" secrets.update(tomllib.loads(args.secrets).get("secrets")) logger.debug(f"Inline secrets: {secrets}") + if args.additional_build_args: + args.additional_build_args = f"additional_build_args = {args.additional_build_args}" + additional_build_args.update(tomllib.loads(args.additional_build_args).get("additional_build_args")) + logger.debug(f"Inline additional_build_args: {additional_build_args}") # Import the workflow module from the workflow name workflow = importlib.import_module(f"firestarter.workflows.{args.workflow}") @@ -46,6 +56,7 @@ def main(): result = workflow.run( vars=vars, secrets=secrets, + additional_build_args=additional_build_args, config_file=config_file, ) diff --git a/firestarter/common/firestarter_workflow.py b/firestarter/common/firestarter_workflow.py index 628610a1..7786efb3 100644 --- a/firestarter/common/firestarter_workflow.py +++ b/firestarter/common/firestarter_workflow.py @@ -4,6 +4,7 @@ def __init__(self, **kwargs) -> None: self._config_file = kwargs.get('config_file', None) self._vars = kwargs.get('vars', None) self._secrets = kwargs.get('secrets', None) + self._additional_build_args = kwargs.get('additional_build_args', None) self.__validate_required_vars() def __validate_required_vars(self): @@ -23,6 +24,10 @@ def config_file(self): def vars(self): return self._vars + @property + def additional_build_args(self): + return self._additional_build_args + @property def secrets(self): return self._secrets diff --git a/firestarter/tests/test_build_images_functionality.py b/firestarter/tests/test_build_images_functionality.py index 60171782..45c9a281 100644 --- a/firestarter/tests/test_build_images_functionality.py +++ b/firestarter/tests/test_build_images_functionality.py @@ -29,6 +29,7 @@ "platforms": "linux/amd64,linux/arm64", } secrets = {} +additional_build_args = {} config_file_path = f"{os.path.dirname(os.path.realpath(__file__))}/fixtures/build_images.yaml" with open(config_file_path, 'r') as config_file: @@ -57,7 +58,10 @@ def reset_builder_value() -> None: global builder builder = BuildImages( - vars=vars, secrets=secrets, config_file=config_file_path + vars=vars, + secrets=secrets, + additional_build_args=additional_build_args, + config_file=config_file_path ) diff --git a/firestarter/workflows/build_images/README.md b/firestarter/workflows/build_images/README.md index 3d1c9c6c..b58bc7fe 100644 --- a/firestarter/workflows/build_images/README.md +++ b/firestarter/workflows/build_images/README.md @@ -26,7 +26,7 @@ Beyond the configuration file which is mandatory, there are some other extra var Additionally there are some optional variables: * `container_structure_filename`: path of the [container-structure-test](https://github.com/GoogleContainerTools/container-structure-test) filename (if not set, no tests are checked) - + > Highly recommended! ⚠️ * `publish`: publish the docker image to the registry @@ -44,6 +44,21 @@ RUN --mount=type=secret,id=github_token,dst=/run/secrets/github_token \ > Remember to make sure the secret key name and secret id are the same + +## Additional build args + +In addition to those found in the configuration file, additional build args may be set using the command line argument `--additional-build-args` or via the `INPUT_ADDITIONAL_BUILD_ARGS` environment variable. These values can then be used in the Dockerfile, for example: + +```Dockerfile +ARG YOUR_BUILD_ARG +ENV YOUR_BUILD_ARG=$YOUR_BUILD_ARG + +RUN echo "The additional build arg is: $YOUR_BUILD_ARG" +``` + +> [!WARNING] +> Secrets and build args set in the configuration file will overwrite those set via the command line or environment variables. + ## Example 1. Create a repository that uses run-dagger-py (check [docs](https://github.com/prefapp/run-dagger-py/blob/main/docs/index.md) for more details). diff --git a/firestarter/workflows/build_images/__init__.py b/firestarter/workflows/build_images/__init__.py index 0f5b10da..a18eb722 100644 --- a/firestarter/workflows/build_images/__init__.py +++ b/firestarter/workflows/build_images/__init__.py @@ -4,9 +4,14 @@ logger = logging.getLogger(__name__) -def run(*, vars: dict, secrets: dict, config_file:str): +def run(*, vars: dict, secrets: dict, additional_build_args: dict, config_file:str): try: - wf = BuildImages(vars=vars, secrets=secrets, config_file=config_file) + wf = BuildImages( + vars=vars, + secrets=secrets, + additional_build_args=additional_build_args, + config_file=config_file + ) return wf.execute() except Exception as e: logger.exception("Fatal error encountered during BuildImages execution.") diff --git a/firestarter/workflows/build_images/build_images.py b/firestarter/workflows/build_images/build_images.py index 10016775..554e42bd 100644 --- a/firestarter/workflows/build_images/build_images.py +++ b/firestarter/workflows/build_images/build_images.py @@ -378,6 +378,16 @@ async def compile_images_for_all_flavors(self): f"Using these secrets for all flavors: {self.secrets.keys()}" ) + build_args_for_all_flavors = [] + for key, value in self.additional_build_args.items(): + build_args_for_all_flavors.append( + dagger.BuildArg(name=key, value=value) + ) + + logger.info( + f"Using these build_args for all flavors: {self.additional_build_args.keys()}" + ) + for flavor in self.flavors: registry, full_repo_name, build_args,\ dockerfile, extra_registries,\ @@ -416,6 +426,14 @@ async def compile_images_for_all_flavors(self): for key, value in build_args.items() ] + # Combine generic and custom build_args for this flavor + # (Order matters: the args added in the second array will + # overwrite the ones with the same name in the first array. + # In this case, args defined in the flavor configuration will + # overwrite the same args defined as command line arguments + # or environment variables) + full_build_args = build_args_for_all_flavors + build_args_list + resolved_secret_refs = self.resolve_secrets( self.config.images[flavor].secrets or {} ) @@ -433,6 +451,11 @@ async def compile_images_for_all_flavors(self): flavor_secrets.append(client.set_secret(key, value)) # Combine generic and custom secrets for this flavor + # (Order matters: the secrets added in the second array will + # overwrite the ones with the same name in the first array. + # In this case, secretss defined in the flavor configuration + # will overwrite the same secrets defined as command line + # arguments or environment variables) secrets = secrets_for_all_flavors + flavor_secrets # Set the address for the default registry @@ -471,7 +494,7 @@ async def compile_images_for_all_flavors(self): for image in registry_list: await self.compile_image_and_publish( client, - build_args_list, + full_build_args, secrets, dockerfile, image,