Esempio n. 1
0
async def parse_dockerfile(request: DockerfileInfoRequest) -> DockerfileInfo:
    wrapped_target = await Get(
        WrappedTarget,
        WrappedTargetRequest(request.address,
                             description_of_origin="<infallible>"))
    target = wrapped_target.target
    sources = await Get(
        HydratedSources,
        HydrateSourcesRequest(
            target.get(SourcesField),
            for_sources_types=(DockerImageSourceField, ),
            enable_codegen=True,
        ),
    )

    dockerfiles = sources.snapshot.files
    assert len(dockerfiles) == 1, (
        f"Internal error: Expected a single source file to Dockerfile parse request {request}, "
        f"got: {dockerfiles}.")

    result = await Get(
        ProcessResult,
        DockerfileParseRequest(
            sources.snapshot.digest,
            dockerfiles,
        ),
    )

    try:
        raw_output = result.stdout.decode("utf-8")
        outputs = json.loads(raw_output)
        assert len(outputs) == len(dockerfiles)
    except Exception as e:
        raise DockerfileInfoError(
            f"Unexpected failure to parse Dockerfiles: {', '.join(dockerfiles)}, "
            f"for the {request.address} target: {e}") from e

    info = outputs[0]
    try:
        return DockerfileInfo(
            address=request.address,
            digest=sources.snapshot.digest,
            source=info["source"],
            build_args=DockerBuildArgs.from_strings(
                *info["build_args"], duplicates_must_match=True),
            copy_source_paths=tuple(info["copy_source_paths"]),
            from_image_build_args=DockerBuildArgs.from_strings(
                *info["from_image_build_args"], duplicates_must_match=True),
            version_tags=tuple(info["version_tags"]),
        )
    except ValueError as e:
        raise DockerfileInfoError(
            f"Error while parsing {info['source']} for the {request.address} target: {e}"
        ) from e
Esempio n. 2
0
class DockerfileInfo:
    address: Address
    digest: Digest

    # Data from the parsed Dockerfile, keep in sync with
    # `dockerfile_wrapper_script.py:ParsedDockerfileInfo`:
    source: str
    build_args: DockerBuildArgs = DockerBuildArgs()
    copy_source_paths: tuple[str, ...] = ()
    from_image_build_args: DockerBuildArgs = DockerBuildArgs()
    version_tags: tuple[str, ...] = ()
Esempio n. 3
0
class DockerfileInfo:
    address: Address
    digest: Digest
    source: str
    putative_target_addresses: tuple[str, ...] = ()
    version_tags: tuple[str, ...] = ()
    build_args: DockerBuildArgs = DockerBuildArgs()
    from_image_build_arg_names: tuple[str, ...] = ()
    copy_sources: tuple[str, ...] = ()
Esempio n. 4
0
def test_create_docker_build_context() -> None:
    context = DockerBuildContext.create(
        build_args=DockerBuildArgs.from_strings("ARGNAME=value1"),
        snapshot=EMPTY_SNAPSHOT,
        build_env=DockerBuildEnvironment.create({"ENVNAME": "value2"}),
        dockerfile_info=DockerfileInfo(
            address=Address("test"),
            digest=EMPTY_DIGEST,
            source="test/Dockerfile",
            build_args=DockerBuildArgs.from_strings(),
            copy_source_paths=(),
            from_image_build_args=DockerBuildArgs.from_strings(),
            version_tags=("base latest", "stage1 1.2", "dev 2.0", "prod 2.0"),
        ),
    )
    assert list(context.build_args) == ["ARGNAME=value1"]
    assert dict(context.build_env.environment) == {"ENVNAME": "value2"}
    assert context.dockerfile == "test/Dockerfile"
    assert context.stages == ("base", "dev", "prod")
