Skip to content
This repository was archived by the owner on May 4, 2021. It is now read-only.

Commit c7788a1

Browse files
authored
Add --target option to build command (#331)
* Add `--target` option to build command * Cleanup logs and error
1 parent 2c3c165 commit c7788a1

File tree

6 files changed

+94
-10
lines changed

6 files changed

+94
-10
lines changed

bin/makisu/cmd/build.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type buildCmd struct {
4646
registryConfig string
4747
destination string
4848

49+
target string
4950
buildArgs []string
5051
allowModifyFS bool
5152
commit string
@@ -103,6 +104,7 @@ func getBuildCmd() *buildCmd {
103104
buildCmd.PersistentFlags().StringVar(&buildCmd.registryConfig, "registry-config", "", "Set build-time variables")
104105
buildCmd.PersistentFlags().StringVar(&buildCmd.destination, "dest", "", "Destination of the image tar")
105106

107+
buildCmd.PersistentFlags().StringVar(&buildCmd.target, "target", "", "Set the target build stage to build.")
106108
buildCmd.PersistentFlags().StringArrayVar(&buildCmd.buildArgs, "build-arg", nil, "Argument to the dockerfile as per the spec of ARG. Format is \"--build-arg <arg>=<value>\"")
107109
buildCmd.PersistentFlags().BoolVar(&buildCmd.allowModifyFS, "modifyfs", false, "Allow makisu to modify files outside of its internal storage dir")
108110
buildCmd.PersistentFlags().StringVar(&buildCmd.commit, "commit", "implicit", "Set to explicit to only commit at steps with '#!COMMIT' annotations; Set to implicit to commit at every ADD/COPY/RUN step")
@@ -207,7 +209,7 @@ func (cmd *buildCmd) newBuildPlan(
207209

208210
// Create BuildPlan and validate it.
209211
return builder.NewBuildPlan(
210-
buildContext, imageName, replicas, cacheMgr, dockerfile, cmd.allowModifyFS, forceCommit)
212+
buildContext, imageName, replicas, cacheMgr, dockerfile, cmd.allowModifyFS, forceCommit, cmd.target)
211213
}
212214

213215
// Build image from the specified dockerfile.

lib/builder/build_plan.go

+16-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ type BuildPlan struct {
4545

4646
// stages contains the build stages defined in dockerfile.
4747
stages []*buildStage
48+
// Which stage is the target for this plan
49+
stageTarget string
4850

4951
// TODO: this is not used for now.
5052
// Aliases of stages.
@@ -63,7 +65,7 @@ type BuildPlan struct {
6365
// returns a new BuildPlan.
6466
func NewBuildPlan(
6567
ctx *context.BuildContext, target image.Name, replicas []image.Name, cacheMgr cache.Manager,
66-
parsedStages []*dockerfile.Stage, allowModifyFS, forceCommit bool) (*BuildPlan, error) {
68+
parsedStages []*dockerfile.Stage, allowModifyFS, forceCommit bool, stageTarget string) (*BuildPlan, error) {
6769

6870
plan := &BuildPlan{
6971
baseCtx: ctx,
@@ -72,6 +74,7 @@ func NewBuildPlan(
7274
replicas: replicas,
7375
cacheMgr: cacheMgr,
7476
stages: make([]*buildStage, 0),
77+
stageTarget: stageTarget,
7578
stageAliases: make(map[string]struct{}),
7679
stageIndexAliases: make(map[string]*buildStage),
7780
opts: &buildPlanOptions{
@@ -156,6 +159,13 @@ func (plan *BuildPlan) processStagesAndAliases(
156159
// scratch before executing a stage.
157160
seedCacheID = stage.nodes[len(stage.nodes)-1].CacheID()
158161
}
162+
plan.stageAliases = existingAliases
163+
164+
if plan.stageTarget != "" {
165+
if _, ok := plan.stageAliases[plan.stageTarget]; !ok {
166+
return fmt.Errorf("target stage not found in dockerfile %s", plan.stageTarget)
167+
}
168+
}
159169

160170
return nil
161171
}
@@ -188,6 +198,11 @@ func (plan *BuildPlan) Execute() (*image.DistributionManifest, error) {
188198
for k, v := range orignalEnv {
189199
os.Setenv(k, v)
190200
}
201+
202+
if plan.stageTarget != "" && currStage.alias == plan.stageTarget {
203+
log.Info("Finished building target stage")
204+
break
205+
}
191206
}
192207

193208
// Wait for cache layers to be pushed. This will make them available to

lib/builder/build_plan_test.go

+50-7
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func TestBuildPlanExecution(t *testing.T) {
4949
}
5050
stages := []*dockerfile.Stage{{from, directives}}
5151

52-
plan, err := NewBuildPlan(ctx, target, nil, cacheMgr, stages, true, false)
52+
plan, err := NewBuildPlan(ctx, target, nil, cacheMgr, stages, true, false, "")
5353
require.NoError(err)
5454

5555
manifest, err := plan.Execute()
@@ -93,7 +93,7 @@ func TestBuildPlanContextDirs(t *testing.T) {
9393
// Here we need to set the allowModifyFS to true because we copy
9494
// files across stages.
9595
// TODO(pourchet): support copy --from without relying on FS.
96-
plan, err := NewBuildPlan(ctx, target, nil, cacheMgr, stages, true, false)
96+
plan, err := NewBuildPlan(ctx, target, nil, cacheMgr, stages, true, false, "")
9797
require.NoError(err)
9898
require.Contains(plan.copyFromDirs, "stage1")
9999
require.Len(plan.copyFromDirs, 1)
@@ -108,7 +108,7 @@ func TestBuildPlanContextDirs(t *testing.T) {
108108
}
109109
stages = []*dockerfile.Stage{{from, directives}}
110110

111-
_, err = NewBuildPlan(ctx, target, nil, cacheMgr, stages, false, false)
111+
_, err = NewBuildPlan(ctx, target, nil, cacheMgr, stages, false, false, "")
112112
require.Error(err)
113113

114114
// Copy from subsequent stage.
@@ -119,7 +119,7 @@ func TestBuildPlanContextDirs(t *testing.T) {
119119
from2 = dockerfile.FromDirectiveFixture("", envImage.String(), "stage2")
120120
stages = []*dockerfile.Stage{{from1, directives1}, {from2, nil}}
121121

122-
_, err = NewBuildPlan(ctx, target, nil, cacheMgr, stages, false, false)
122+
_, err = NewBuildPlan(ctx, target, nil, cacheMgr, stages, false, false, "")
123123
require.Error(err)
124124
}
125125

@@ -142,7 +142,7 @@ func TestBuildPlanBadRun(t *testing.T) {
142142
}
143143
stages := []*dockerfile.Stage{{from, directives}}
144144

145-
plan, err := NewBuildPlan(ctx, target, nil, cacheMgr, stages, true, false)
145+
plan, err := NewBuildPlan(ctx, target, nil, cacheMgr, stages, true, false, "")
146146
require.NoError(err)
147147

148148
_, err = plan.Execute()
@@ -166,14 +166,57 @@ func TestDuplicateStageAlias(t *testing.T) {
166166
from2 := dockerfile.FromDirectiveFixture("", envImage.String(), "alias")
167167
stages := []*dockerfile.Stage{{from1, nil}, {from2, nil}}
168168

169-
_, err = NewBuildPlan(ctx, target, nil, cacheMgr, stages, false, false)
169+
_, err = NewBuildPlan(ctx, target, nil, cacheMgr, stages, false, false, "")
170170
require.Error(err)
171171

172172
// Same image different alias.
173173
from1 = dockerfile.FromDirectiveFixture("", envImage.String(), "alias1")
174174
from2 = dockerfile.FromDirectiveFixture("", envImage.String(), "alias2")
175175
stages = []*dockerfile.Stage{{from1, nil}, {from2, nil}}
176176

177-
_, err = NewBuildPlan(ctx, target, nil, cacheMgr, stages, false, false)
177+
_, err = NewBuildPlan(ctx, target, nil, cacheMgr, stages, false, false, "")
178+
require.NoError(err)
179+
}
180+
181+
func TestTargetStageMissing(t *testing.T) {
182+
require := require.New(t)
183+
184+
ctx, cleanup := context.BuildContextFixture()
185+
defer cleanup()
186+
187+
target := image.NewImageName("", "testrepo", "testtag")
188+
envImage, err := image.ParseName("scratch")
189+
require.NoError(err)
190+
191+
cacheMgr := cache.New(ctx.ImageStore, nil, registry.NoopClientFixture())
192+
193+
// Same image same alias.
194+
from1 := dockerfile.FromDirectiveFixture("", envImage.String(), "alias1")
195+
from2 := dockerfile.FromDirectiveFixture("", envImage.String(), "alias2")
196+
stages := []*dockerfile.Stage{{from1, nil}, {from2, nil}}
197+
198+
_, err = NewBuildPlan(ctx, target, nil, cacheMgr, stages, false, false, "alias3")
199+
require.Error(err)
200+
}
201+
202+
func TestTargetStageOk(t *testing.T) {
203+
require := require.New(t)
204+
205+
ctx, cleanup := context.BuildContextFixture()
206+
defer cleanup()
207+
208+
target := image.NewImageName("", "testrepo", "testtag")
209+
envImage, err := image.ParseName("scratch")
210+
require.NoError(err)
211+
212+
cacheMgr := cache.New(ctx.ImageStore, nil, registry.NoopClientFixture())
213+
214+
// Same image same alias.
215+
from1 := dockerfile.FromDirectiveFixture("", envImage.String(), "alias1")
216+
from2 := dockerfile.FromDirectiveFixture("", envImage.String(), "alias2")
217+
from3 := dockerfile.FromDirectiveFixture("", envImage.String(), "alias3")
218+
stages := []*dockerfile.Stage{{from1, nil}, {from2, nil}, {from3, nil}}
219+
220+
_, err = NewBuildPlan(ctx, target, nil, cacheMgr, stages, false, false, "alias2")
178221
require.NoError(err)
179222
}

test/python/test_build.py

+10
Original file line numberDiff line numberDiff line change
@@ -300,3 +300,13 @@ def test_build_global_arg(registry1, storage_dir):
300300
registry=registry1.addr, docker_args=docker_build_args)
301301
code, err = docker_run_image(registry1.addr, new_image)
302302
assert code == 0, err
303+
304+
def test_build_target(registry1, storage_dir):
305+
new_image = new_image_name()
306+
context_dir = os.path.join(
307+
os.getcwd(), 'testdata/build-context/target')
308+
makisu_build_image(
309+
new_image, context_dir, storage_dir,
310+
registry=registry1.addr, target="second")
311+
code, err = docker_run_image(registry1.addr, new_image)
312+
assert code == 0, err

test/python/utils.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def makisu_run_cmd(volumes, args):
108108
def makisu_build_image(
109109
new_image_tag, context_dir, storage_dir, cache_dir=None, volumes=None,
110110
docker_args=None, load=False, registry=None, replicas=None,
111-
registry_cfg=None):
111+
registry_cfg=None, target=None):
112112

113113
volumes = volumes or {}
114114
volumes[storage_dir] = storage_dir # Sandbox and file store
@@ -144,6 +144,9 @@ def makisu_build_image(
144144
if not cache_dir:
145145
args.extend(['--local-cache-ttl', '0s'])
146146

147+
if target:
148+
args.extend(['--target', target])
149+
147150
args.append('/context')
148151

149152
exit_code = makisu_run_cmd(volumes, args)
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM alpine
2+
3+
CMD echo "This is not the correct one, with target"; exit 1;
4+
5+
FROM alpine:latest as second
6+
7+
CMD echo "This is the correct one, with target"; exit 0;
8+
9+
FROM alpine:latest as third
10+
11+
CMD echo "This is not the correct one, with target"; exit 1;

0 commit comments

Comments
 (0)