Skip to content

Commit c38908d

Browse files
committed
feat(manifest): add target and cache from build options
1 parent 2256074 commit c38908d

15 files changed

Lines changed: 123 additions & 27 deletions

File tree

internal/pkg/cli/svc_deploy.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package cli
66
import (
77
"errors"
88
"fmt"
9+
"github.com/aws/aws-sdk-go/aws"
910
"path/filepath"
1011
"strings"
1112

@@ -312,6 +313,8 @@ func buildArgs(name, imageTag, copilotDir string, unmarshaledManifest interface{
312313
Context: *args.Context,
313314
Args: args.Args,
314315
ImageTag: imageTag,
316+
CacheFrom: args.CacheFrom,
317+
Target: aws.StringValue(args.Target),
315318
}, nil
316319
}
317320

internal/pkg/docker/docker.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ type BuildArguments struct {
3535
ImageTag string // Required. Tag to pass to `docker build` via -t flag. Usually Git commit short ID.
3636
Dockerfile string // Required. Dockerfile to pass to `docker build` via --file flag.
3737
Context string // Optional. Build context directory to pass to `docker build`
38+
Target string // Optional. The target build stage to pass to `docker build`
39+
CacheFrom []string // Optional. Images to consider as cache sources to pass to `docker build`
3840
Args map[string]string // Optional. Build args to pass via `--build-arg` flags. Equivalent to ARG directives in dockerfile.
3941
AdditionalTags []string // Optional. Additional image tags to pass to docker.
4042
}
@@ -53,6 +55,16 @@ func (r Runner) Build(in *BuildArguments) error {
5355
args = append(args, "-t", imageName(in.URI, tag))
5456
}
5557

58+
// Add cache from options
59+
for _, imageFrom := range in.CacheFrom {
60+
args = append(args, "--cache-from", imageFrom)
61+
}
62+
63+
// Add target option
64+
if in.Target != "" {
65+
args = append(args, "--target", in.Target)
66+
}
67+
5668
// Add the "args:" override section from manifest to the docker build call
5769

5870
// Collect the keys in a slice to sort for test stability

internal/pkg/docker/docker_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ func TestBuild(t *testing.T) {
3131
context string
3232
additionalTags []string
3333
args map[string]string
34+
target string
35+
cacheFrom []string
3436
setupMocks func(controller *gomock.Controller)
3537

3638
wantedError error
@@ -103,6 +105,20 @@ func TestBuild(t *testing.T) {
103105
"mockPath/to", "-f", "mockPath/to/mockDockerfile"}).Return(nil)
104106
},
105107
},
108+
"success with options": {
109+
path: mockPath,
110+
target: "foobar",
111+
cacheFrom: []string{"foo/bar:latest", "foo/bar/baz:1.2.3"},
112+
setupMocks: func(c *gomock.Controller) {
113+
mockRunner = mocks.NewMockrunner(c)
114+
mockRunner.EXPECT().Run("docker", []string{"build",
115+
"-t", mockURI + ":" + mockTag1,
116+
"--cache-from", "foo/bar:latest",
117+
"--cache-from", "foo/bar/baz:1.2.3",
118+
"--target", "foobar",
119+
"mockPath/to", "-f", "mockPath/to/mockDockerfile"}).Return(nil)
120+
},
121+
},
106122
}
107123

