Skip to content

Commit

Permalink
Merge pull request #3703 from nkubala/init-k8sgen
Browse files Browse the repository at this point in the history
Autogenerate k8s manifests in skaffold init
  • Loading branch information
nkubala authored Mar 2, 2020
2 parents 54c9796 + 42846ba commit 0a024c4
Show file tree
Hide file tree
Showing 27 changed files with 639 additions and 113 deletions.
52 changes: 28 additions & 24 deletions cmd/skaffold/app/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,18 @@ import (
const maxFileSize = 1024 * 1024 * 512

var (
composeFile string
cliArtifacts []string
cliKubernetesManifests []string
skipBuild bool
skipDeploy bool
force bool
analyze bool
enableJibInit bool
enableBuildpacksInit bool
enableNewInitFormat bool
buildpacksBuilder string
composeFile string
cliArtifacts []string
cliKubernetesManifests []string
skipBuild bool
skipDeploy bool
force bool
analyze bool
enableJibInit bool
enableBuildpacksInit bool
enableNewInitFormat bool
enableManifestGeneration bool
buildpacksBuilder string
)

// for testing
Expand Down Expand Up @@ -68,24 +69,27 @@ func NewCmdInit() *cobra.Command {
f.MarkHidden("XXenableBuildpacksInit")
f.StringVar(&buildpacksBuilder, "XXdefaultBuildpacksBuilder", "heroku/buildpacks", "")
f.MarkHidden("XXdefaultBuildpacksBuilder")
f.BoolVar(&enableManifestGeneration, "XXenableManifestGeneration", false, "")
f.MarkHidden("XXenableManifestGeneration")
}).
NoArgs(cancelWithCtrlC(context.Background(), doInit))
}

func doInit(ctx context.Context, out io.Writer) error {
return initEntrypoint(ctx, out, config.Config{
ComposeFile: composeFile,
CliArtifacts: cliArtifacts,
CliKubernetesManifests: cliKubernetesManifests,
SkipBuild: skipBuild,
SkipDeploy: skipDeploy,
Force: force,
Analyze: analyze,
EnableJibInit: enableJibInit,
EnableBuildpacksInit: enableBuildpacksInit,
EnableNewInitFormat: enableNewInitFormat || enableBuildpacksInit || enableJibInit,
BuildpacksBuilder: buildpacksBuilder,
Opts: opts,
MaxFileSize: maxFileSize,
ComposeFile: composeFile,
CliArtifacts: cliArtifacts,
CliKubernetesManifests: cliKubernetesManifests,
SkipBuild: skipBuild,
SkipDeploy: skipDeploy,
Force: force,
Analyze: analyze,
EnableJibInit: enableJibInit,
EnableBuildpacksInit: enableBuildpacksInit,
EnableNewInitFormat: enableNewInitFormat || enableBuildpacksInit || enableJibInit,
EnableManifestGeneration: enableManifestGeneration,
BuildpacksBuilder: buildpacksBuilder,
Opts: opts,
MaxFileSize: maxFileSize,
})
}
53 changes: 53 additions & 0 deletions integration/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,48 @@ func TestInitCompose(t *testing.T) {
}
}

func TestInitManifestGeneration(t *testing.T) {
if testing.Short() || RunOnGCP() {
t.Skip("skipping kind integration test")
}

tests := []struct {
name string
dir string
args []string
expectedManifestPaths []string
}{
{
name: "hello",
dir: "testdata/init/hello",
args: []string{"--XXenableManifestGeneration"},
expectedManifestPaths: []string{"deployment.yaml"},
},
// TODO(nkubala): add this back when the --force flag is fixed
// {
// name: "microservices",
// dir: "testdata/init/microservices",
// args: []string{"--XXenableManifestGeneration"},
// expectedManifestPaths: []string{"leeroy-web/deployment.yaml", "leeroy-app/deployment.yaml"},
// },
}

for _, test := range tests {
testutil.Run(t, test.name, func(t *testutil.T) {
ns, _, deleteNs := SetupNamespace(t.T)
defer deleteNs()

initArgs := append([]string{"--force"}, test.args...)
skaffold.Init(initArgs...).InDir(test.dir).WithConfig("skaffold.yaml.out").RunOrFail(t.T)

checkGeneratedManifests(t, test.dir, test.expectedManifestPaths)

// Make sure the skaffold yaml and the kubernetes manifests created by kompose are ok
skaffold.Run().InDir(test.dir).WithConfig("skaffold.yaml.out").InNs(ns.Name).RunOrFail(t.T)
})
}
}

