Skip to content

Commit

Permalink
build: add ConfigMap build sources
Browse files Browse the repository at this point in the history
RFE/bug 1540978
  • Loading branch information
adambkaplan committed Jun 21, 2018
1 parent 0d0ebdf commit 9be6877
Show file tree
Hide file tree
Showing 23 changed files with 844 additions and 129 deletions.
20 changes: 20 additions & 0 deletions pkg/build/apis/build/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ func validateSource(input *buildapi.BuildSource, isCustomStrategy, isDockerStrat
}

allErrs = append(allErrs, validateSecrets(input.Secrets, isDockerStrategy, fldPath.Child("secrets"))...)
allErrs = append(allErrs, validateConfigMaps(input.ConfigMaps, isDockerStrategy, fldPath.Child("configMaps"))...)

allErrs = append(allErrs, validateSecretRef(input.SourceSecret, fldPath.Child("sourceSecret"))...)

Expand Down Expand Up @@ -275,6 +276,25 @@ func validateGitSource(git *buildapi.GitBuildSource, fldPath *field.Path) field.
return allErrs
}

func validateConfigMaps(configs []buildapi.ConfigMapBuildSource, isDockerStrategy bool, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for i, c := range configs {
if len(c.ConfigMap.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("configMap"), ""))
}
if reasons := validation.ValidateConfigMapName(c.ConfigMap.Name, false); len(reasons) != 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("configMap"), c, "must be valid configMap name"))
}
if strings.HasPrefix(path.Clean(c.DestinationDir), "..") {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("destinationDir"), c.DestinationDir, "destination dir cannot start with '..'"))
}
if isDockerStrategy && filepath.IsAbs(c.DestinationDir) {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("destinationDir"), c.DestinationDir, "for the docker strategy the destinationDir has to be relative path"))
}
}
return allErrs
}

