Skip to content

Commit

Permalink
docker:Image - Add flag to enable build on preview (#855)
Browse files Browse the repository at this point in the history
This pull request's intent is to allow a user to build their image
during `pulumi preview` by using the new `buildOnPreview` resource
field.

This field is added to the Image schema as a boolean and defaults to
false.

The implementation specifics are as follows, with some things for the
reviewer to weigh in on.
1. As a prerequisite, `supportPreview` is enabled and all implemented
RPD methods should handle Unknowns. This should also help address #847
and #620.
2. `ContainsUnknowns()` checks are added to the marshaler and some of
the `Check()` logic. I wasn't sure if `ContainsUnknowns()` or
`IsComputed()`should be used here; the former contains a check for the
latter.
3. Unit tests verify the new marshaling behavior.
4. When a Dockerfile is Unknown, we do not verify its location during
Preview `Check()`, instead we apply other defaults and carry on. We will
calculate the build hash on the `update` call once Unknowns are
computed.
5. When in preview mode, and buildOnPreview is false, we return all
inputs as-is in Update() and Create().
6. When an attempt is made to build on preview, but there are Unknowns
in the inputs or news, we send an error instructing the user to set
`buildOnPreview` to false.
7. An integration test is added that verifies an image builds on preview
if the `buildOnPreview` flag is set to true.
8. An integration test is added that verifies an image _fails_ to build
on preview if there are Unknown inputs and the `buildOnPreview` flag is
set to true

Fixes #540.

- Handle unknowns in Build object
- Handle unknowns in Check; skip dockerfile location finding.
- Set SupportPreview to true
- Add ContainsUnknowns() checks for build: target, stages, platform,
Dockerfile, Context; and registry: username, password, server
- Add tests for Unknowns, and tweak Unknown checks as a result of a bit
of TDD
- Add logic to imageBuild that allows for buildOnPreview
- Use Command.stdout to test unknowns
- Add a few more Unknown checks in Check()
- Add an integration test for Build On Preview
- Build SDKs

---------

Co-authored-by: Bryce Lampe <[email protected]>
  • Loading branch information
guineveresaenger and blampe authored Dec 5, 2023
1 parent 78c6fd5 commit 11599a3
Show file tree
Hide file tree
Showing 23 changed files with 630 additions and 123 deletions.
4 changes: 2 additions & 2 deletions examples/aws-container-registry/ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"@types/node": "^14.0.0"
},
"dependencies": {
"@pulumi/aws": "^6.10.0",
"@pulumi/pulumi": "^3.0.0",
"@pulumi/aws": "latest",
"@pulumi/random": "latest"
"@pulumi/random": "^4.14.0"
}
}
4 changes: 2 additions & 2 deletions examples/examples_nodejs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func TestAzureContainerRegistry(t *testing.T) {
integration.ProgramTest(t, &test)
}

func TestAwsContainerRegistry(t *testing.T) {
func TestAwsContainerRegistryNode(t *testing.T) {
region := os.Getenv("AWS_REGION")
if region == "" {
t.Skipf("Skipping test due to missing AWS_REGION environment variable")
Expand Down Expand Up @@ -147,7 +147,7 @@ func TestUnknownInputsNode(t *testing.T) {
With(integration.ProgramTestOptions{
Dir: path.Join(getCwd(t), "test-unknowns", "ts"),
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
randID, ok := stack.Outputs["randnameid"]
randID, ok := stack.Outputs["randArgument"]
assert.True(t, ok)
assert.NotEmpty(t, randID)
},
Expand Down
51 changes: 44 additions & 7 deletions examples/examples_yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package examples

import (
"bytes"
"encoding/json"
"os"
"os/exec"
Expand All @@ -40,7 +41,7 @@ func TestUnknownInputsYAML(t *testing.T) {
Quick: true,
SkipRefresh: true,
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
randID, ok := stack.Outputs["randNameId"]
randID, ok := stack.Outputs["extraArgument"]
assert.True(t, ok)
assert.NotEmpty(t, randID)
},
Expand Down Expand Up @@ -77,20 +78,37 @@ func TestSecretsYAML(t *testing.T) {
})
}

func TestBuildOnPreviewYAML(t *testing.T) {
cwd, err := os.Getwd()
if !assert.NoError(t, err) {
t.FailNow()
}
var outputBuf bytes.Buffer
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: path.Join(cwd, "test-build-on-preview", "yaml"),
SkipUpdate: true, //only run Preview
SkipExportImport: true,
Verbose: true, //we need this to verify the build output logs
AllowEmptyPreviewChanges: true,
Stdout: &outputBuf,
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
assert.Contains(t, outputBuf.String(), "Image built successfully, local id")
assert.Contains(t, outputBuf.String(), "repoDigest:")
},
})
}
func TestDockerSwarmYAML(t *testing.T) {
cwd, err := os.Getwd()
if !assert.NoError(t, err) {
t.FailNow()
}
// Temporarily make ourselves a swarm manager.
cmd := exec.Command("docker", "swarm", "init")
output, err := cmd.CombinedOutput()
require.NoError(t, err, string(output))
t.Cleanup(func() {
require.NoError(t, exec.Command("docker", "swarm", "leave", "--force").Run())
})

cwd, err := os.Getwd()
if !assert.NoError(t, err) {
t.FailNow()
}

t.Run("service", func(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: path.Join(cwd, "test-swarm", "service"),
Expand All @@ -115,3 +133,22 @@ func TestDockerSwarmYAML(t *testing.T) {
})
})
}

