Skip to content

Commit

Permalink
'skaffold init' for Kustomize projects generates profiles for each ov…
Browse files Browse the repository at this point in the history
…erlay
  • Loading branch information
nkubala committed Jun 24, 2020
1 parent d378d35 commit 2242480
Show file tree
Hide file tree
Showing 29 changed files with 445 additions and 81 deletions.
9 changes: 6 additions & 3 deletions cmd/skaffold/app/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const maxFileSize = 1024 * 1024 * 512

var (
composeFile string
buildpacksBuilder string
defaultKustomization string
cliArtifacts []string
cliKubernetesManifests []string
skipBuild bool
Expand All @@ -42,7 +44,6 @@ var (
enableBuildpacksInit bool
enableNewInitFormat bool
enableManifestGeneration bool
buildpacksBuilder string
)

// for testing
Expand All @@ -59,8 +60,9 @@ func NewCmdInit() *cobra.Command {
f.MarkHidden("skip-deploy")
f.BoolVar(&force, "force", false, "Force the generation of the Skaffold config")
f.StringVar(&composeFile, "compose-file", "", "Initialize from a docker-compose file")
f.StringVar(&defaultKustomization, "default-kustomization", "", "Default Kustomization overlay path (others will be added as profiles)")
f.StringArrayVarP(&cliArtifacts, "artifact", "a", nil, "'='-delimited Dockerfile/image pair, or JSON string, to generate build artifact\n(example: --artifact='{\"builder\":\"Docker\",\"payload\":{\"path\":\"/web/Dockerfile.web\"},\"image\":\"gcr.io/web-project/image\"}')")
f.StringArrayVarP(&cliKubernetesManifests, "kubernetes-manifest", "k", nil, "a path or a glob pattern to kubernetes manifests (can be non-existent) to be added to the kubectl deployer (overrides detection of kubernetes manifests). Repeat the flag for multiple entries. E.g.: skaffold init -k pod.yaml -k k8s/*.yml")
f.StringArrayVarP(&cliKubernetesManifests, "kubernetes-manifest", "k", nil, "A path or a glob pattern to kubernetes manifests (can be non-existent) to be added to the kubectl deployer (overrides detection of kubernetes manifests). Repeat the flag for multiple entries. E.g.: skaffold init -k pod.yaml -k k8s/*.yml")
f.BoolVar(&analyze, "analyze", false, "Print all discoverable Dockerfiles and images in JSON format to stdout")
f.BoolVar(&enableNewInitFormat, "XXenableNewInitFormat", false, "")
f.MarkHidden("XXenableNewInitFormat")
Expand All @@ -80,7 +82,9 @@ func NewCmdInit() *cobra.Command {

func doInit(ctx context.Context, out io.Writer) error {
return initEntrypoint(ctx, out, config.Config{
BuildpacksBuilder: buildpacksBuilder,
ComposeFile: composeFile,
DefaultKustomization: defaultKustomization,
CliArtifacts: cliArtifacts,
CliKubernetesManifests: cliKubernetesManifests,
SkipBuild: skipBuild,
Expand All @@ -92,7 +96,6 @@ func doInit(ctx context.Context, out io.Writer) error {
EnableBuildpacksInit: enableBuildpacksInit,
EnableNewInitFormat: enableNewInitFormat || enableBuildpacksInit || enableJibInit,
EnableManifestGeneration: enableManifestGeneration,
BuildpacksBuilder: buildpacksBuilder,
Opts: opts,
MaxFileSize: maxFileSize,
})
Expand Down
74 changes: 59 additions & 15 deletions docs/content/en/docs/pipeline-stages/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ Skaffold auto-generates `build` and `deploy` config for supported builders and d


## Build Config Initialization
`skaffold init` currently supports build detection for two builders.
`skaffold init` currently supports build detection for two builders:

1. [Docker]({{<relref "/docs/pipeline-stages/builders/docker">}})
2. [Jib]({{<relref "/docs/pipeline-stages/builders/jib">}})

`skaffold init` will walk your project directory and look for any `Dockerfiles`
or `build.gradle/pom.xml`. Please note, `skaffold init` skips files that are larger than 500MB.

If you have multiple `Dockerfile` or `build.gradle/pom.xml` files, Skaffold will provide an option
to pair an image with one of the file.
If you have multiple `Dockerfile` or `build.gradle/pom.xml` files, Skaffold will prompt you to
pair your build config files with any images detected in your deploy configuration.

E.g. For a multi-services [microservices example](https://github.com/GoogleContainerTools/skaffold/tree/master/examples/microservices)
E.g. For an application with [two microservices] (https://github.com/GoogleContainerTools/skaffold/tree/master/examples/microservices):

```bash
skaffold init
Expand All @@ -31,15 +31,15 @@ skaffold init


{{< alert title="Note" >}}
You can choose <code>None (image not built from these sources)</code> in case none of the suggested
options are correct. <br>
You will have to manually set up build config for this artifact
You can choose <code>None (image not built from these sources)</code> if none of the suggested
options are correct, or this image is not built by any of your source code.<br>
If this image is one you want Skaffold to build, you'll need to manually set up the build configuration for this artifact.
{{</alert>}}

`skaffold` init also recognizes a maven or gradle project and will auto-suggest [`jib`]({{<relref "/docs/pipeline-stages/builders#/local#jib-maven-and-gradle">}}) builder.
Currently `jib` artifact detection is disabled by default, you can turn it on using the flag `--XXenableJibInit`.
`skaffold` init also recognizes Maven and Gradle projects, and will auto-suggest the [`jib`]({{<relref "/docs/pipeline-stages/builders#/local#jib-maven-and-gradle">}}) builder.
Currently `jib` artifact detection is disabled by default, but can be enabled using the flag `--XXenableJibInit`.

You can try it this out on example [jib project](https://github.com/GoogleContainerTools/skaffold/tree/master/examples/jib-multimodule)
You can try this out on our example [jib project](https://github.com/GoogleContainerTools/skaffold/tree/master/examples/jib-multimodule)

```bash
skaffold init --XXenableJibInit
Expand All @@ -48,11 +48,10 @@ skaffold init --XXenableJibInit
![jib-multimodule](/images/jib-multimodule-init-flow.png)


In case you want to configure build artifacts on your own, use `--skip-build` flag.

## Deploy Config Initialization
`skaffold init` currently supports only [`kubectl` deployer]({{<relref "/docs/pipeline-stages/deployers#deploying-with-kubectl" >}})
Skaffold will walk through all the `yaml` files in your project and find valid kubernetes manifest files.
`skaffold init` support bootstrapping projects set up to deploy with [`kubectl`]({{<relref "/docs/pipeline-stages/deployers#deploying-with-kubectl" >}})
or [`kustomize`]({{<relref "/docs/pipeline-stages/deployers#deploying-with-kubectl" >}}).
For projects deploying straight through `kubectl`, Skaffold will walk through all the `yaml` files in your project and find valid Kubernetes manifest files.

These files will be added to `deploy` config in `skaffold.yaml`.

Expand All @@ -64,15 +63,60 @@ deploy:
- leeroy-web/kubernetes/deployment.yaml
```

For projects deploying with `kustomize`, Skaffold will scan your project and look for `kustomization.yaml`s as well as Kubernetes manifests.
It will attempt to infer the project structure based on the recommended project structure from the Kustomize project: thus,
**it is highly recommended to match your project structure to the recommended base/ and overlay/ structure from Kustomize!**

This generally looks like this:

```
app/ <- application source code, along with build configuration
main.go
Dockerfile
...
base/ <- base deploy configuration
kustomization.yaml
deployment.yaml
overlays/ <- one or more nested directories, each with modified environment configuration
dev/
deployment.yaml
kustomization.yaml
prod/
...
```

When overlay directories are found, these will be listed in the generated Skaffold config as `paths` in the `kustomize` deploy stanza. However, it generally does not make sense to have multiple overlays applied at the same time, so **Skaffold will attempt to choose a default overlay, and put each other overlay into its own profile**. This can be specified by the user through the flag `--default-kustomization`; otherwise, Skaffold will use the following heuristic:

1) Any overlay with the name `dev`
2) If none present, the **first** overlay that isn't named `prod`

*Note: order is guaranteed, since Skaffold's directory parsing is always deterministic.*

## Init API
`skaffold init` also exposes an api which tools like IDEs can integrate with via flags.
`skaffold init` also exposes an AP:I which tools like IDEs can integrate with via flags.

This API can be used to

1. Analyze a project workspace and discover all build definitions (e.g. `Dockerfile`s) and artifacts (image names from the Kubernetes manifests) - this then provides an ability for tools to ask the user to pair the artifacts with Dockerfiles interactively.
2. Given a pairing between the image names (artifacts) and build definitions (e.g. Dockerfiles), generate Skaffold `build` config for a given artifact.

The resulting `skaffold.yaml` will look something like this:

```yaml
apiVersion: skaffold/v2beta5
...
deploy:
kustomize:
paths:
- overlays/dev
profiles:
- name: prod
deploy:
kustomize:
paths:
- overlays/prod
```

**Init API contract**

| API | flag | input/output |
Expand Down
4 changes: 3 additions & 1 deletion docs/content/en/docs/references/cli/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -663,9 +663,10 @@ Options:
-a, --artifact=[]: '='-delimited Dockerfile/image pair, or JSON string, to generate build artifact
(example: --artifact='{"builder":"Docker","payload":{"path":"/web/Dockerfile.web"},"image":"gcr.io/web-project/image"}')
--compose-file='': Initialize from a docker-compose file
--default-kustomization='': Default Kustomization overlay path (others will be added as profiles)
-f, --filename='skaffold.yaml': Path or URL to the Skaffold config file
--force=false: Force the generation of the Skaffold config
-k, --kubernetes-manifest=[]: a path or a glob pattern to kubernetes manifests (can be non-existent) to be added to the kubectl deployer (overrides detection of kubernetes manifests). Repeat the flag for multiple entries. E.g.: skaffold init -k pod.yaml -k k8s/*.yml
-k, --kubernetes-manifest=[]: A path or a glob pattern to kubernetes manifests (can be non-existent) to be added to the kubectl deployer (overrides detection of kubernetes manifests). Repeat the flag for multiple entries. E.g.: skaffold init -k pod.yaml -k k8s/*.yml
--skip-build=false: Skip generating build artifacts in Skaffold config
Usage:
Expand All @@ -680,6 +681,7 @@ Env vars:
* `SKAFFOLD_ANALYZE` (same as `--analyze`)
* `SKAFFOLD_ARTIFACT` (same as `--artifact`)
* `SKAFFOLD_COMPOSE_FILE` (same as `--compose-file`)
* `SKAFFOLD_DEFAULT_KUSTOMIZATION` (same as `--default-kustomization`)
* `SKAFFOLD_FILENAME` (same as `--filename`)
* `SKAFFOLD_FORCE` (same as `--force`)
* `SKAFFOLD_KUBERNETES_MANIFEST` (same as `--kubernetes-manifest`)
Expand Down
1 change: 0 additions & 1 deletion examples/getting-started-kustomize/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,3 @@ spec:
metadata:
labels:
app: skaffold-kustomize

Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ metadata:
labels:
app: skaffold-kustomize
spec:
replicas: 1
selector:
matchLabels:
app: skaffold-kustomize
template:
metadata:
labels:
app: skaffold-kustomize

spec:
containers:
- name: skaffold-kustomize
image: skaffold-kustomize
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- deployment.yaml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: skaffold-kustomize
labels:
env: dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

# namespace: dev
nameSuffix: -dev

patchesStrategicMerge:
- deployment.yaml

resources:
- ../../base
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: skaffold-kustomize
labels:
env: prod
spec:
replicas: 3
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

# namespace: prod
nameSuffix: -prod

patchesStrategicMerge:
- deployment.yaml

resources:
- ../../base
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: skaffold-kustomize
labels:
env: staging
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

# namespace: staging
nameSuffix: -staging

patchesStrategicMerge:
- deployment.yaml

resources:
- ../../base
10 changes: 0 additions & 10 deletions integration/examples/getting-started-kustomize/patch.yaml

This file was deleted.

18 changes: 16 additions & 2 deletions integration/examples/getting-started-kustomize/skaffold.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ metadata:
name: getting-started-kustomize
build:
artifacts:
- image: hello-world
- image: skaffold-kustomize
context: app
deploy:
kustomize: {}
kustomize:
paths:
- overlays/dev
profiles:
- name: prod
deploy:
kustomize:
paths:
- overlays/prod
- name: staging
deploy:
kustomize:
paths:
- overlays/staging
10 changes: 7 additions & 3 deletions pkg/skaffold/deploy/kustomize.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
var (
DefaultKustomizePath = "."
kustomizeFilePaths = []string{"kustomization.yaml", "kustomization.yml", "Kustomization"}
basePath = "base"
)

type patchPath struct {
Expand Down Expand Up @@ -261,7 +262,7 @@ func dependenciesForKustomization(dir string) ([]string, error) {
candidates := append(content.Bases, content.Resources...)

for _, candidate := range candidates {
// If the file doesn't exist locally, we can assume it's a remote file and
// If the file doesn't exist locally, we can assume it's a remote file and
// skip it, since we can't monitor remote files. Kustomize itself will
// handle invalid/missing files.
local, mode := pathExistsLocally(candidate, dir)
Expand Down Expand Up @@ -313,8 +314,7 @@ func dependenciesForKustomization(dir string) ([]string, error) {
// A Kustomization config must be at the root of the directory. Kustomize will
// error if more than one of these files exists so order doesn't matter.
func findKustomizationConfig(dir string) (string, error) {
candidates := []string{"kustomization.yaml", "kustomization.yml", "Kustomization"}
for _, candidate := range candidates {
for _, candidate := range kustomizeFilePaths {
if local, _ := pathExistsLocally(candidate, dir); local {
return filepath.Join(dir, candidate), nil
}
Expand Down Expand Up @@ -368,6 +368,10 @@ func buildCommandArgs(buildArgs []string, kustomizePath string) []string {
return args
}

func IsKustomizationBase(path string) bool {
return filepath.Dir(path) == basePath
}

func IsKustomizationPath(path string) bool {
filename := filepath.Base(path)
for _, candidate := range kustomizeFilePaths {
Expand Down
4 changes: 4 additions & 0 deletions pkg/skaffold/initializer/analyze/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ func (a *ProjectAnalysis) KustomizePaths() []string {
return a.kustomizeAnalyzer.kustomizePaths
}

func (a *ProjectAnalysis) KustomizeBases() []string {
return a.kustomizeAnalyzer.bases
}

func (a *ProjectAnalysis) analyzers() []analyzer {
return []analyzer{
a.kubeAnalyzer,
Expand Down
12 changes: 9 additions & 3 deletions pkg/skaffold/initializer/analyze/kustomize.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,18 @@ import (
// kustomizeAnalyzer is a Visitor during the directory analysis that finds kustomize files
type kustomizeAnalyzer struct {
directoryAnalyzer

bases []string
kustomizePaths []string
}

func (k *kustomizeAnalyzer) analyzeFile(filePath string) error {
if !schema.IsSkaffoldConfig(filePath) && deploy.IsKustomizationPath(filePath) {
k.kustomizePaths = append(k.kustomizePaths, filepath.Dir(filePath))
func (k *kustomizeAnalyzer) analyzeFile(path string) error {
switch {
case schema.IsSkaffoldConfig(path):
case deploy.IsKustomizationBase(path):
k.bases = append(k.bases, filepath.Dir(path))
case deploy.IsKustomizationPath(path):
k.kustomizePaths = append(k.kustomizePaths, filepath.Dir(path))
}
return nil
}
Loading

0 comments on commit 2242480

Please sign in to comment.