Esempio n. 5
0
async def parse_dockerfile(request: DockerfileInfoRequest) -> DockerfileInfo:
    wrapped_target = await Get(WrappedTarget, Address, request.address)
    target = wrapped_target.target
    sources = await Get(
        HydratedSources,
        HydrateSourcesRequest(
            target.get(SourcesField),
            for_sources_types=(DockerImageSourceField,),
            enable_codegen=True,
        ),
    )

    dockerfile = sources.snapshot.files[0]

    result = await Get(
        ProcessResult,
        DockerfileParseRequest(
            sources.snapshot.digest,
            (
                "version-tags,putative-targets,build-args,from-image-build-args,copy-sources",
                dockerfile,
            ),
        ),
    )

    output = result.stdout.decode("utf-8").strip().split("\n")
    (
        version_tags,
        putative_targets,
        build_args,
        from_image_build_arg_names,
        copy_sources,
    ) = split_iterable("---", output)

    try:
        return DockerfileInfo(
            address=request.address,
            digest=sources.snapshot.digest,
            source=dockerfile,
            putative_target_addresses=putative_targets,
            version_tags=version_tags,
            build_args=DockerBuildArgs.from_strings(*build_args, duplicates_must_match=True),
            from_image_build_arg_names=from_image_build_arg_names,
            copy_sources=copy_sources,
        )
    except ValueError as e:
        raise DockerfileInfoError(
            f"Error while parsing {dockerfile} for the {request.address} target: {e}"
        ) from e
Esempio n. 6
0
def test_docker_binary_build_image(docker_path: str,
                                   docker: DockerBinary) -> None:
    dockerfile = "src/test/repo/Dockerfile"
    digest = Digest(sha256().hexdigest(), 123)
    tags = (
        "test:0.1.0",
        "test:latest",
    )
    env = {"DOCKER_HOST": "tcp://127.0.0.1:1234"}
    build_request = docker.build_image(
        tags=tags,
        digest=digest,
        dockerfile=dockerfile,
        build_args=DockerBuildArgs.from_strings("arg1=2"),
        context_root="build/context",
        env=env,
        extra_args=("--pull", "--squash"),
    )

    assert build_request == Process(
        argv=(
            docker_path,
            "build",
            "--pull",
            "--squash",
            "--tag",
            tags[0],
            "--tag",
            tags[1],
            "--build-arg",
            "arg1=2",
            "--file",
            dockerfile,
            "build/context",
        ),
        env=env,
        input_digest=digest,
        cache_scope=ProcessCacheScope.PER_SESSION,
        description="",  # The description field is marked `compare=False`
    )
    assert build_request.description == "Building docker image test:0.1.0 +1 additional tag."
Esempio n. 7
0
def test_build_args(rule_runner: RuleRunner) -> None:
    rule_runner.write_files({
        "test/BUILD":
        "docker_image()",
        "test/Dockerfile":
        dedent("""\
                ARG registry
                FROM ${registry}/image:latest
                ARG OPT_A
                ARG OPT_B=default_b_value
                ENV A=${OPT_A:-A_value}
                ENV B=${OPT_B}
                """),
    })
    addr = Address("test")
    info = rule_runner.request(DockerfileInfo, [DockerfileInfoRequest(addr)])
    assert info.build_args == DockerBuildArgs.from_strings(
        "registry",
        "OPT_A",
        "OPT_B=default_b_value",
    )