func checkGeneratedConfig(t *testutil.T, dir string) {
expectedOutput, err := ioutil.ReadFile(filepath.Join(dir, "skaffold.yaml"))
t.CheckNoError(err)
Expand All @@ -65,3 +107,14 @@ func checkGeneratedConfig(t *testutil.T, dir string) {
t.CheckNoError(err)
t.CheckDeepEqual(string(expectedOutput), string(output))
}

func checkGeneratedManifests(t *testutil.T, dir string, manifestPaths []string) {
for _, path := range manifestPaths {
expectedOutput, err := ioutil.ReadFile(filepath.Join(dir, path+".expected"))
t.CheckNoError(err)

output, err := ioutil.ReadFile(filepath.Join(dir, path))
t.CheckNoError(err)
t.CheckDeepEqual(string(expectedOutput), string(output))
}
}
3 changes: 2 additions & 1 deletion integration/testdata/init/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
skaffold.yaml.out
**/deployment.yaml
**/skaffold.yaml.out
7 changes: 7 additions & 0 deletions integration/testdata/init/hello/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM golang:1.12.9-alpine3.10 as builder
COPY hello.go .
RUN go build -o /app hello.go

FROM alpine:3.10
CMD ["./app"]
COPY --from=builder /app .
30 changes: 30 additions & 0 deletions integration/testdata/init/hello/deployment.yaml.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
apiVersion: v1
kind: Service
metadata:
name: dockerfile-image
labels:
app: dockerfile-image
spec:
clusterIP: None
selector:
app: dockerfile-image
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dockerfile-image
labels:
app: dockerfile-image
spec:
replicas: 1
selector:
matchLabels:
app: dockerfile-image
template:
metadata:
labels:
app: dockerfile-image
spec:
containers:
- name: dockerfile-image
image: dockerfile-image
14 changes: 14 additions & 0 deletions integration/testdata/init/hello/hello.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package main

import (
"fmt"
"time"
)

func main() {
for {
fmt.Println("Hello world!")

time.Sleep(time.Second * 1)
}
}
7 changes: 7 additions & 0 deletions integration/testdata/init/microservices/leeroy-app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM golang:1.12.9-alpine3.10 as builder
COPY app.go .
RUN go build -o /app .

FROM alpine:3.10
CMD ["./app"]
COPY --from=builder /app .
17 changes: 17 additions & 0 deletions integration/testdata/init/microservices/leeroy-app/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import (
"fmt"
"log"
"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "leeroooooy app!!\n")
}

func main() {
log.Print("leeroy app server ready")
http.HandleFunc("/", handler)
http.ListenAndServe(":50051", nil)
}
7 changes: 7 additions & 0 deletions integration/testdata/init/microservices/leeroy-web/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM golang:1.12.9-alpine3.10 as builder
COPY web.go .
RUN go build -o /web .

FROM alpine:3.10
CMD ["./web"]
COPY --from=builder /web .
25 changes: 25 additions & 0 deletions integration/testdata/init/microservices/leeroy-web/web.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"io"
"net/http"

"log"
)

func handler(w http.ResponseWriter, r *http.Request) {
resp, err := http.Get("http://leeroy-app:50051")
if err != nil {
panic(err)
}
defer resp.Body.Close()
if _, err := io.Copy(w, resp.Body); err != nil {
panic(err)
}
}

