diff --git a/pkg/dockerfile/dockerfile.go b/pkg/dockerfile/dockerfile.go index f41875c56..51519dc30 100644 --- a/pkg/dockerfile/dockerfile.go +++ b/pkg/dockerfile/dockerfile.go @@ -87,6 +87,75 @@ func Stages(opts *config.KanikoOptions) ([]config.KanikoStage, error) { return kanikoStages, nil } +func ParseInstructions(opts *config.KanikoOptions) ([]instructions.Stage, []instructions.ArgCommand, error) { + var err error + var d []uint8 + match, _ := regexp.MatchString("^https?://", opts.DockerfilePath) + if match { + response, e := http.Get(opts.DockerfilePath) + if e != nil { + return nil, nil, e + } + d, err = ioutil.ReadAll(response.Body) + } else { + d, err = ioutil.ReadFile(opts.DockerfilePath) + } + + if err != nil { + return nil, nil, errors.Wrap(err, fmt.Sprintf("reading dockerfile at path %s", opts.DockerfilePath)) + } + + stages, metaArgs, err := Parse(d) + if err != nil { + return nil, nil, errors.Wrap(err, "parsing dockerfile") + } + + return stages, metaArgs, nil +} + +func ResolveCrossStageInstructions(stages []instructions.Stage) map[string]string { + nameToIndex := make(map[string]string) + for i, stage := range stages { + index := strconv.Itoa(i) + if stage.Name != "" { + nameToIndex[stage.Name] = index + } + ResolveCommands(stage.Commands, nameToIndex) + } + + logrus.Debugf("Built stage name to index map: %v", nameToIndex) + return nameToIndex +} + +func MakeKanikoStages(opts *config.KanikoOptions, stages []instructions.Stage, metaArgs []instructions.ArgCommand) ([]config.KanikoStage, error) { + targetStage, err := targetStage(stages, opts.Target) + if err != nil { + return nil, errors.Wrap(err, "Error finding target stage") + } + var kanikoStages []config.KanikoStage + for index, stage := range stages { + resolvedBaseName, err := util.ResolveEnvironmentReplacement(stage.BaseName, opts.BuildArgs, false) + if err != nil { + return nil, errors.Wrap(err, "resolving base name") + } + stage.Name = resolvedBaseName + logrus.Infof("Resolved base name %s to %s", stage.BaseName, stage.Name) + kanikoStages = append(kanikoStages, config.KanikoStage{ + Stage: stage, + BaseImageIndex: baseImageIndex(index, stages), + BaseImageStoredLocally: (baseImageIndex(index, stages) != -1), + SaveStage: saveStage(index, stages), + Final: index == targetStage, + MetaArgs: metaArgs, + Index: index, + }) + if index == targetStage { + break + } + } + return kanikoStages, nil +} + // baseImageIndex returns the index of the stage the current stage is built off // returns -1 if the current stage isn't built off a previous stage func baseImageIndex(currentStage int, stages []instructions.Stage) int { @@ -221,7 +290,6 @@ func resolveStages(stages []instructions.Stage) { if val, ok := nameToIndex[strings.ToLower(c.From)]; ok { c.From = val } - } } } @@ -264,3 +332,18 @@ func saveStage(index int, stages []instructions.Stage) bool { return false } + +// TODO move it to private method or put elsewhere +func ResolveCommands(cmds []instructions.Command, stageNameToIdx map[string]string) () { + for _, cmd := range cmds { + switch c := cmd.(type) { + case *instructions.CopyCommand: + if c.From != "" { + if val, ok := stageNameToIdx[strings.ToLower(c.From)]; ok { + c.From = val + } + } + } + } + +} \ No newline at end of file diff --git a/pkg/dockerfile/dockerfile_test.go b/pkg/dockerfile/dockerfile_test.go index d903fdb52..256cd9dc4 100644 --- a/pkg/dockerfile/dockerfile_test.go +++ b/pkg/dockerfile/dockerfile_test.go @@ -25,6 +25,7 @@ import ( "github.com/GoogleContainerTools/kaniko/pkg/config" "github.com/GoogleContainerTools/kaniko/testutil" "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/sirupsen/logrus" ) func Test_Stages_ArgValueWithQuotes(t *testing.T) { @@ -364,3 +365,30 @@ func Test_baseImageIndex(t *testing.T) { }) } } + + + +func Test_ResolveStages(t *testing.T) { + in := &instructions.CopyCommand{ + SourcesAndDest: []string{ + "/var/bo", "foo.txt", + }, + From: "boo", + Chown: "", + } + ibn := &instructions.CopyCommand{ + SourcesAndDest: []string{ + "/var/bo", "foo.txt", + }, + From: "poo", + Chown: "", + } + + foo := []instructions.Command{in, ibn} + stageMap := map[string]string{"boo": "1"} + logrus.Infof("%#v", foo) + ResolveCommands(foo, stageMap) + logrus.Infof("%#v", foo) + logrus.Infof("%#v", foo[0]) + +} \ No newline at end of file diff --git a/pkg/executor/build.go b/pkg/executor/build.go index 547965898..06542d35e 100644 --- a/pkg/executor/build.go +++ b/pkg/executor/build.go @@ -78,7 +78,7 @@ type stageBuilder struct { } // newStageBuilder returns a new type stageBuilder which contains all the information required to build the stage -func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage, crossStageDeps map[int][]string, dcm map[string]string, sid map[string]string) (*stageBuilder, error) { +func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage, crossStageDeps map[int][]string, dcm map[string]string, sid map[string]string, stageNameToIdx map[string]string) (*stageBuilder, error) { sourceImage, err := util.RetrieveSourceImage(stage, opts) if err != nil { return nil, err @@ -89,7 +89,7 @@ func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage, cross return nil, err } - if err := resolveOnBuild(&stage, &imageConfig.Config); err != nil { + if err := resolveOnBuild(&stage, &imageConfig.Config, stageNameToIdx); err != nil { return nil, err } @@ -550,8 +550,25 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) { digestToCacheKey := make(map[string]string) stageIdxToDigest := make(map[string]string) + //* dani plan + // 1. parse opts - to []instruction.Stages + // 2. resolve environment - get map stageidx-->name + // 3. convert []instruction.Stages to []config.KanikoStages + // 4. pass to newStageBuilder() the map to resolve on build + //*/ + // Parse dockerfile - stages, err := dockerfile.Stages(opts) + //stages, err := dockerfile.Stages(opts) + //if err != nil { + // return nil, err + //} + instrct, metaArgs, err := dockerfile.ParseInstructions(opts) + if err != nil { + return nil, err + } + stageNameToIdx := dockerfile.ResolveCrossStageInstructions(instrct) + + stages, err := dockerfile.MakeKanikoStages(opts, instrct, metaArgs) if err != nil { return nil, err } @@ -571,7 +588,7 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) { logrus.Infof("Built cross stage deps: %v", crossStageDependencies) for index, stage := range stages { - sb, err := newStageBuilder(opts, stage, crossStageDependencies, digestToCacheKey, stageIdxToDigest) + sb, err := newStageBuilder(opts, stage, crossStageDependencies, digestToCacheKey, stageIdxToDigest, stageNameToIdx) if err != nil { return nil, err } @@ -771,7 +788,7 @@ func getHasher(snapshotMode string) (func(string) (string, error), error) { return nil, fmt.Errorf("%s is not a valid snapshot mode", snapshotMode) } -func resolveOnBuild(stage *config.KanikoStage, config *v1.Config) error { +func resolveOnBuild(stage *config.KanikoStage, config *v1.Config, stageNameToIdx map[string]string) error { if config.OnBuild == nil || len(config.OnBuild) == 0 { return nil } @@ -780,6 +797,10 @@ func resolveOnBuild(stage *config.KanikoStage, config *v1.Config) error { if err != nil { return err } + + // Iterate over commands and replace references to other stages with their index + dockerfile.ResolveCommands(cmds, stageNameToIdx) + // Append to the beginning of the commands in the stage stage.Commands = append(cmds, stage.Commands...) logrus.Infof("Executing %v build triggers", len(cmds))