Skip to content

Commit

Permalink
Merge pull request #1999 from priyawadhwa/custom
Browse files Browse the repository at this point in the history
Add custom artifact for custom local builds
  • Loading branch information
priyawadhwa authored May 1, 2019
2 parents aafb313 + c3339ce commit 4a4d696
Show file tree
Hide file tree
Showing 23 changed files with 693 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
out/
examples/bazel/bazel-*
integration/examples/bazel/bazel-*
integration/testdata/custom/bazel-*
integration/testdata/plugin/local/bazel/bazel-*
*.new
*.iml
Expand Down
67 changes: 67 additions & 0 deletions docs/content/en/docs/how-tos/builders/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Skaffold supports the following tools to build your image:
* [Bazel](https://bazel.build/) locally
* [Jib](https://github.com/GoogleContainerTools/jib) Maven and Gradle projects locally
* [Jib](https://github.com/GoogleContainerTools/jib) remotely with [Google Cloud Build](https://cloud.google.com/cloud-build/docs/)
* Custom build script run locally

The `build` section in the Skaffold configuration file, `skaffold.yaml`,
controls how artifacts are built. To use a specific tool for building
Expand Down Expand Up @@ -208,3 +209,69 @@ The following `build` section instructs Skaffold to build a
Docker image `gcr.io/k8s-skaffold/example` with Bazel:

{{% readfile file="samples/builders/bazel.yaml" %}}

## Custom Build Script Run Locally

Custom build scripts allow skaffold users the flexibility to build artifacts with any builder they desire.
Users can write a custom build script which must abide by the following contract for skaffold to work as expected:

### Contract between Skaffold and Custom Build Script

Skaffold will pass in the following environment variables to the custom build script:

| Environment Variable | Description | Expectation |
| ------------- |-------------| -----|
| $IMAGES | An array of fully qualified image names, separated by spaces. For example, "gcr.io/image1 gcr.io/image2" | The custom build script is expected to build an image and tag it with each image name in $IMAGES. Each image should also be pushed if `$PUSH_IMAGE=true`. |
| $PUSH_IMAGE | Set to true if each image in `$IMAGES` is expected to exist in a remote registry. Set to false if each image in `$IMAGES` is expected to exist locally. | The custom build script will push each image in `$IMAGES` if `$PUSH_IMAGE=true` |
| $BUILD_CONTEXT | An absolute path to the directory this artifact is meant to be built from. Specified by artifact `context` in the skaffold.yaml. | None. |
| Local environment variables | The current state of the local environment (e.g. `$HOST`, `$PATH)`. Determined by the golang [os.Environ](https://golang.org/pkg/os/#Environ) function.| None. |

As described above, the custom build script is expected to:

1. Build and tag each image in `$IMAGES`
2. Push each image in `$IMAGES` if `$PUSH_IMAGE=true`

Once the build script has finished executing, skaffold will try to obtain the digest of the newly built image from a remote registry (if `$PUSH_IMAGE=true`) or the local daemon (if `$PUSH_IMAGE=false`).
If skaffold fails to obtain the digest, it will error out.

#### Additional Environment Variables

Skaffold will pass in the following additional environment variables for the following builders:

##### Local builder
| Environment Variable | Description | Expectation |
| ------------- |-------------| -----|
| Docker daemon environment variables | Inform the custom builder of which docker daemon endpoint we are using. Allows custom build scripts to work with tools like Minikube. For Minikube, this is the output of `minikube docker-env`.| None. |


### Configuration

To use a custom build script, add a `custom` field to each corresponding artifact in the `build` section of the skaffold.yaml.
Currently, this only works with the build type `local`. Supported schema for `custom` includes:


{{< schema root="CustomArtifact" >}}


`buildCommand` is *required* and points skaffold to the custom build script which will be executed to build the artifact.
`dependencies` tells the skaffold file watcher which files should be watched to trigger rebuilds and file syncs. Supported schema for `dependencies` includes:


{{< schema root="CustomDependencies" >}}

#### Custom Build Scripts and File Sync
Syncable files must be included in both the `paths` section of `dependencies`, so that the skaffold file watcher knows to watch them, and the `sync` section, so that skaffold knows to sync them.

#### Custom Build Scripts and Logging
STDOUT and STDERR from the custom build script will be redirected and displayed within skaffold logs.


### Example

The following `build` section instructs Skaffold to build an image `gcr.io/k8s-skaffold/example` with a custom build script `build.sh`:

{{% readfile file="samples/builders/custom.yaml" %}}

A sample `build.sh` file, which builds an image with bazel and docker:

{{% readfile file="samples/builders/build.sh" %}}
20 changes: 20 additions & 0 deletions docs/content/en/samples/builders/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash

bazel build //:skaffold_example.tar
TAR_PATH=$(bazel info bazel-bin)
docker load -i $TAR_PATH/skaffold_example.tar


images=$(echo $IMAGES | tr " " "\n")

for image in $images
do
docker tag bazel:skaffold_example $image

if $PUSH_IMAGE
then
docker push $image
fi

done

10 changes: 10 additions & 0 deletions docs/content/en/samples/builders/custom.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
build:
artifacts:
- image: gcr.io/k8s-skaffold/example
custom:
buildCommand: ./build.sh
dependencies:
paths:
- .
ignore:
- README*
92 changes: 92 additions & 0 deletions docs/content/en/schemas/v1beta10.json
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,48 @@
"kaniko"
],
"additionalProperties": false
},
{
"properties": {
"context": {
"type": "string",
"description": "directory containing the artifact's sources.",
"x-intellij-html-description": "directory containing the artifact's sources.",
"default": "."
},
"custom": {
"$ref": "#/definitions/CustomArtifact",
"description": "*alpha* builds images using a custom build script written by the user.",
"x-intellij-html-description": "<em>alpha</em> builds images using a custom build script written by the user."
},
"image": {
"type": "string",
"description": "name of the image to be built.",
"x-intellij-html-description": "name of the image to be built.",
"examples": [
"gcr.io/k8s-skaffold/example"
]
},
"sync": {
"additionalProperties": {
"type": "string"
},
"type": "object",
"description": "*alpha* local files synced to pods instead of triggering an image build when modified. This is a mapping of local files to sync to remote folders.",
"x-intellij-html-description": "<em>alpha</em> local files synced to pods instead of triggering an image build when modified. This is a mapping of local files to sync to remote folders.",
"default": "{}",
"examples": [
"{\"*.py\": \".\", \"css/**/*.css\": \"app/css\"}"
]
}
},
"preferredOrder": [
"image",
"context",
"sync",
"custom"
],
"additionalProperties": false
}
],
"description": "items that need to be built, along with the context in which they should be built.",
Expand Down Expand Up @@ -530,6 +572,56 @@
"description": "*beta* describes how to do an on-cluster build.",
"x-intellij-html-description": "<em>beta</em> describes how to do an on-cluster build."
},
"CustomArtifact": {
"properties": {
"buildCommand": {
"type": "string",
"description": "command executed to build the image.",
"x-intellij-html-description": "command executed to build the image."
},
"dependencies": {
"$ref": "#/definitions/CustomDependencies",
"description": "file dependencies that skaffold should watch for both rebuilding and file syncing for this artifact.",
"x-intellij-html-description": "file dependencies that skaffold should watch for both rebuilding and file syncing for this artifact."
}
},
"preferredOrder": [
"buildCommand",
"dependencies"
],
"additionalProperties": false,
"description": "*alpha* describes an artifact built from a custom build script written by the user. It can be used to build images with builders that aren't directly integrated with skaffold.",
"x-intellij-html-description": "<em>alpha</em> describes an artifact built from a custom build script written by the user. It can be used to build images with builders that aren't directly integrated with skaffold."
},
"CustomDependencies": {
"properties": {
"ignore": {
"items": {
"type": "string"
},
"type": "array",
"description": "specifies the paths that should be ignored by skaffold's file watcher. If a file exists in both `paths` and in `ignore`, it will be ignored, and will be excluded from both rebuilds and file synchronization. Will only work in conjunction with `paths`.",
"x-intellij-html-description": "specifies the paths that should be ignored by skaffold's file watcher. If a file exists in both <code>paths</code> and in <code>ignore</code>, it will be ignored, and will be excluded from both rebuilds and file synchronization. Will only work in conjunction with <code>paths</code>.",
"default": "[]"
},
"paths": {
"items": {
"type": "string"
},
"type": "array",
"description": "should be set to the file dependencies for this artifact, so that the skaffold file watcher knows when to rebuild and perform file synchronization.",
"x-intellij-html-description": "should be set to the file dependencies for this artifact, so that the skaffold file watcher knows when to rebuild and perform file synchronization.",
"default": "[]"
}
},
"preferredOrder": [
"paths",
"ignore"
],
"additionalProperties": false,
"description": "*alpha* used to specify dependencies for an artifact built by a custom build script.",
"x-intellij-html-description": "<em>alpha</em> used to specify dependencies for an artifact built by a custom build script."
},
"DateTimeTagger": {
"properties": {
"format": {
Expand Down
2 changes: 1 addition & 1 deletion hack/boilerplate/boilerplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@


SKIPPED_DIRS = ["Godeps", "third_party", ".git", "vendor", "examples", "testdata", "node_modules"]
SKIPPED_FILES = ["install_golint.sh", "skaffold.pb.go", "skaffold.pb.gw.go"]
SKIPPED_FILES = ["install_golint.sh", "skaffold.pb.go", "skaffold.pb.gw.go", "build.sh"]

parser = argparse.ArgumentParser()
parser.add_argument("filenames", help="list of files to check, all files if unspecified", nargs='*')
Expand Down
4 changes: 4 additions & 0 deletions integration/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ func TestRun(t *testing.T) {
args: []string{"-p", "gcb"},
deployments: []string{"web"},
remoteOnly: true,
}, {
description: "custom builder",
dir: "testdata/custom",
pods: []string{"bazel"},
},
}

Expand Down
9 changes: 9 additions & 0 deletions integration/testdata/custom/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
load("@io_bazel_rules_docker//go:image.bzl", "go_image")

go_image(
name = "skaffold_example",
srcs = ["main.go"],
goos = "linux",
goarch = "amd64",
static = "on",
)
30 changes: 30 additions & 0 deletions integration/testdata/custom/WORKSPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
workspace(name = "skaffold")

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
name = "io_bazel_rules_docker",
strip_prefix = "rules_docker-0.7.0",
urls = ["https://github.com/bazelbuild/rules_docker/archive/v0.7.0.tar.gz"],
sha256 = "aed1c249d4ec8f703edddf35cbe9dfaca0b5f5ea6e4cd9e83e99f3b0d1136c3d",
)

http_archive(
name = "io_bazel_rules_go",
urls = ["https://github.com/bazelbuild/rules_go/releases/download/0.16.5/rules_go-0.16.5.tar.gz"],
sha256 = "7be7dc01f1e0afdba6c8eb2b43d2fa01c743be1b9273ab1eaf6c233df078d705",
)

load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")

go_rules_dependencies()
go_register_toolchains(
go_version = "1.10.1",
)

load(
"@io_bazel_rules_docker//go:image.bzl",
_go_image_repos = "repositories",
)

_go_image_repos()
20 changes: 20 additions & 0 deletions integration/testdata/custom/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash

bazel build //:skaffold_example.tar
TAR_PATH=$(bazel info bazel-bin)
docker load -i $TAR_PATH/skaffold_example.tar


images=$(echo $IMAGES | tr " " "\n")

for image in $images
do
docker tag bazel:skaffold_example $image

if $PUSH_IMAGE
then
docker push $image
fi

done

8 changes: 8 additions & 0 deletions integration/testdata/custom/k8s/k8s-pod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: Pod
metadata:
name: bazel
spec:
containers:
- name: bazel
image: gcr.io/k8s-skaffold/skaffold-bazel
13 changes: 13 additions & 0 deletions integration/testdata/custom/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import (
"fmt"
"time"
)

func main() {
for {
fmt.Println("Hello bazel!")
time.Sleep(time.Second * 1)
}
}
13 changes: 13 additions & 0 deletions integration/testdata/custom/skaffold.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: skaffold/v1beta10
kind: Config
build:
artifacts:
- image: gcr.io/k8s-skaffold/skaffold-bazel
custom:
buildCommand: ./build.sh
dependencies:
paths:
- .
ignore:
- bazel*
- README*
Loading

0 comments on commit 4a4d696

Please sign in to comment.