func main() {
log.Print("leeroy web server ready")
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
20 changes: 16 additions & 4 deletions pkg/skaffold/initializer/build/builders.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,23 @@ type BuilderImagePair struct {
ImageName string
}

// GeneratedBuilderImagePair pairs a discovered builder with a
// generated image name, and the path to the manifest that should be generated
type GeneratedBuilderImagePair struct {
BuilderImagePair
ManifestPath string
}

type Initializer interface {
// ProcessImages is the entrypoint call, and handles the pairing of all builders
// contained in the initializer with the provided images from the deploy initializer
ProcessImages([]string) error
// BuildConfig returns the processed build config to be written to the skaffold.yaml
BuildConfig() latest.BuildConfig
BuilderImagePairs() []BuilderImagePair
// PrintAnalysis writes the project analysis to the provided out stream
PrintAnalysis(io.Writer) error
// GenerateManifests generates image names and manifests for all unresolved pairs
GenerateManifests() (map[GeneratedBuilderImagePair][]byte, error)
}

type emptyBuildInitializer struct {
Expand All @@ -73,12 +85,12 @@ func (e *emptyBuildInitializer) BuildConfig() latest.BuildConfig {
return latest.BuildConfig{}
}

func (e *emptyBuildInitializer) BuilderImagePairs() []BuilderImagePair {
func (e *emptyBuildInitializer) PrintAnalysis(io.Writer) error {
return nil
}

func (e *emptyBuildInitializer) PrintAnalysis(io.Writer) error {
return nil
func (e *emptyBuildInitializer) GenerateManifests() (map[GeneratedBuilderImagePair][]byte, error) {
return nil, nil
}

func NewInitializer(builders []InitBuilder, c config.Config) Initializer {
Expand Down
49 changes: 39 additions & 10 deletions pkg/skaffold/initializer/build/builders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,26 @@ package build
import (
"testing"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/initializer/prompt"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/warnings"
"github.com/google/go-cmp/cmp"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/buildpacks"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/jib"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/initializer/prompt"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/warnings"
"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestResolveBuilderImages(t *testing.T) {
tests := []struct {
description string
buildConfigs []InitBuilder
images []string
force bool
shouldMakeChoice bool
shouldErr bool
expectedPairs []BuilderImagePair
description string
buildConfigs []InitBuilder
images []string
force bool
shouldMakeChoice bool
shouldErr bool
expectedPairs []BuilderImagePair
expectedGeneratedPairs []GeneratedBuilderImagePair
}{
{
description: "nothing to choose from",
Expand Down Expand Up @@ -72,6 +74,15 @@ func TestResolveBuilderImages(t *testing.T) {
ImageName: "image2",
},
},
expectedGeneratedPairs: []GeneratedBuilderImagePair{
{
BuilderImagePair: BuilderImagePair{
Builder: jib.ArtifactConfig{BuilderName: "Jib Maven Plugin", File: "pom.xml", Project: "project"},
ImageName: "pom.xml-image",
},
ManifestPath: "deployment.yaml",
},
},
},
{
description: "successful force",
Expand All @@ -94,6 +105,23 @@ func TestResolveBuilderImages(t *testing.T) {
force: true,
shouldErr: true,
},
{
description: "one unresolved image",
buildConfigs: []InitBuilder{docker.ArtifactConfig{File: "foo"}},
images: []string{},
expectedGeneratedPairs: []GeneratedBuilderImagePair{
{
BuilderImagePair: BuilderImagePair{
Builder: docker.ArtifactConfig{File: "foo"},
ImageName: "foo-image",
},
ManifestPath: "deployment.yaml",
},
},
shouldMakeChoice: false,
force: false,
shouldErr: false,
},
}
for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
Expand All @@ -111,7 +139,8 @@ func TestResolveBuilderImages(t *testing.T) {
unresolvedImages: test.images,
}
err := initializer.resolveBuilderImages()
t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expectedPairs, initializer.BuilderImagePairs())
t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expectedPairs, initializer.builderImagePairs, cmp.AllowUnexported())
t.CheckDeepEqual(test.expectedGeneratedPairs, initializer.generatedBuilderImagePairs, cmp.AllowUnexported())
})
}
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/skaffold/initializer/build/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ func (c *cliBuildInitializer) BuildConfig() latest.BuildConfig {
}
}

func (c *cliBuildInitializer) BuilderImagePairs() []BuilderImagePair {
return c.builderImagePairs
}

func (c *cliBuildInitializer) PrintAnalysis(out io.Writer) error {
return printAnalysis(out, c.enableNewFormat, c.skipBuild, c.builderImagePairs, c.builders, nil)
}

func (c *cliBuildInitializer) GenerateManifests() (map[GeneratedBuilderImagePair][]byte, error) {
return nil, nil
}

func (c *cliBuildInitializer) processCliArtifacts() error {
pairs, err := processCliArtifacts(c.cliArtifacts)
if err != nil {
Expand Down
Loading

0 comments on commit 0a024c4

Please sign in to comment.