func validateSecrets(secrets []buildapi.SecretBuildSource, isDockerStrategy bool, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for i, s := range secrets {
Expand Down
135 changes: 129 additions & 6 deletions pkg/build/apis/build/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -820,11 +820,14 @@ func TestValidateSource(t *testing.T) {
dockerfile := "FROM something"
invalidProxyAddress := "some!@#$%^&*()url"
errorCases := []struct {
t field.ErrorType
path string
source *buildapi.BuildSource
ok bool
multiple bool
t field.ErrorType
path string
source *buildapi.BuildSource
ok bool
multiple bool
customStrategy bool
dockerStrategy bool
jenkinsStrategy bool
}{
// 0
{
Expand Down Expand Up @@ -1189,10 +1192,130 @@ func TestValidateSource(t *testing.T) {
},
},
},
// 25 - invalid configMap name
{
t: field.ErrorTypeInvalid,
path: "configMaps[0].configMap",
source: &buildapi.BuildSource{
ConfigMaps: []buildapi.ConfigMapBuildSource{
{
ConfigMap: kapi.LocalObjectReference{
Name: "A@ba!dn#me",
},
DestinationDir: "./some/relative/path",
},
},
},
},
// 26 - invalid relative path
{
t: field.ErrorTypeInvalid,
path: "configMaps[0].destinationDir",
source: &buildapi.BuildSource{
ConfigMaps: []buildapi.ConfigMapBuildSource{
{
ConfigMap: kapi.LocalObjectReference{
Name: "good-secret-name",
},
DestinationDir: "../bad/parent/path",
},
},
},
},
// 27 - invalid abs path with Docker strategy
{
t: field.ErrorTypeInvalid,
path: "configMaps[0].destinationDir",
dockerStrategy: true,
source: &buildapi.BuildSource{
ConfigMaps: []buildapi.ConfigMapBuildSource{
{
ConfigMap: kapi.LocalObjectReference{
Name: "good-secret-name",
},
DestinationDir: "/var/log/something",
},
},
},
},
// 28 - ok abs path without Docker strategy
{
ok: true,
source: &buildapi.BuildSource{
ConfigMaps: []buildapi.ConfigMapBuildSource{
{
ConfigMap: kapi.LocalObjectReference{
Name: "good-secret-name",
},
DestinationDir: "/var/log/something",
},
},
},
},
// 29 - invalid secret name
{
t: field.ErrorTypeInvalid,
path: "secrets[0].secret",
source: &buildapi.BuildSource{
Secrets: []buildapi.SecretBuildSource{
{
Secret: kapi.LocalObjectReference{
Name: "A@ba!dn#me",
},
DestinationDir: "./some/relative/path",
},
},
},
},
// 30 - invalid secret relative path
{
t: field.ErrorTypeInvalid,
path: "secrets[0].destinationDir",
source: &buildapi.BuildSource{
Secrets: []buildapi.SecretBuildSource{
{
Secret: kapi.LocalObjectReference{
Name: "good-secret-name",
},
DestinationDir: "../bad/parent/path",
},
},
},
},
// 31 - invalid abs path with Docker strategy
{
t: field.ErrorTypeInvalid,
path: "secrets[0].destinationDir",
dockerStrategy: true,
source: &buildapi.BuildSource{
Secrets: []buildapi.SecretBuildSource{
{
Secret: kapi.LocalObjectReference{
Name: "good-secret-name",
},
DestinationDir: "/var/log/something",
},
},
},
},
// 32 - ok abs path without Docker strategy
{
ok: true,
source: &buildapi.BuildSource{
Secrets: []buildapi.SecretBuildSource{
{
Secret: kapi.LocalObjectReference{
Name: "good-secret-name",
},
DestinationDir: "/var/log/something",
},
},
},
},
}
for i, tc := range errorCases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
errors := validateSource(tc.source, false, false, false, nil)
errors := validateSource(tc.source, tc.customStrategy, tc.dockerStrategy, tc.jenkinsStrategy, nil)
switch len(errors) {
case 0:
if !tc.ok {
Expand Down
46 changes: 46 additions & 0 deletions pkg/build/builder/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"strings"
"time"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
Expand Down Expand Up @@ -66,6 +67,51 @@ type GitClient interface {
GetInfo(location string) (*git.SourceInfo, []error)
}

// localObjectBuildSource is a build source that is copied into a build from a Kubernetes
// key-value store, such as a `Secret` or `ConfigMap`.
type localObjectBuildSource interface {
// LocalObjectRef returns a reference to a local Kubernetes object by name.
LocalObjectRef() corev1.LocalObjectReference
// DestinationPath returns the directory where the files from the build source should be
// available for the build time.
// For the Source build strategy, these will be injected into a container
// where the assemble script runs.
// For the Docker build strategy, these will be copied into the build
// directory, where the Dockerfile is located, so users can ADD or COPY them
// during docker build.
DestinationPath() string
// IsSecret returns `true` if the build source is a `Secret` containing sensitive data.
IsSecret() bool
}

type configMapSource buildapiv1.ConfigMapBuildSource

func (c configMapSource) LocalObjectRef() corev1.LocalObjectReference {
return c.ConfigMap
}

func (c configMapSource) DestinationPath() string {
return c.DestinationDir
}

func (c configMapSource) IsSecret() bool {
return false
}

type secretSource buildapiv1.SecretBuildSource

func (s secretSource) LocalObjectRef() corev1.LocalObjectReference {
return s.Secret
}

func (s secretSource) DestinationPath() string {
return s.DestinationDir
}

func (s secretSource) IsSecret() bool {
return true
}

// buildInfo returns a slice of KeyValue pairs with build metadata to be
// inserted into Docker images produced by build.
func buildInfo(build *buildapiv1.Build, sourceInfo *git.SourceInfo) []KeyValue {
Expand Down
110 changes: 68 additions & 42 deletions pkg/build/builder/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func (d *DockerBuilder) Build() error {
}

startTime := metav1.Now()
err = d.dockerBuild(buildDir, buildTag, d.build.Spec.Source.Secrets)
err = d.dockerBuild(buildDir, buildTag)

timing.RecordNewStep(ctx, buildapiv1.StageBuild, buildapiv1.StepDockerBuild, startTime, metav1.Now())

Expand Down Expand Up @@ -205,59 +205,82 @@ func (d *DockerBuilder) Build() error {
return nil
}

// copyConfigMaps copies all files from the directory where the configMap is
// mounted in the builder pod to a directory where the is the Dockerfile, so
// users can ADD or COPY the files inside their Dockerfile.
func (d *DockerBuilder) copyConfigMaps(configs []buildapiv1.ConfigMapBuildSource, targetDir string) error {
var err error
for _, c := range configs {
err = d.copyLocalObject(configMapSource(c), strategy.ConfigMapBuildSourceBaseMountPath, targetDir)
if err != nil {
return err
}
}
return nil
}

// copySecrets copies all files from the directory where the secret is
// mounted in the builder pod to a directory where the is the Dockerfile, so
// users can ADD or COPY the files inside their Dockerfile.
func (d *DockerBuilder) copySecrets(secrets []buildapiv1.SecretBuildSource, buildDir string) error {
func (d *DockerBuilder) copySecrets(secrets []buildapiv1.SecretBuildSource, targetDir string) error {
var err error
for _, s := range secrets {
dstDir := filepath.Join(buildDir, s.DestinationDir)
if err := os.MkdirAll(dstDir, 0777); err != nil {
err = d.copyLocalObject(secretSource(s), strategy.SecretBuildSourceBaseMountPath, targetDir)
if err != nil {
return err
}
glog.V(3).Infof("Copying files from the build secret %q to %q", s.Secret.Name, dstDir)

// Secrets contain nested directories and fairly baroque links. To prevent extra data being
// copied, perform the following steps:
//
// 1. Only top level files and directories within the secret directory are candidates
// 2. Any item starting with '..' is ignored
// 3. Destination directories are created first with 0777
// 4. Use the '-L' option to cp to copy only contents.
//
srcDir := filepath.Join(strategy.SecretBuildSourceBaseMountPath, s.Secret.Name)
if err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if srcDir == path {
return nil
}
}
return nil
}

// skip any contents that begin with ".."
if strings.HasPrefix(filepath.Base(path), "..") {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
func (d *DockerBuilder) copyLocalObject(s localObjectBuildSource, sourceDir, targetDir string) error {
dstDir := filepath.Join(targetDir, s.DestinationPath())
if err := os.MkdirAll(dstDir, 0777); err != nil {
return err
}
glog.V(3).Infof("Copying files from the build source %q to %q", s.LocalObjectRef().Name, dstDir)

// Build sources contain nested directories and fairly baroque links. To prevent extra data being
// copied, perform the following steps:
//
// 1. Only top level files and directories within the secret directory are candidates
// 2. Any item starting with '..' is ignored
// 3. Destination directories are created first with 0777
// 4. Use the '-L' option to cp to copy only contents.
//
srcDir := filepath.Join(sourceDir, s.LocalObjectRef().Name)
if err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if srcDir == path {
return nil
}

// ensure all directories are traversable
// skip any contents that begin with ".."
if strings.HasPrefix(filepath.Base(path), "..") {
if info.IsDir() {
if err := os.MkdirAll(dstDir, 0777); err != nil {
return err
}
return filepath.SkipDir
}
out, err := exec.Command("cp", "-vLRf", path, dstDir+"/").Output()
if err != nil {
glog.V(4).Infof("Secret %q failed to copy: %q", s.Secret.Name, string(out))
return nil
}

// ensure all directories are traversable
if info.IsDir() {
if err := os.MkdirAll(dstDir, 0777); err != nil {
return err
}
// See what is copied when debugging.
glog.V(5).Infof("Result of secret copy %s\n%s", s.Secret.Name, string(out))
return nil
}); err != nil {
}
out, err := exec.Command("cp", "-vLRf", path, dstDir+"/").Output()
if err != nil {
glog.V(4).Infof("Build source %q failed to copy: %q", s.LocalObjectRef().Name, string(out))
return err
}
// See what is copied when debugging.
glog.V(5).Infof("Result of build source copy %s\n%s", s.LocalObjectRef().Name, string(out))
return nil
}); err != nil {
return err
}
return nil
}
Expand All @@ -282,7 +305,7 @@ func (d *DockerBuilder) setupPullSecret() (*docker.AuthConfigurations, error) {
}

// dockerBuild performs a docker build on the source that has been retrieved
func (d *DockerBuilder) dockerBuild(dir string, tag string, secrets []buildapiv1.SecretBuildSource) error {
func (d *DockerBuilder) dockerBuild(dir string, tag string) error {
var noCache bool
var forcePull bool
var buildArgs []docker.BuildArg
Expand All @@ -304,7 +327,10 @@ func (d *DockerBuilder) dockerBuild(dir string, tag string, secrets []buildapiv1
if err != nil {
return err
}
if err := d.copySecrets(secrets, dir); err != nil {
if err := d.copySecrets(d.build.Spec.Source.Secrets, dir); err != nil {
return err
}
if err = d.copyConfigMaps(d.build.Spec.Source.ConfigMaps, dir); err != nil {
return err
}

Expand Down
Loading

0 comments on commit 9be6877

Please sign in to comment.