Esempio n. 8
0
async def create_docker_build_context(
        request: DockerBuildContextRequest,
        docker_options: DockerOptions) -> DockerBuildContext:
    # Get all targets to include in context.
    transitive_targets = await Get(TransitiveTargets,
                                   TransitiveTargetsRequest([request.address]))
    docker_image = transitive_targets.roots[0]

    # Get all dependencies for the root target.
    root_dependencies = await Get(
        Targets, DependenciesRequest(docker_image.get(Dependencies)))

    # Get all file sources from the root dependencies. That includes any non-file sources that can
    # be "codegen"ed into a file source.
    sources_request = Get(
        SourceFiles,
        SourceFilesRequest(
            sources_fields=[
                tgt.get(SourcesField) for tgt in root_dependencies
            ],
            for_sources_types=(
                DockerContextFilesSourcesField,
                FileSourceField,
            ),
            enable_codegen=True,
        ),
    )

    embedded_pkgs_per_target_request = Get(
        FieldSetsPerTarget,
        FieldSetsPerTargetRequest(PackageFieldSet,
                                  transitive_targets.dependencies),
    )

    sources, embedded_pkgs_per_target, dockerfile_info = await MultiGet(
        sources_request,
        embedded_pkgs_per_target_request,
        Get(DockerfileInfo, DockerfileInfoRequest(docker_image.address)),
    )

    # Package binary dependencies for build context.
    embedded_pkgs = await MultiGet(
        Get(BuiltPackage, PackageFieldSet, field_set)
        for field_set in embedded_pkgs_per_target.field_sets
        # Exclude docker images, unless build_upstream_images is true.
        if request.build_upstream_images or not isinstance(
            getattr(field_set, "source", None), DockerImageSourceField))

    if request.build_upstream_images:
        images_str = ", ".join(a.tags[0] for p in embedded_pkgs
                               for a in p.artifacts
                               if isinstance(a, BuiltDockerImage))
        if images_str:
            logger.debug(f"Built upstream Docker images: {images_str}")
        else:
            logger.debug("Did not build any upstream Docker images")

    packages_str = ", ".join(a.relpath for p in embedded_pkgs
                             for a in p.artifacts if a.relpath)
    if packages_str:
        logger.debug(f"Built packages for Docker image: {packages_str}")
    else:
        logger.debug("Did not build any packages for Docker image")

    embedded_pkgs_digest = [
        built_package.digest for built_package in embedded_pkgs
    ]
    all_digests = (dockerfile_info.digest, sources.snapshot.digest,
                   *embedded_pkgs_digest)

    # Merge all digests to get the final docker build context digest.
    context_request = Get(Snapshot, MergeDigests(d for d in all_digests if d))

    # Requests for build args and env
    build_args_request = Get(DockerBuildArgs,
                             DockerBuildArgsRequest(docker_image))
    build_env_request = Get(DockerBuildEnvironment,
                            DockerBuildEnvironmentRequest(docker_image))
    context, build_args, build_env = await MultiGet(context_request,
                                                    build_args_request,
                                                    build_env_request)

    if request.build_upstream_images:
        # Update build arg values for FROM image build args.

        # Get the FROM image build args with defined values in the Dockerfile.
        dockerfile_build_args = {
            arg_name: arg_value
            for arg_name, arg_value in
            dockerfile_info.build_args.to_dict().items() if arg_value
            and arg_name in dockerfile_info.from_image_build_arg_names
        }
        # Parse the build args values into Address instances.
        from_image_addresses = await Get(
            Addresses,
            UnparsedAddressInputs(
                dockerfile_build_args.values(),
                owning_address=dockerfile_info.address,
            ),
        )
        # Map those addresses to the corresponding built image ref (tag).
        address_to_built_image_tag = {
            field_set.address: image.tags[0]
            for field_set, built in zip(embedded_pkgs_per_target.field_sets,
                                        embedded_pkgs)
            for image in built.artifacts if isinstance(image, BuiltDockerImage)
        }
        # Create the FROM image build args.
        from_image_build_args = [
            f"{arg_name}={address_to_built_image_tag[addr]}" for arg_name, addr
            in zip(dockerfile_build_args.keys(), from_image_addresses)
        ]
        # Merge all build args.
        build_args = DockerBuildArgs.from_strings(*build_args,
                                                  *from_image_build_args)

    return DockerBuildContext.create(
        build_args=build_args,
        snapshot=context,
        dockerfile_info=dockerfile_info,
        build_env=build_env,
    )