Fix a panic that could occur when context was omitted (#83)

I could have sworn the context property was required, but evidently it
isn't and we weren't handling the case when it was missing.

This PR updates things to set a default location of the current
directory if the context is absent. Some unit tests are also added.

Fixes #78.
This commit is contained in:
Bryce Lampe
2024-05-31 07:41:22 -07:00
committed by GitHub
parent 44e082a0a0
commit 4e8cf8f4ba
27 changed files with 137 additions and 66 deletions

View File

@@ -4,6 +4,7 @@
- Fixed the default value for `ACTIONS_CACHE_URL` when using GitHub action caching. (https://github.com/pulumi/pulumi-docker-build/pull/80)
- Fixed Java SDK publishing. (https://github.com/pulumi/pulumi-docker-build/pull/89)
- Fixed a panic that could occur when `context` was omitted. (https://github.com/pulumi/pulumi-docker-build/pull/83)
## 0.0.2 (2024-04-25)

View File

@@ -107,6 +107,7 @@ define example
--language $(1) \
--out ${WORKING_DIR}/examples/$(1)
cp -r ${WORKING_DIR}/examples/app ${WORKING_DIR}/examples/$(1)/app
cp ${WORKING_DIR}/examples/yaml/.dockerignore ${WORKING_DIR}/examples/$(1)/.dockerignore
endef
up::

View File

@@ -0,0 +1,2 @@
command-output
tmp

View File

@@ -239,10 +239,6 @@ COPY hello.c ./
RUN echo ""This uses an inline Dockerfile! 👍""
",
},
Context = new DockerBuild.Inputs.BuildContextArgs
{
Location = "./app",
},
});
var dockerLoad = new DockerBuild.Image("dockerLoad", new()

View File

@@ -0,0 +1,2 @@
command-output
tmp

View File

@@ -207,9 +207,6 @@ func main() {
Dockerfile: &dockerbuild.DockerfileArgs{
Inline: pulumi.String("FROM alpine\nRUN echo \"This uses an inline Dockerfile! 👍\"\n"),
},
Context: &dockerbuild.BuildContextArgs{
Location: pulumi.String("./app"),
},
})
if err != nil {
return err

View File

@@ -0,0 +1,2 @@
command-output
tmp

View File

@@ -185,9 +185,6 @@ FROM alpine
RUN echo "This uses an inline Dockerfile! 👍"
""")
.build())
.context(BuildContextArgs.builder()
.location("./app")
.build())
.build());
var dockerLoad = new Image("dockerLoad", ImageArgs.builder()

View File

@@ -0,0 +1,2 @@
command-output
tmp

View File

@@ -154,9 +154,6 @@ const inline = new docker_build.Image("inline", {
RUN echo "This uses an inline Dockerfile! 👍"
`,
},
context: {
location: "./app",
},
});
const dockerLoad = new docker_build.Image("dockerLoad", {
push: false,

View File

@@ -0,0 +1,2 @@
command-output
tmp

View File

@@ -141,9 +141,6 @@ inline = docker_build.Image("inline",
inline="""FROM alpine
RUN echo "This uses an inline Dockerfile! 👍"
""",
),
context=docker_build.BuildContextArgs(
location="./app",
))
docker_load = docker_build.Image("dockerLoad",
push=False,

View File

@@ -0,0 +1,2 @@
command-output
tmp

View File

@@ -166,8 +166,6 @@ resources:
inline: |
FROM alpine
RUN echo "This uses an inline Dockerfile! 👍"
context:
location: "./app"
# docker buildx build --load .
dockerLoad:

View File

@@ -1165,7 +1165,7 @@
},
"context": {
"$ref": "#/types/docker-build:index:BuildContext",
"description": "Build context settings.\n\nEquivalent to Docker's `PATH | URL | -` positional argument."
"description": "Build context settings. Defaults to the current directory.\n\nEquivalent to Docker's `PATH | URL | -` positional argument."
},
"contextHash": {
"type": "string",
@@ -1309,7 +1309,7 @@
},
"context": {
"$ref": "#/types/docker-build:index:BuildContext",
"description": "Build context settings.\n\nEquivalent to Docker's `PATH | URL | -` positional argument."
"description": "Build context settings. Defaults to the current directory.\n\nEquivalent to Docker's `PATH | URL | -` positional argument."
},
"dockerfile": {
"$ref": "#/types/docker-build:index:Dockerfile",

View File

@@ -26,12 +26,14 @@ import (
"os"
"path"
"path/filepath"
"slices"
"syscall"
buildx "github.com/docker/buildx/build"
"github.com/moby/patternmatcher/ignorefile"
"github.com/spf13/afero"
"github.com/tonistiigi/fsutil"
"golang.org/x/exp/maps"
"github.com/pulumi/pulumi-go-provider/infer"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
@@ -90,7 +92,9 @@ func (c *Context) Annotate(a infer.Annotator) {
// validate returns a non-nil CheckError if the Context is invalid. The
// returned Dockerfile may have defaults set to match Docker's default
// handling. The returned Dockerfile should be validated separately.
// handling. The returned Dockerfile should be validated separately. Non-nil
// values are returned even in the case of errors to allow additional
// validation to be performed.
func (bc *BuildContext) validate(preview bool, d *Dockerfile) (*Dockerfile, *Context, error) {
if d == nil {
d = &Dockerfile{}
@@ -107,6 +111,11 @@ func (bc *BuildContext) validate(preview bool, d *Dockerfile) (*Dockerfile, *Con
// a build later.
return d, c, nil
}
// If this isn't a preview but our location still isn't set, default it to
// the current directory.
if c.Location == "" {
c.Location = "."
}
if buildx.IsRemoteURL(c.Location) {
// We assume remote URLs are always valid.
@@ -225,8 +234,11 @@ func hashBuildContext(
}
}
// Hash any local named contexts.
for _, namedContext := range namedContexts {
// Hash any local named contexts. Sort keys for stable iteration order.
keys := maps.Keys(namedContexts)
slices.Sort(keys)
for _, key := range keys {
namedContext := namedContexts[key]
if isLocalDir(fs, namedContext) {
fs, err := rootFS(namedContext, excludes)
if err != nil {

View File

@@ -33,62 +33,69 @@ func TestValidateContext(t *testing.T) {
t.Parallel()
tests := []struct {
name string
c Context
c *BuildContext
givenD Dockerfile
preview bool
wantD *Dockerfile
wantC *Context
wantErr string
}{
{
name: "relative",
c: Context{
c: &BuildContext{Context: Context{
Location: "../internal/../internal/testdata/noop",
},
}},
wantD: &Dockerfile{
Location: "../internal/testdata/noop/Dockerfile",
},
},
{
name: "missing directory",
c: Context{
c: &BuildContext{Context: Context{
Location: "/does/not/exist/",
},
}},
wantErr: "not a valid directory",
},
{
name: "missing default Dockerfile",
c: Context{
c: &BuildContext{Context: Context{
Location: "testdata",
},
}},
wantD: &Dockerfile{Location: "testdata/Dockerfile"},
},
{
name: "with explicit Dockerfile",
c: Context{
c: &BuildContext{Context: Context{
Location: "testdata",
},
}},
givenD: Dockerfile{
Location: "testdata/Dockerfile.invalid",
},
},
{
name: "default location",
c: Context{},
c: &BuildContext{Context: Context{}},
wantD: &Dockerfile{Location: "Dockerfile"},
},
{
name: "remote context doesn't default to local Dockerfile",
c: Context{
c: &BuildContext{Context: Context{
Location: "https://raw.githubusercontent.com/pulumi/pulumi-docker/api-types/provider/testdata/Dockerfile",
},
}},
wantD: &Dockerfile{},
},
{
name: "preview",
c: Context{},
c: &BuildContext{Context: Context{}},
preview: true,
},
{
name: "missing context defaults to current directory",
c: nil,
wantC: &Context{Location: "."},
wantD: &Dockerfile{Location: "Dockerfile"},
},
}
for _, tt := range tests {
@@ -96,8 +103,7 @@ func TestValidateContext(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
bc := &BuildContext{Context: tt.c}
d, _, err := bc.validate(tt.preview, &tt.givenD)
d, c, err := tt.c.validate(tt.preview, &tt.givenD)
if tt.wantErr == "" {
assert.NoError(t, err)
@@ -109,6 +115,9 @@ func TestValidateContext(t *testing.T) {
assert.Equal(t, tt.wantD.Location, d.Location)
assert.Equal(t, tt.wantD.Inline, d.Inline)
}
if tt.wantC != nil {
assert.Equal(t, tt.wantC.Location, c.Location)
}
})
}
}

View File

@@ -16,8 +16,10 @@ package internal
import (
"context"
"fmt"
"path/filepath"
"sync"
"time"
"github.com/docker/buildx/builder"
"github.com/docker/buildx/store/storeutil"
@@ -72,7 +74,7 @@ func (h *host) builderFor(build Build) (*cachedBuilder, error) {
txn, release, err := storeutil.GetStore(h.cli)
if err != nil {
return nil, err
return nil, fmt.Errorf("getting store: %w", err)
}
defer release()
@@ -86,7 +88,7 @@ func (h *host) builderFor(build Build) (*cachedBuilder, error) {
builder.WithStore(txn),
)
if err != nil {
return nil, err
return nil, fmt.Errorf("new builder: %w", err)
}
// If we didn't request a particular builder, and we loaded a default
@@ -95,7 +97,7 @@ func (h *host) builderFor(build Build) (*cachedBuilder, error) {
if b.Driver == "" && opts.Builder == "" {
builders, err := builder.GetBuilders(h.cli, txn)
if err != nil {
return nil, err
return nil, fmt.Errorf("getting builders: %w", err)
}
nextbuilder:
for _, bb := range builders {
@@ -128,6 +130,7 @@ func (h *host) builderFor(build Build) (*cachedBuilder, error) {
}
if b.Driver == "" && opts.Builder == "" {
// If we STILL don't have a builder, create a docker-container instance.
b, err = builder.Create(
context.Background(),
@@ -136,7 +139,12 @@ func (h *host) builderFor(build Build) (*cachedBuilder, error) {
builder.CreateOpts{Driver: "docker-container"},
)
if err != nil {
return nil, err
return nil, fmt.Errorf("creating builder: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if _, err := b.Boot(ctx); err != nil {
return nil, fmt.Errorf("booting builder: %w", err)
}
}
@@ -145,7 +153,7 @@ func (h *host) builderFor(build Build) (*cachedBuilder, error) {
// drivers that are unknown to us.
nodes, err := b.LoadNodes(context.Background())
if err != nil && !build.ShouldExec() {
return nil, err
return nil, fmt.Errorf("loading nodes: %w", err)
}
cached := &cachedBuilder{name: b.Name, driver: b.Driver, nodes: nodes}

View File

@@ -153,7 +153,7 @@ func (ia *ImageArgs) Annotate(a infer.Annotator) {
Equivalent to Docker's "--cache-to" flag.
`))
a.Describe(&ia.Context, dedent(`
Build context settings.
Build context settings. Defaults to the current directory.
Equivalent to Docker's "PATH | URL | -" positional argument.
`))
@@ -546,6 +546,10 @@ func (ia *ImageArgs) validate(preview bool) (controllerapi.BuildOptions, error)
multierr = errors.Join(multierr, err)
}
ia.Dockerfile = dockerfile
// Set a default context if one wasn't provided.
if ia.Context == nil {
ia.Context = &BuildContext{Context: *context}
}
if err := ia.Dockerfile.validate(preview, context); err != nil {
multierr = errors.Join(multierr, err)

View File

@@ -273,6 +273,48 @@ func TestImageLifecycle(t *testing.T) {
}
},
},
{
name: "context defaults to current directory (pulumi-docker-build#78)",
client: func(t *testing.T) Client {
ctrl := gomock.NewController(t)
c := NewMockClient(ctrl)
c.EXPECT().BuildKitEnabled().Return(true, nil).AnyTimes()
c.EXPECT().Build(gomock.Any(), gomock.AssignableToTypeOf(build{})).DoAndReturn(
func(_ context.Context, b Build) (*client.SolveResponse, error) {
assert.Equal(t, "FROM alpine:latest", b.Inline())
return &client.SolveResponse{
ExporterResponse: map[string]string{"image.name": "alpine:latest"},
}, nil
},
).AnyTimes()
c.EXPECT().Delete(gomock.Any(), "inline-dockerfile").Return(nil)
return c
},
op: func(t *testing.T) integration.Operation {
return integration.Operation{
Inputs: resource.PropertyMap{
"push": resource.NewBoolProperty(false),
"tags": resource.NewArrayProperty(
[]resource.PropertyValue{
resource.NewStringProperty("inline-dockerfile"),
},
),
"buildOnPreview": resource.NewBoolProperty(true),
"dockerfile": resource.NewObjectProperty(resource.PropertyMap{
"inline": resource.NewStringProperty("FROM alpine:latest"),
}),
},
Hook: func(_, output resource.PropertyMap) {
context := output["context"]
require.NotNil(t, context)
require.True(t, context.IsObject())
location := context.ObjectValue()["location"]
require.True(t, location.IsString())
assert.Equal(t, ".", location.StringValue())
},
}
},
},
}
for _, tt := range tests {

4
sdk/dotnet/Image.cs generated
View File

@@ -557,7 +557,7 @@ namespace Pulumi.DockerBuild
public Output<ImmutableArray<Outputs.CacheTo>> CacheTo { get; private set; } = null!;
/// <summary>
/// Build context settings.
/// Build context settings. Defaults to the current directory.
///
/// Equivalent to Docker's `PATH | URL | -` positional argument.
/// </summary>
@@ -897,7 +897,7 @@ namespace Pulumi.DockerBuild
}
/// <summary>
/// Build context settings.
/// Build context settings. Defaults to the current directory.
///
/// Equivalent to Docker's `PATH | URL | -` positional argument.
/// </summary>

View File

@@ -582,7 +582,7 @@ type Image struct {
//
// Equivalent to Docker's `--cache-to` flag.
CacheTo CacheToArrayOutput `pulumi:"cacheTo"`
// Build context settings.
// Build context settings. Defaults to the current directory.
//
// Equivalent to Docker's `PATH | URL | -` positional argument.
Context BuildContextPtrOutput `pulumi:"context"`
@@ -804,7 +804,7 @@ type imageArgs struct {
//
// Equivalent to Docker's `--cache-to` flag.
CacheTo []CacheTo `pulumi:"cacheTo"`
// Build context settings.
// Build context settings. Defaults to the current directory.
//
// Equivalent to Docker's `PATH | URL | -` positional argument.
Context *BuildContext `pulumi:"context"`
@@ -952,7 +952,7 @@ type ImageArgs struct {
//
// Equivalent to Docker's `--cache-to` flag.
CacheTo CacheToArrayInput
// Build context settings.
// Build context settings. Defaults to the current directory.
//
// Equivalent to Docker's `PATH | URL | -` positional argument.
Context BuildContextPtrInput
@@ -1165,7 +1165,7 @@ func (o ImageOutput) CacheTo() CacheToArrayOutput {
return o.ApplyT(func(v *Image) CacheToArrayOutput { return v.CacheTo }).(CacheToArrayOutput)
}
// Build context settings.
// Build context settings. Defaults to the current directory.
//
// Equivalent to Docker's `PATH | URL | -` positional argument.
func (o ImageOutput) Context() BuildContextPtrOutput {

View File

@@ -582,7 +582,7 @@ type Image struct {
//
// Equivalent to Docker's `--cache-to` flag.
CacheTo pulumix.GArrayOutput[CacheTo, CacheToOutput] `pulumi:"cacheTo"`
// Build context settings.
// Build context settings. Defaults to the current directory.
//
// Equivalent to Docker's `PATH | URL | -` positional argument.
Context pulumix.GPtrOutput[BuildContext, BuildContextOutput] `pulumi:"context"`
@@ -804,7 +804,7 @@ type imageArgs struct {
//
// Equivalent to Docker's `--cache-to` flag.
CacheTo []CacheTo `pulumi:"cacheTo"`
// Build context settings.
// Build context settings. Defaults to the current directory.
//
// Equivalent to Docker's `PATH | URL | -` positional argument.
Context *BuildContext `pulumi:"context"`
@@ -952,7 +952,7 @@ type ImageArgs struct {
//
// Equivalent to Docker's `--cache-to` flag.
CacheTo pulumix.Input[[]*CacheToArgs]
// Build context settings.
// Build context settings. Defaults to the current directory.
//
// Equivalent to Docker's `PATH | URL | -` positional argument.
Context pulumix.Input[*BuildContextArgs]
@@ -1151,7 +1151,7 @@ func (o ImageOutput) CacheTo() pulumix.GArrayOutput[CacheTo, CacheToOutput] {
return pulumix.GArrayOutput[CacheTo, CacheToOutput]{OutputState: unwrapped.OutputState}
}
// Build context settings.
// Build context settings. Defaults to the current directory.
//
// Equivalent to Docker's `PATH | URL | -` positional argument.
func (o ImageOutput) Context() pulumix.GPtrOutput[BuildContext, BuildContextOutput] {

View File

@@ -771,7 +771,7 @@ public class Image extends com.pulumi.resources.CustomResource {
return Codegen.optional(this.cacheTo);
}
/**
* Build context settings.
* Build context settings. Defaults to the current directory.
*
* Equivalent to Docker&#39;s `PATH | URL | -` positional argument.
*
@@ -780,7 +780,7 @@ public class Image extends com.pulumi.resources.CustomResource {
private Output</* @Nullable */ BuildContext> context;
/**
* @return Build context settings.
* @return Build context settings. Defaults to the current directory.
*
* Equivalent to Docker&#39;s `PATH | URL | -` positional argument.
*

View File

@@ -169,7 +169,7 @@ public final class ImageArgs extends com.pulumi.resources.ResourceArgs {
}
/**
* Build context settings.
* Build context settings. Defaults to the current directory.
*
* Equivalent to Docker&#39;s `PATH | URL | -` positional argument.
*
@@ -178,7 +178,7 @@ public final class ImageArgs extends com.pulumi.resources.ResourceArgs {
private @Nullable Output<BuildContextArgs> context;
/**
* @return Build context settings.
* @return Build context settings. Defaults to the current directory.
*
* Equivalent to Docker&#39;s `PATH | URL | -` positional argument.
*
@@ -816,7 +816,7 @@ public final class ImageArgs extends com.pulumi.resources.ResourceArgs {
}
/**
* @param context Build context settings.
* @param context Build context settings. Defaults to the current directory.
*
* Equivalent to Docker&#39;s `PATH | URL | -` positional argument.
*
@@ -829,7 +829,7 @@ public final class ImageArgs extends com.pulumi.resources.ResourceArgs {
}
/**
* @param context Build context settings.
* @param context Build context settings. Defaults to the current directory.
*
* Equivalent to Docker&#39;s `PATH | URL | -` positional argument.
*

4
sdk/nodejs/image.ts generated
View File

@@ -545,7 +545,7 @@ export class Image extends pulumi.CustomResource {
*/
public readonly cacheTo!: pulumi.Output<outputs.CacheTo[] | undefined>;
/**
* Build context settings.
* Build context settings. Defaults to the current directory.
*
* Equivalent to Docker's `PATH | URL | -` positional argument.
*/
@@ -842,7 +842,7 @@ export interface ImageArgs {
*/
cacheTo?: pulumi.Input<pulumi.Input<inputs.CacheToArgs>[]>;
/**
* Build context settings.
* Build context settings. Defaults to the current directory.
*
* Equivalent to Docker's `PATH | URL | -` positional argument.
*/

View File

@@ -76,7 +76,7 @@ class ImageArgs:
:param pulumi.Input[Sequence[pulumi.Input['CacheToArgs']]] cache_to: Cache import configuration.
Equivalent to Docker's `--cache-to` flag.
:param pulumi.Input['BuildContextArgs'] context: Build context settings.
:param pulumi.Input['BuildContextArgs'] context: Build context settings. Defaults to the current directory.
Equivalent to Docker's `PATH | URL | -` positional argument.
:param pulumi.Input['DockerfileArgs'] dockerfile: Dockerfile settings.
@@ -328,7 +328,7 @@ class ImageArgs:
@pulumi.getter
def context(self) -> Optional[pulumi.Input['BuildContextArgs']]:
"""
Build context settings.
Build context settings. Defaults to the current directory.
Equivalent to Docker's `PATH | URL | -` positional argument.
"""
@@ -944,7 +944,7 @@ class Image(pulumi.CustomResource):
:param pulumi.Input[Sequence[pulumi.Input[pulumi.InputType['CacheToArgs']]]] cache_to: Cache import configuration.
Equivalent to Docker's `--cache-to` flag.
:param pulumi.Input[pulumi.InputType['BuildContextArgs']] context: Build context settings.
:param pulumi.Input[pulumi.InputType['BuildContextArgs']] context: Build context settings. Defaults to the current directory.
Equivalent to Docker's `PATH | URL | -` positional argument.
:param pulumi.Input[pulumi.InputType['DockerfileArgs']] dockerfile: Dockerfile settings.
@@ -1552,7 +1552,7 @@ class Image(pulumi.CustomResource):
@pulumi.getter
def context(self) -> pulumi.Output[Optional['outputs.BuildContext']]:
"""
Build context settings.
Build context settings. Defaults to the current directory.
Equivalent to Docker's `PATH | URL | -` positional argument.
"""