Skip to content

Commit

Permalink
Buildpacks builder supports mounting read/write volumes (experimental) (
Browse files Browse the repository at this point in the history
  • Loading branch information
briandealwis authored Jun 8, 2021
1 parent 34e85a6 commit 16f7510
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 9 deletions.
39 changes: 38 additions & 1 deletion docs/content/en/schemas/v2beta17.json
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,10 @@
"description": "indicates that the builder should be trusted.",
"x-intellij-html-description": "indicates that the builder should be trusted.",
"default": "false"
},
"volumes": {
"description": "support mounting host volumes into the container.",
"x-intellij-html-description": "support mounting host volumes into the container."
}
},
"preferredOrder": [
Expand All @@ -637,7 +641,8 @@
"buildpacks",
"trustBuilder",
"projectDescriptor",
"dependencies"
"dependencies",
"volumes"
],
"additionalProperties": false,
"type": "object",
Expand Down Expand Up @@ -674,6 +679,38 @@
"description": "*alpha* used to specify dependencies for an artifact built by buildpacks.",
"x-intellij-html-description": "<em>alpha</em> used to specify dependencies for an artifact built by buildpacks."
},
"BuildpackVolume": {
"required": [
"host",
"target"
],
"properties": {
"host": {
"type": "string",
"description": "local volume or absolute directory of the path to mount.",
"x-intellij-html-description": "local volume or absolute directory of the path to mount."
},
"options": {
"type": "string",
"description": "specify a list of comma-separated mount options. Valid options are: `ro` (default): volume contents are read-only. `rw`: volume contents are readable and writable. `volume-opt=<key>=<value>`: can be specified more than once, takes a key-value pair.",
"x-intellij-html-description": "specify a list of comma-separated mount options. Valid options are: <code>ro</code> (default): volume contents are read-only. <code>rw</code>: volume contents are readable and writable. <code>volume-opt=&lt;key&gt;=&lt;value&gt;</code>: can be specified more than once, takes a key-value pair."
},
"target": {
"type": "string",
"description": "path where the file or directory is available in the container. It is strongly recommended to not specify locations under `/cnb` or `/layers`.",
"x-intellij-html-description": "path where the file or directory is available in the container. It is strongly recommended to not specify locations under <code>/cnb</code> or <code>/layers</code>."
}
},
"preferredOrder": [
"host",
"target",
"options"
],
"additionalProperties": false,
"type": "object",
"description": "*alpha* used to mount host volumes or directories in the build container.",
"x-intellij-html-description": "<em>alpha</em> used to mount host volumes or directories in the build container."
},
"ClusterDetails": {
"properties": {
"HTTPS_PROXY": {
Expand Down
43 changes: 35 additions & 8 deletions pkg/skaffold/build/buildpacks/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ func (b *Builder) build(ctx context.Context, out io.Writer, a *latestV1.Artifact
if err != nil {
return "", fmt.Errorf("unable to evaluate env variables: %w", err)
}

cc, err := containerConfig(artifact)
if err != nil {
return "", fmt.Errorf("%q: %w", a.ImageName, err)
}

// List buildpacks to be used for the build.
// Those specified in the skaffold.yaml replace those in the project.toml.
buildpacks := artifact.Buildpacks
Expand All @@ -92,14 +98,15 @@ func (b *Builder) build(ctx context.Context, out io.Writer, a *latestV1.Artifact
builderImage, runImage, pullPolicy := resolveDependencyImages(artifact, b.artifacts, a.Dependencies, b.pushImages)

if err := runPackBuildFunc(ctx, output.GetUnderlyingWriter(out), b.localDocker, pack.BuildOptions{
AppPath: workspace,
Builder: builderImage,
RunImage: runImage,
Buildpacks: buildpacks,
Env: env,
Image: latest,
PullPolicy: pullPolicy,
TrustBuilder: artifact.TrustBuilder,
AppPath: workspace,
Builder: builderImage,
RunImage: runImage,
Buildpacks: buildpacks,
Env: env,
Image: latest,
PullPolicy: pullPolicy,
TrustBuilder: artifact.TrustBuilder,
ContainerConfig: cc,
// TODO(dgageot): Support project.toml include/exclude.
// FileFilter: func(string) bool { return true },
}); err != nil {
Expand Down Expand Up @@ -231,3 +238,23 @@ func resolveDependencyImages(artifact *latestV1.BuildpackArtifact, r ArtifactRes

return builderImage, runImage, pullPolicy
}

func containerConfig(artifact *latestV1.BuildpackArtifact) (pack.ContainerConfig, error) {
var vols []string
if artifact.Volumes != nil {
for _, v := range *artifact.Volumes {
if v.Host == "" || v.Target == "" {
// in case these slip by the JSON schema
return pack.ContainerConfig{}, errors.New("buildpacks volumes must have both host and target")
}
var spec string
if v.Options == "" {
spec = fmt.Sprintf("%s:%s", v.Host, v.Target)
} else {
spec = fmt.Sprintf("%s:%s:%s", v.Host, v.Target, v.Options)
}
vols = append(vols, spec)
}
}
return pack.ContainerConfig{Volumes: vols}, nil
}
52 changes: 52 additions & 0 deletions pkg/skaffold/build/buildpacks/lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import (
"testing"

lifecycle "github.com/buildpacks/lifecycle/cmd"
"github.com/buildpacks/pack"

latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestLifecycleStatusCode(t *testing.T) {
Expand Down Expand Up @@ -61,3 +65,51 @@ func TestLifecycleStatusCode(t *testing.T) {
}
}
}

func TestContainerConfig(t *testing.T) {
tests := []struct {
description string
volumes []latestV1.BuildpackVolume
shouldErr bool
expected pack.ContainerConfig
}{
{
description: "single volume with no options",
volumes: []latestV1.BuildpackVolume{{Host: "/foo", Target: "/bar"}},
expected: pack.ContainerConfig{Volumes: []string{"/foo:/bar"}},
},
{
description: "single volume with options",
volumes: []latestV1.BuildpackVolume{{Host: "/foo", Target: "/bar", Options: "rw"}},
expected: pack.ContainerConfig{Volumes: []string{"/foo:/bar:rw"}},
},
{
description: "multiple volumes",
volumes: []latestV1.BuildpackVolume{
{Host: "/foo", Target: "/bar", Options: "rw"},
{Host: "/bat", Target: "/baz", Options: "ro"},
},
expected: pack.ContainerConfig{Volumes: []string{"/foo:/bar:rw", "/bat:/baz:ro"}},
},
{
description: "missing host is skipped",
volumes: []latestV1.BuildpackVolume{{Host: "", Target: "/bar"}},
shouldErr: true,
},
{
description: "missing target is skipped",
volumes: []latestV1.BuildpackVolume{{Host: "/foo", Target: ""}},
shouldErr: true,
},
}

for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
artifact := latestV1.BuildpackArtifact{
Volumes: &test.volumes,
}
result, err := containerConfig(&artifact)
t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expected, result)
})
}
}
20 changes: 20 additions & 0 deletions pkg/skaffold/schema/latest/v1/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,9 @@ type BuildpackArtifact struct {

// Dependencies are the file dependencies that skaffold should watch for both rebuilding and file syncing for this artifact.
Dependencies *BuildpackDependencies `yaml:"dependencies,omitempty"`

// Volumes support mounting host volumes into the container.
Volumes *[]BuildpackVolume `yaml:"volumes,omitempty"`
}

// BuildpackDependencies *alpha* is used to specify dependencies for an artifact built by buildpacks.
Expand All @@ -1006,6 +1009,23 @@ type BuildpackDependencies struct {
Ignore []string `yaml:"ignore,omitempty"`
}

// BuildpackVolume *alpha* is used to mount host volumes or directories in the build container.
type BuildpackVolume struct {
// Host is the local volume or absolute directory of the path to mount.
Host string `yaml:"host" skaffold:"filepath" yamltags:"required"`

// Target is the path where the file or directory is available in the container.
// It is strongly recommended to not specify locations under `/cnb` or `/layers`.
Target string `yaml:"target" yamltags:"required"`

// Options specify a list of comma-separated mount options.
// Valid options are:
// `ro` (default): volume contents are read-only.
// `rw`: volume contents are readable and writable.
// `volume-opt=<key>=<value>`: can be specified more than once, takes a key-value pair.
Options string `yaml:"options,omitempty"`
}

// CustomArtifact *beta* 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.
type CustomArtifact struct {
Expand Down

0 comments on commit 16f7510

Please sign in to comment.