Skip to content

Commit

Permalink
Update skaffold init --analyze to handle more builder types (#2327)
Browse files Browse the repository at this point in the history
  • Loading branch information
TadCordle authored Jul 11, 2019
1 parent 6b8cd3f commit d8bf283
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 57 deletions.
26 changes: 15 additions & 11 deletions cmd/skaffold/app/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ import (
)

var (
composeFile string
cliArtifacts []string
skipBuild bool
force bool
analyze bool
composeFile string
cliArtifacts []string
skipBuild bool
force bool
analyze bool
enableJibInit bool
)

// NewCmdInit describes the CLI command to generate a Skaffold configuration.
Expand All @@ -43,17 +44,20 @@ func NewCmdInit() *cobra.Command {
f.StringVar(&composeFile, "compose-file", "", "Initialize from a docker-compose file")
f.StringSliceVarP(&cliArtifacts, "artifact", "a", nil, "'='-delimited dockerfile/image pair to generate build artifact\n(example: --artifact=/web/Dockerfile.web=gcr.io/web-project/image)")
f.BoolVar(&analyze, "analyze", false, "Print all discoverable Dockerfiles and images in JSON format to stdout")
f.BoolVar(&enableJibInit, "XXenableJibInit", false, "")
f.MarkHidden("XXenableJibInit")
}).
NoArgs(doInit)
}

func doInit(out io.Writer) error {
return initializer.DoInit(out, initializer.Config{
ComposeFile: composeFile,
CliArtifacts: cliArtifacts,
SkipBuild: skipBuild,
Force: force,
Analyze: analyze,
Opts: opts,
ComposeFile: composeFile,
CliArtifacts: cliArtifacts,
SkipBuild: skipBuild,
Force: force,
Analyze: analyze,
EnableJibInit: enableJibInit,
Opts: opts,
})
}
15 changes: 8 additions & 7 deletions pkg/skaffold/docker/docker_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ var (
)

// Docker is the path to a dockerfile. Implements the InitBuilder interface.
type Docker string
type Docker struct {
File string `json:"path"`
}