func TestUnknownsBuildOnPreviewWarnsYAML(t *testing.T) {
cwd, err := os.Getwd()
if !assert.NoError(t, err) {
t.FailNow()
}
var outputBuf bytes.Buffer
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: path.Join(cwd, "test-unknowns", "yaml-build-on-preview"),
SkipUpdate: true, //only run Preview
SkipExportImport: true,
Verbose: true, //we need this to verify the build output logs
AllowEmptyPreviewChanges: true,
Stderr: &outputBuf,
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
assert.Contains(t, outputBuf.String(), "Minimum inputs for build are unresolved.")
},
})
}
4 changes: 4 additions & 0 deletions examples/test-build-on-preview/yaml/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM python:3.6 AS base

FROM base AS dependencies
RUN pip install gunicorn
11 changes: 11 additions & 0 deletions examples/test-build-on-preview/yaml/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: dockerfile-build-on-preview
runtime: yaml
resources:
previewImage:
type: docker:Image
properties:
imageName: docker.io/pulumibot/build-on-preview:yaml
skipPush: true
buildOnPreview: true
outputs:
repoDigest: ${previewImage.repoDigest}
11 changes: 5 additions & 6 deletions examples/test-unknowns/ts/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import * as pulumi from "@pulumi/pulumi";
import * as docker from "@pulumi/docker";
import * as random from "@pulumi/random";
import * as command from "@pulumi/command";



const randName = new random.RandomString("random", {
length: 10,

const randArg = new command.local.Command ("arg", {
create: "echo setMyArg"
});

const img = new docker.Image("docker-565-one", {
imageName: "pulumibot/test-image:with-build-args",

build: {
args: {
"RANDOM_ARG": randName.id
"RANDOM_ARG": randArg.stdout
},
},
skipPush: true,
});

export const randnameid = randName.id
export const randArgument = randArg.stdout
7 changes: 4 additions & 3 deletions examples/test-unknowns/ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
"@types/node": "^14.0.0"
},
"dependencies": {
"@pulumi/docker": "^4.1.0",
"@pulumi/random": "^4.12.1",
"@pulumi/pulumi": "^3.0.0"
"@pulumi/command": "^0.9.2",
"@pulumi/docker": "^4.4.5",
"@pulumi/pulumi": "^3.0.0",
"@pulumi/random": "^4.14.0"
}
}
3 changes: 3 additions & 0 deletions examples/test-unknowns/yaml-build-on-preview/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM ubuntu
RUN echo "hello ubuntu"

17 changes: 17 additions & 0 deletions examples/test-unknowns/yaml-build-on-preview/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: unknowns-build-on-preview-fail-yaml
runtime: yaml
resources:
extraArg:
type: command:local:Command
properties:
create: echo extra-argument
demo-image:
type: docker:Image
properties:
imageName: docker.io/pulumibot/test-unknowns-build-on-preview-fail:yaml
skipPush: true
build:
args:
RANDOM_ARG: ${extraArg.stdout}
buildOnPreview: true

9 changes: 2 additions & 7 deletions examples/test-unknowns/yaml/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
FROM ubuntu AS base
RUN echo "base"
FROM ubuntu
RUN echo "hello ubuntu"

FROM base AS stage1
RUN echo "stage1"

FROM base AS stage2
RUN echo "stage2"
16 changes: 7 additions & 9 deletions examples/test-unknowns/yaml/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
name: dockerfile-default-context
name: dockerfile-with-unknowns-yaml
runtime: yaml
resources:
randName:
type: random:RandomString
extraArg:
type: command:local:Command
properties:
length: 8
create: echo extra-argument
demo-image:
type: docker:Image
properties:
imageName: pulumibot/test-unknowns:yaml
imageName: docker.io/pulumibot/test-unknowns:yaml
skipPush: true
build:
args:
RANDOM_ARG: ${randName.id}
options:
version: v4.1.0
RANDOM_ARG: ${extraArg.stdout}
outputs:
randNameId: ${randName.id}
extraArgument: ${extraArg.stdout}
5 changes: 5 additions & 0 deletions provider/cmd/pulumi-resource-docker/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3172,6 +3172,11 @@
"$ref": "#/types/docker:index/dockerBuild:DockerBuild",
"description": "The Docker build context"
},
"buildOnPreview": {
"type": "boolean",
"description": "A flag to build an image on preview",
"default": false
},
"imageName": {
"type": "string",
"description": "The image name, of the format repository[:tag], e.g. `docker.io/username/demo-image:v1`.\nThis reference is not unique to each build and push.For the unique manifest SHA of a pushed docker image, or the local image ID, please use `repoDigest`."
Expand Down
6 changes: 1 addition & 5 deletions provider/hybrid.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,7 @@ func (dp dockerHybridProvider) Configure(
return nil, err
}

// With one important exception: the hybrid provider cannot support preview because Create on the native
// provider is not ready to accept partial data with unknowns when called in preview mode. This limits the
// ability of the bridged provider to do best-effort processing and validation in preview. An alternate design
// would return SupportsPreview=true here but shield the native provider from it.
resp.SupportsPreview = false
resp.SupportsPreview = true
return resp, err
}

Expand Down
Loading

0 comments on commit 11599a3

Please sign in to comment.