108124
for name, tc := range tests {
@@ -119,6 +135,8 @@ func TestBuild(t *testing.T) {
119135
ImageTag: mockTag1,
120136
AdditionalTags: tc.additionalTags,
121137
Args: tc.args,
138+
Target: tc.target,
139+
CacheFrom: tc.cacheFrom,
122140
}
123141
got := s.Build(&buildInput)
124142

internal/pkg/manifest/testdata/backend-svc-nohealthcheck.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ name: subscribers
88
type: Backend Service
99

1010
image:
11-
# Docker build arguments. You can specify additional overrides here. Supported: dockerfile, context, args.
11+
# Docker build arguments. You can specify additional overrides here. Supported: dockerfile, context, args, target, cache_from.
1212
build: ./subscribers/Dockerfile
1313

1414
# Number of CPU units for the task.

internal/pkg/manifest/testdata/scheduled-job-fully-specified.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ name: cuteness-aggregator
99
type: Scheduled Job
1010

1111
image:
12-
# Docker build arguments. You can specify additional overrides here. Supported: dockerfile, context, args.
12+
# Docker build arguments. You can specify additional overrides here. Supported: dockerfile, context, args, target, cache_from.
1313
build: ./cuteness-aggregator/Dockerfile
1414

1515
# Number of CPU units for the task.

internal/pkg/manifest/testdata/scheduled-job-no-retries.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ name: cuteness-aggregator
99
type: Scheduled Job
1010

1111
image:
12-
# Docker build arguments. You can specify additional overrides here. Supported: dockerfile, context, args.
12+
# Docker build arguments. You can specify additional overrides here. Supported: dockerfile, context, args, target, cache_from.
1313
build: ./cuteness-aggregator/Dockerfile
1414

1515
# Number of CPU units for the task.

internal/pkg/manifest/testdata/scheduled-job-no-timeout.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ name: cuteness-aggregator
99
type: Scheduled Job
1010

1111
image:
12-
# Docker build arguments. You can specify additional overrides here. Supported: dockerfile, context, args.
12+
# Docker build arguments. You can specify additional overrides here. Supported: dockerfile, context, args, target, cache_from.
1313
build: ./cuteness-aggregator/Dockerfile
1414

1515
# Number of CPU units for the task.

internal/pkg/manifest/workload.go

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,31 +55,27 @@ func (i Image) GetLocation() string {
5555
func (i *Image) BuildConfig(rootDirectory string) *DockerBuildArgs {
5656
df := i.dockerfile()
5757
ctx := i.context()
58+
dockerfile := aws.String(filepath.Join(rootDirectory, dockerfileDefaultName))
59+
context := aws.String(rootDirectory)
60+
5861
if df != "" && ctx != "" {
59-
return &DockerBuildArgs{
60-
Dockerfile: aws.String(filepath.Join(rootDirectory, df)),
61-
Context: aws.String(filepath.Join(rootDirectory, ctx)),
62-
Args: i.args(),
63-
}
62+
dockerfile = aws.String(filepath.Join(rootDirectory, df))
63+
context = aws.String(filepath.Join(rootDirectory, ctx))
6464
}
6565
if df != "" && ctx == "" {
66-
return &DockerBuildArgs{
67-
Dockerfile: aws.String(filepath.Join(rootDirectory, df)),
68-
Context: aws.String(filepath.Join(rootDirectory, filepath.Dir(df))),
69-
Args: i.args(),
70-
}
66+
dockerfile = aws.String(filepath.Join(rootDirectory, df))
67+
context = aws.String(filepath.Join(rootDirectory, filepath.Dir(df)))
7168
}
7269
if df == "" && ctx != "" {
73-
return &DockerBuildArgs{
74-
Dockerfile: aws.String(filepath.Join(rootDirectory, ctx, dockerfileDefaultName)),
75-
Context: aws.String(filepath.Join(rootDirectory, ctx)),
76-
Args: i.args(),
77-
}
70+
dockerfile = aws.String(filepath.Join(rootDirectory, ctx, dockerfileDefaultName))
71+
context = aws.String(filepath.Join(rootDirectory, ctx))
7872
}
7973
return &DockerBuildArgs{
80-
Dockerfile: aws.String(filepath.Join(rootDirectory, dockerfileDefaultName)),
81-
Context: aws.String(rootDirectory),
74+
Dockerfile: dockerfile,
75+
Context: context,
8276
Args: i.args(),
77+
Target: i.target(),
78+
CacheFrom: i.cacheFrom(),
8379
}
8480
}
8581

@@ -111,6 +107,17 @@ func (i *Image) args() map[string]string {
111107
return i.Build.BuildArgs.Args
112108
}
113109

110+
// target returns the build target stage if it exists, otherwise nil.
111+
func (i *Image) target() *string {
112+
return i.Build.BuildArgs.Target
113+
}
114+
115+
// cacheFrom returns the cache from build section, if it exists.
116+
// Otherwise it returns an empty array.
117+
func (i *Image) cacheFrom() []string {
118+
return i.Build.BuildArgs.CacheFrom
119+
}
120+
114121
// BuildArgsOrString is a custom type which supports unmarshaling yaml which
115122
// can either be of type string or type DockerBuildArgs.
116123
type BuildArgsOrString struct {
@@ -156,10 +163,12 @@ type DockerBuildArgs struct {
156163
Context *string `yaml:"context,omitempty"`
157164
Dockerfile *string `yaml:"dockerfile,omitempty"`
158165
Args map[string]string `yaml:"args,omitempty"`
166+
Target *string `yaml:"target,omitempty"`
167+
CacheFrom []string `yaml:"cache_from,omitempty"`
159168
}
160169

161170
func (b *DockerBuildArgs) isEmpty() bool {
162-
if b.Context == nil && b.Dockerfile == nil && b.Args == nil {
171+
if b.Context == nil && b.Dockerfile == nil && b.Args == nil && b.Target == nil && b.CacheFrom == nil {
163172
return true
164173
}
165174
return false

internal/pkg/manifest/workload_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ func TestBuildArgs_UnmarshalYAML(t *testing.T) {
5656
},
5757
},
5858
},
59+
"Dockerfile with cache from and target build opts": {
60+
inContent: []byte(`build:
61+
cache_from:
62+
- foo/bar:latest
63+
- foo/bar/baz:1.2.3
64+
target: foobar`),
65+
wantedStruct: BuildArgsOrString{
66+
BuildArgs: DockerBuildArgs{
67+
Target: aws.String("foobar"),
68+
CacheFrom: []string{
69+
"foo/bar:latest",
70+
"foo/bar/baz:1.2.3",
71+
},
72+
},
73+
},
74+
},
5975
"Error if unmarshalable": {
6076
inContent: []byte(`build:
6177
badfield: OH NOES
@@ -76,6 +92,8 @@ func TestBuildArgs_UnmarshalYAML(t *testing.T) {
7692
require.Equal(t, tc.wantedStruct.BuildArgs.Context, b.Build.BuildArgs.Context)
7793
require.Equal(t, tc.wantedStruct.BuildArgs.Dockerfile, b.Build.BuildArgs.Dockerfile)
7894
require.Equal(t, tc.wantedStruct.BuildArgs.Args, b.Build.BuildArgs.Args)
95+
require.Equal(t, tc.wantedStruct.BuildArgs.Target, b.Build.BuildArgs.Target)
96+
require.Equal(t, tc.wantedStruct.BuildArgs.CacheFrom, b.Build.BuildArgs.CacheFrom)
7997
}
8098
})
8199
}
@@ -154,6 +172,26 @@ func TestBuildConfig(t *testing.T) {
154172
},
155173
},
156174
},
175+
"including build options": {
176+
inBuild: BuildArgsOrString{
177+
BuildArgs: DockerBuildArgs{
178+
Target: aws.String("foobar"),
179+
CacheFrom: []string{
180+
"foo/bar:latest",
181+
"foo/bar/baz:1.2.3",
182+
},
183+
},
184+
},
185+
wantedBuild: DockerBuildArgs{
186+
Dockerfile: aws.String(filepath.Join(mockWsRoot, "Dockerfile")),
187+
Context: aws.String(mockWsRoot),
188+
Target: aws.String("foobar"),
189+
CacheFrom: []string{
190+
"foo/bar:latest",
191+
"foo/bar/baz:1.2.3",
192+
},
193+
},
194+
},
157195
}
158196
for name, tc := range testCases {
159197
t.Run(name, func(t *testing.T) {

site/content/docs/manifest/backend-service.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,13 @@ image:
6969
build:
7070
dockerfile: path/to/dockerfile
7171
context: context/dir
72+
target: build-stage
73+
cache_from:
74+
- image:tag
7275
args:
7376
key: value
7477
```
75-
In this case, copilot will use the context directory you specified and convert the key-value pairs under args to --build-arg overrides. The equivalent docker build call will be: `$ docker build --file path/to/dockerfile --build-arg key=value context/dir`.
78+
In this case, copilot will use the context directory you specified and convert the key-value pairs under args to --build-arg overrides. The equivalent docker build call will be: `$ docker build --file path/to/dockerfile --target build-stage --cache-from image:tag --build-arg key=value context/dir`.
7679

7780
You can omit fields and Copilot will do its best to understand what you mean. For example, if you specify `context` but not `dockerfile`, Copilot will run Docker in the context directory and assume that your Dockerfile is named "Dockerfile." If you specify `dockerfile` but no `context`, Copilot assumes you want to run Docker in the directory that contains `dockerfile`.
7881

0 commit comments

Comments
 (0)