// Name returns the name of the builder, "Docker"
func (d Docker) Name() string {
Expand All @@ -43,20 +45,19 @@ func (d Docker) Name() string {

// Describe returns the initBuilder's string representation, used when prompting the user to choose a builder.
func (d Docker) Describe() string {
return fmt.Sprintf("%s (%s)", d.Name(), d)
return fmt.Sprintf("%s (%s)", d.Name(), d.File)
}

// CreateArtifact creates an Artifact to be included in the generated Build Config
func (d Docker) CreateArtifact(manifestImage string) *latest.Artifact {
path := string(d)
workspace := filepath.Dir(path)
workspace := filepath.Dir(d.File)
a := &latest.Artifact{ImageName: manifestImage}
if workspace != "." {
a.Workspace = workspace
}
if filepath.Base(path) != constants.DefaultDockerfilePath {
if filepath.Base(d.File) != constants.DefaultDockerfilePath {
a.ArtifactType = latest.ArtifactType{
DockerArtifact: &latest.DockerArtifact{DockerfilePath: path},
DockerArtifact: &latest.DockerArtifact{DockerfilePath: d.File},
}
}

Expand All @@ -71,7 +72,7 @@ func (d Docker) ConfiguredImage() string {

// Path returns the path to the dockerfile
func (d Docker) Path() string {
return string(d)
return d.File
}

// ValidateDockerfile makes sure the given Dockerfile is existing and valid.
Expand Down
8 changes: 4 additions & 4 deletions pkg/skaffold/docker/docker_init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestDescribe(t *testing.T) {
}{
{
description: "Dockerfile prompt",
dockerfile: Docker("path/to/Dockerfile"),
dockerfile: Docker{File: "path/to/Dockerfile"},
expectedPrompt: "Docker (path/to/Dockerfile)",
},
}
Expand All @@ -96,7 +96,7 @@ func TestCreateArtifact(t *testing.T) {
}{
{
description: "default filename",
dockerfile: Docker(filepath.Join("path", "to", "Dockerfile")),
dockerfile: Docker{File: filepath.Join("path", "to", "Dockerfile")},
manifestImage: "image",
expectedArtifact: latest.Artifact{
ImageName: "image",
Expand All @@ -106,7 +106,7 @@ func TestCreateArtifact(t *testing.T) {
},
{
description: "non-default filename",
dockerfile: Docker(filepath.Join("path", "to", "Dockerfile1")),
dockerfile: Docker{File: filepath.Join("path", "to", "Dockerfile1")},
manifestImage: "image",
expectedArtifact: latest.Artifact{
ImageName: "image",
Expand All @@ -118,7 +118,7 @@ func TestCreateArtifact(t *testing.T) {
},
{
description: "ignore workspace",
dockerfile: Docker("Dockerfile"),
dockerfile: Docker{File: "Dockerfile"},
manifestImage: "image",
expectedArtifact: latest.Artifact{
ImageName: "image",
Expand Down
121 changes: 98 additions & 23 deletions pkg/skaffold/initializer/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,13 @@ type InitBuilder interface {

// Config defines the Initializer Config for Init API of skaffold.
type Config struct {
ComposeFile string
CliArtifacts []string
SkipBuild bool
Force bool
Analyze bool
Opts *config.SkaffoldOptions
ComposeFile string
CliArtifacts []string
SkipBuild bool
Force bool
Analyze bool
EnableJibInit bool // TODO: Remove this parameter
Opts *config.SkaffoldOptions
}

// builderImagePair defines a builder and the image it builds
Expand Down Expand Up @@ -114,28 +115,33 @@ func DoInit(out io.Writer, c Config) error {
return err
}
images := k.GetImages()

// Determine which builders/images require prompting
pairs, unresolvedBuilderConfigs, unresolvedImages := autoSelectBuilders(builderConfigs, images)

if c.Analyze {
return printAnalyzeJSON(out, c.SkipBuild, builderConfigs, images)
// TODO: Remove backwards compatibility block
if !c.EnableJibInit {
return printAnalyzeJSONNoJib(out, c.SkipBuild, pairs, unresolvedBuilderConfigs, unresolvedImages)
}

return printAnalyzeJSON(out, c.SkipBuild, pairs, unresolvedBuilderConfigs, unresolvedImages)
}

// conditionally generate build artifacts
var pairs []builderImagePair
if !c.SkipBuild {
if len(builderConfigs) == 0 {
return errors.New("one or more valid Dockerfiles must be present to build images with skaffold; please provide at least Dockerfile and try again or run `skaffold init --skip-build`")
}

var unresolvedImages []string
pairs, builderConfigs, unresolvedImages = autoSelectBuilders(builderConfigs, images)

if c.CliArtifacts != nil {
newPairs, err := processCliArtifacts(c.CliArtifacts)
if err != nil {
return errors.Wrap(err, "processing cli artifacts")
}
pairs = append(pairs, newPairs...)
} else {
pairs = append(pairs, resolveBuilderImages(builderConfigs, unresolvedImages)...)
pairs = append(pairs, resolveBuilderImages(unresolvedBuilderConfigs, unresolvedImages)...)
}
}

Expand Down Expand Up @@ -218,7 +224,7 @@ func autoSelectBuilders(builderConfigs []InitBuilder, images []string) ([]builde
func detectBuilders(path string) ([]InitBuilder, error) {
// Check for Dockerfile
if docker.ValidateDockerfileFunc(path) {
results := []InitBuilder{docker.Docker(path)}
results := []InitBuilder{docker.Docker{File: path}}
return results, nil
}

Expand All @@ -236,7 +242,7 @@ func processCliArtifacts(artifacts []string) ([]builderImagePair, error) {
}

pairs = append(pairs, builderImagePair{
Builder: docker.Docker(parts[0]),
Builder: docker.Docker{File: parts[0]},
ImageName: parts[1],
})
}
Expand Down Expand Up @@ -335,20 +341,89 @@ func generateSkaffoldConfig(k Initializer, buildConfigPairs []builderImagePair)
return pipelineStr, nil
}

// TODO: make more flexible for non-docker builders
func printAnalyzeJSON(out io.Writer, skipBuild bool, dockerfiles []InitBuilder, images []string) error {
if !skipBuild && len(dockerfiles) == 0 {
return errors.New("one or more valid Dockerfiles must be present to build images with skaffold; please provide at least one Dockerfile and try again or run `skaffold init --skip-build`")
func printAnalyzeJSONNoJib(out io.Writer, skipBuild bool, pairs []builderImagePair, unresolvedBuilders []InitBuilder, unresolvedImages []string) error {
if !skipBuild && len(unresolvedBuilders) == 0 {
return errors.New("one or more valid Dockerfiles must be present to build images with skaffold; please provide at least one Dockerfile and try again, or run `skaffold init --skip-build`")
}

a := struct {
Dockerfiles []string `json:"dockerfiles,omitempty"`
Images []string `json:"images,omitempty"`
}{
Images: images,
}{Images: unresolvedImages}

for _, pair := range pairs {
if pair.Builder.Name() == "Docker" {
a.Dockerfiles = append(a.Dockerfiles, pair.Builder.Path())
}
a.Images = append(a.Images, pair.ImageName)
}
for _, config := range unresolvedBuilders {
if config.Name() == "Docker" {
a.Dockerfiles = append(a.Dockerfiles, config.Path())
}
}

contents, err := json.Marshal(a)
if err != nil {
return errors.Wrap(err, "marshalling contents")
}
_, err = out.Write(contents)
return err
}

// printAnalyzeJSON takes the automatically resolved builder/image pairs, the unresolved images, and the unresolved builders, and generates
// a JSON string containing builder config information,
func printAnalyzeJSON(out io.Writer, skipBuild bool, pairs []builderImagePair, unresolvedBuilders []InitBuilder, unresolvedImages []string) error {
if !skipBuild && len(unresolvedBuilders) == 0 {
return errors.New("one or more valid Dockerfiles must be present to build images with skaffold; please provide at least one Dockerfile and try again, or run `skaffold init --skip-build`")
}

// Build JSON output. Example schema is below:
// {
// "builders":[
// {
// "name":"Docker",
// "payload":"path/to/Dockerfile"
// },
// {
// "name":"Name of Builder",
// "payload": { // Payload structure may vary depending on builder type
// "path":"path/to/builder.config",
// "targetImage":"gcr.io/project/images",
// ...
// }
// },
// ],
// "images":[
// {"name":"gcr.io/project/images", "foundMatch":"true"}, // No need to prompt for this image since its builder was automatically resolved
// {"name":"another/image", "foundMatch":"false"},
// ],
// }
//
// "builders" is the list of builder configurations, and contains a builder name and a builder-specific payload
// "images" contains an image name and a boolean that indicates whether a builder/image pair can be automatically resolved (true) or if it requires prompting (false)
type Builder struct {
Name string `json:"name,omitempty"`
Payload InitBuilder `json:"payload"`
}
type Image struct {
Name string `json:"name"`
FoundMatch bool `json:"foundMatch"`
}
a := struct {
Builders []Builder `json:"builders,omitempty"`
Images []Image `json:"images,omitempty"`
}{}

for _, pair := range pairs {
a.Builders = append(a.Builders, Builder{Name: pair.Builder.Name(), Payload: pair.Builder})
a.Images = append(a.Images, Image{Name: pair.ImageName, FoundMatch: true})
}
for _, config := range unresolvedBuilders {
a.Builders = append(a.Builders, Builder{Name: config.Name(), Payload: config})
}
a.Dockerfiles = make([]string, len(dockerfiles))
for i, dockerfile := range dockerfiles {
a.Dockerfiles[i] = dockerfile.Path()
for _, image := range unresolvedImages {
a.Images = append(a.Images, Image{Name: image, FoundMatch: false})
}

contents, err := json.Marshal(a)
Expand Down
Loading

0 comments on commit d8bf283

Please sign in to comment.