From 7edadaa534089e760213c1941a2e4c1c1fc0a9d5 Mon Sep 17 00:00:00 2001 From: Matthew McNew Date: Tue, 14 Jan 2020 15:57:43 -0700 Subject: [PATCH] Add support for platform api 0.2 Signed-off-by: Arjun Sreedharan --- pkg/apis/build/v1alpha1/build.go | 38 +- pkg/apis/build/v1alpha1/build_pod.go | 580 +++++++------ pkg/apis/build/v1alpha1/build_pod_test.go | 781 ++++++++++-------- pkg/apis/build/v1alpha1/build_test.go | 29 +- .../build/v1alpha1/zz_generated.deepcopy.go | 1 - pkg/buildpod/generator.go | 28 +- pkg/buildpod/generator_test.go | 163 ++-- pkg/cnb/cnb_metadata.go | 12 +- pkg/cnb/cnb_metadata_test.go | 148 +++- pkg/reconciler/v1alpha1/build/build.go | 3 +- pkg/reconciler/v1alpha1/build/build_test.go | 14 +- 11 files changed, 1065 insertions(+), 732 deletions(-) diff --git a/pkg/apis/build/v1alpha1/build.go b/pkg/apis/build/v1alpha1/build.go index 6f714c370..0ffb3bd6a 100644 --- a/pkg/apis/build/v1alpha1/build.go +++ b/pkg/apis/build/v1alpha1/build.go @@ -8,26 +8,6 @@ import ( corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" ) -func (bi *BuildBuilderSpec) getBuilderSecretVolume() corev1.Volume { - if len(bi.ImagePullSecrets) > 0 { - return corev1.Volume{ - Name: builderPullSecretsDirName, - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: bi.ImagePullSecrets[0].Name, - }, - }, - } - } else { - return corev1.Volume{ - Name: builderPullSecretsDirName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - } - } -} - func (*Build) GetGroupVersionKind() schema.GroupVersionKind { return SchemeGroupVersion.WithKind("Build") } @@ -36,6 +16,14 @@ func (b *Build) Tag() string { return b.Spec.Tags[0] } +func (b *Build) ServiceAccount() string { + return b.Spec.ServiceAccount +} + +func (b *Build) BuilderSpec() BuildBuilderSpec { + return b.Spec.Builder +} + func (b *Build) IsRunning() bool { if b == nil { return false @@ -100,15 +88,7 @@ func (b *Build) Finished() bool { return !b.Status.GetCondition(corev1alpha1.ConditionSucceeded).IsUnknown() } -func (b *Build) SourceEnvVars() []corev1.EnvVar { - return b.Spec.Source.Source().BuildEnvVars() -} - -func (b *Build) ImagePullSecretsVolume() corev1.Volume { - return b.Spec.Source.Source().ImagePullSecretsVolume() -} - -func (b *Build) Rebasable(builderStack string) bool { +func (b *Build) rebasable(builderStack string) bool { return b.Spec.LastBuild != nil && b.Annotations[BuildReasonAnnotation] == BuildReasonStack && b.Spec.LastBuild.StackId == builderStack } diff --git a/pkg/apis/build/v1alpha1/build_pod.go b/pkg/apis/build/v1alpha1/build_pod.go index 144d2268d..26902d9ce 100644 --- a/pkg/apis/build/v1alpha1/build_pod.go +++ b/pkg/apis/build/v1alpha1/build_pod.go @@ -11,6 +11,7 @@ import ( ) const ( + directExecute = "--" buildInitBinary = "build-init" rebaseBinary = "rebase" @@ -36,11 +37,11 @@ type BuildPodImages struct { } type BuildPodBuilderConfig struct { - BuilderSpec BuildBuilderSpec StackID string RunImage string Uid int64 Gid int64 + PlatformAPI string } var ( @@ -80,65 +81,19 @@ var ( } ) -func (b *Build) BuildPod(config BuildPodImages, secrets []corev1.Secret, buildPodBuilderConfig BuildPodBuilderConfig) (*corev1.Pod, error) { - if b.Rebasable(buildPodBuilderConfig.StackID) { - secretVolumes, secretVolumeMounts, secretArgs, err := b.setupSecretVolumesAndArgs(secrets, dockerSecrets) - if err != nil { - return nil, err - } - - return &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.PodName(), - Namespace: b.Namespace, - Labels: b.labels(map[string]string{ - BuildLabel: b.Name, - }), - OwnerReferences: []metav1.OwnerReference{ - *kmeta.NewControllerRef(b), - }, - }, - Spec: corev1.PodSpec{ - ServiceAccountName: b.Spec.ServiceAccount, - Volumes: secretVolumes, - RestartPolicy: corev1.RestartPolicyNever, - Containers: []corev1.Container{ - { - Name: "completion", - Image: config.CompletionImage, - ImagePullPolicy: corev1.PullIfNotPresent, - Resources: b.Spec.Resources, - }, - }, - InitContainers: []corev1.Container{ - { - Name: "rebase", - Image: config.RebaseImage, - Args: rebaseArgs(rebaseBinary, buildPodBuilderConfig.RunImage, b.Spec.LastBuild.Image, b.Spec.Tags, secretArgs), - ImagePullPolicy: corev1.PullIfNotPresent, - WorkingDir: "/workspace", - VolumeMounts: secretVolumeMounts, - }, - }, - }, - Status: corev1.PodStatus{}, - }, nil +func (b *Build) BuildPod(config BuildPodImages, secrets []corev1.Secret, bc BuildPodBuilderConfig) (*corev1.Pod, error) { + if b.rebasable(bc.StackID) { + return b.rebasePod(secrets, config, bc) } - buf, err := json.Marshal(b.Spec.Env) + envVars, err := json.Marshal(b.Spec.Env) if err != nil { return nil, err } - envVars := string(buf) - volumes := append(b.setupVolumes(), buildPodBuilderConfig.BuilderSpec.getBuilderSecretVolume()) - secretVolumes, secretVolumeMounts, secretArgs, err := b.setupSecretVolumesAndArgs(secrets, gitAndDockerSecrets) - if err != nil { - return nil, err - } - volumes = append(volumes, secretVolumes...) + secretVolumes, secretVolumeMounts, secretArgs := b.setupSecretVolumesAndArgs(secrets, gitAndDockerSecrets) - builderImage := buildPodBuilderConfig.BuilderSpec.Image + builderImage := b.Spec.Builder.Image workspaceVolume := corev1.VolumeMount{ Name: sourceVolume.Name, @@ -169,153 +124,326 @@ func (b *Build) BuildPod(config BuildPodImages, secrets []corev1.Secret, buildPo }, }, SecurityContext: &corev1.PodSecurityContext{ - FSGroup: &buildPodBuilderConfig.Gid, + FSGroup: &bc.Gid, }, - InitContainers: []corev1.Container{ - { - Name: "prepare", - Image: config.BuildInitImage, - SecurityContext: &corev1.SecurityContext{ - RunAsUser: &buildPodBuilderConfig.Uid, - RunAsGroup: &buildPodBuilderConfig.Gid, - }, - Args: buildInitArgs(buildInitBinary, secretArgs), - Env: append( - b.SourceEnvVars(), - corev1.EnvVar{ - Name: "PLATFORM_ENV_VARS", - Value: envVars, + InitContainers: steps(func(step func(corev1.Container)) { + step( + corev1.Container{ + Name: "prepare", + Image: config.BuildInitImage, + SecurityContext: &corev1.SecurityContext{ + RunAsUser: &bc.Uid, + RunAsGroup: &bc.Gid, }, - corev1.EnvVar{ - Name: "IMAGE_TAG", - Value: b.Tag(), + Args: args(a( + directExecute, + buildInitBinary), + secretArgs, + ), + Env: append( + b.Spec.Source.Source().BuildEnvVars(), + corev1.EnvVar{ + Name: "PLATFORM_ENV_VARS", + Value: string(envVars), + }, + corev1.EnvVar{ + Name: "IMAGE_TAG", + Value: b.Tag(), + }, + corev1.EnvVar{ + Name: "RUN_IMAGE", + Value: bc.RunImage, + }, + ), + ImagePullPolicy: corev1.PullIfNotPresent, + WorkingDir: "/workspace", + VolumeMounts: append( + secretVolumeMounts, + builderPullSecretsVolume, + imagePullSecretsVolume, + platformVolume, + sourceVolume, + homeVolume, + ), + }, + ) + step( + corev1.Container{ + Name: "detect", + Image: builderImage, + Command: []string{"/lifecycle/detector"}, + Args: []string{ + "-app=/workspace", + "-group=/layers/group.toml", + "-plan=/layers/plan.toml", }, - corev1.EnvVar{ - Name: "RUN_IMAGE", - Value: buildPodBuilderConfig.RunImage, + VolumeMounts: []corev1.VolumeMount{ + layersVolume, + platformVolume, + workspaceVolume, }, - ), - ImagePullPolicy: corev1.PullIfNotPresent, - WorkingDir: "/workspace", - VolumeMounts: append( - secretVolumeMounts, - builderPullSecretsVolume, - imagePullSecretsVolume, - platformVolume, - sourceVolume, - homeVolume, - ), - }, - { - Name: "detect", - Image: builderImage, - Command: []string{"/lifecycle/detector"}, - Args: []string{ - "-app=/workspace", - "-group=/layers/group.toml", - "-plan=/layers/plan.toml", + ImagePullPolicy: corev1.PullIfNotPresent, }, - VolumeMounts: []corev1.VolumeMount{ - layersVolume, - platformVolume, - workspaceVolume, + ) + if bc.legacy() { + step( + corev1.Container{ + Name: "restore", + Image: builderImage, + Command: []string{"/lifecycle/restorer"}, + Args: []string{ + "-group=/layers/group.toml", + "-layers=/layers", + "-path=/cache", + }, + VolumeMounts: []corev1.VolumeMount{ + layersVolume, + cacheVolume, + }, + ImagePullPolicy: corev1.PullIfNotPresent, + }, + ) + step( + corev1.Container{ + Name: "analyze", + Image: builderImage, + Command: []string{"/lifecycle/analyzer"}, + Args: []string{ + "-layers=/layers", + "-helpers=false", + "-group=/layers/group.toml", + "-analyzed=/layers/analyzed.toml", + b.Tag(), + }, + VolumeMounts: []corev1.VolumeMount{ + layersVolume, + workspaceVolume, + homeVolume, + }, + Env: []corev1.EnvVar{ + homeEnv, + }, + ImagePullPolicy: corev1.PullIfNotPresent, + }, + ) + } else { + step( + corev1.Container{ + Name: "analyze", + Image: builderImage, + Command: []string{"/lifecycle/analyzer"}, + Args: []string{ + "-layers=/layers", + "-helpers=false", + "-group=/layers/group.toml", + "-analyzed=/layers/analyzed.toml", + "-cache-dir=/cache", + b.Tag(), + }, + VolumeMounts: []corev1.VolumeMount{ + layersVolume, + workspaceVolume, + homeVolume, + cacheVolume, + }, + Env: []corev1.EnvVar{ + homeEnv, + }, + ImagePullPolicy: corev1.PullIfNotPresent, + }, + ) + step( + corev1.Container{ + Name: "restore", + Image: builderImage, + Command: []string{"/lifecycle/restorer"}, + Args: []string{ + "-group=/layers/group.toml", + "-layers=/layers", + "-cache-dir=/cache", + }, + VolumeMounts: []corev1.VolumeMount{ + layersVolume, + cacheVolume, + }, + ImagePullPolicy: corev1.PullIfNotPresent, + }, + ) + } + step( + corev1.Container{ + Name: "build", + Image: builderImage, + Command: []string{"/lifecycle/builder"}, + Args: []string{ + "-layers=/layers", + "-app=/workspace", + "-group=/layers/group.toml", + "-plan=/layers/plan.toml", + }, + VolumeMounts: []corev1.VolumeMount{ + layersVolume, + platformVolume, + workspaceVolume, + }, + ImagePullPolicy: corev1.PullIfNotPresent, }, - ImagePullPolicy: corev1.PullIfNotPresent, + ) + if bc.legacy() { + step( + corev1.Container{ + Name: "export", + Image: builderImage, + Command: []string{"/lifecycle/exporter"}, + Args: append([]string{ + "-layers=/layers", + "-helpers=false", + "-app=/workspace", + "-group=/layers/group.toml", + "-analyzed=/layers/analyzed.toml", + }, b.Spec.Tags...), + VolumeMounts: []corev1.VolumeMount{ + layersVolume, + workspaceVolume, + homeVolume, + }, + Env: []corev1.EnvVar{ + homeEnv, + }, + ImagePullPolicy: corev1.PullIfNotPresent, + }, + ) + step( + corev1.Container{ + Name: "cache", + Image: builderImage, + Command: []string{"/lifecycle/cacher"}, + Args: []string{ + "-group=/layers/group.toml", + "-layers=/layers", + "-path=/cache", + }, + VolumeMounts: []corev1.VolumeMount{ + layersVolume, + cacheVolume, + }, + ImagePullPolicy: corev1.PullIfNotPresent, + }, + ) + } else { + step( + corev1.Container{ + Name: "export", + Image: builderImage, + Command: []string{"/lifecycle/exporter"}, + Args: append([]string{ + "-layers=/layers", + "-helpers=false", + "-app=/workspace", + "-group=/layers/group.toml", + "-analyzed=/layers/analyzed.toml", + "-cache-dir=/cache", + }, b.Spec.Tags...), + VolumeMounts: []corev1.VolumeMount{ + layersVolume, + workspaceVolume, + homeVolume, + cacheVolume, + }, + Env: []corev1.EnvVar{ + homeEnv, + }, + ImagePullPolicy: corev1.PullIfNotPresent, + }, + ) + } + }), + ServiceAccountName: b.Spec.ServiceAccount, + Volumes: append( + secretVolumes, + corev1.Volume{ + Name: cacheDirName, + VolumeSource: b.cacheVolume(), }, - { - Name: "restore", - Image: builderImage, - Command: []string{"/lifecycle/restorer"}, - Args: []string{ - "-group=/layers/group.toml", - "-layers=/layers", - "-path=/cache", - }, - VolumeMounts: []corev1.VolumeMount{ - layersVolume, - cacheVolume, + corev1.Volume{ + Name: layersDirName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, }, - ImagePullPolicy: corev1.PullIfNotPresent, }, - { - Name: "analyze", - Image: builderImage, - Command: []string{"/lifecycle/analyzer"}, - Args: []string{ - "-layers=/layers", - "-helpers=false", - "-group=/layers/group.toml", - "-analyzed=/layers/analyzed.toml", - b.Tag(), + corev1.Volume{ + Name: homeDir, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, }, - VolumeMounts: []corev1.VolumeMount{ - layersVolume, - workspaceVolume, - homeVolume, - }, - Env: []corev1.EnvVar{ - homeEnv, - }, - ImagePullPolicy: corev1.PullIfNotPresent, }, - { - Name: "build", - Image: builderImage, - Command: []string{"/lifecycle/builder"}, - Args: []string{ - "-layers=/layers", - "-app=/workspace", - "-group=/layers/group.toml", - "-plan=/layers/plan.toml", + corev1.Volume{ + Name: workspaceDir, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, }, - VolumeMounts: []corev1.VolumeMount{ - layersVolume, - platformVolume, - workspaceVolume, + }, + corev1.Volume{ + Name: platformDir, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, }, - ImagePullPolicy: corev1.PullIfNotPresent, }, + b.Spec.Source.Source().ImagePullSecretsVolume(), + builderSecretVolume(b.Spec.Builder), + ), + ImagePullSecrets: b.Spec.Builder.ImagePullSecrets, + }, + }, nil +} + +func (b *Build) rebasePod(secrets []corev1.Secret, config BuildPodImages, buildPodBuilderConfig BuildPodBuilderConfig) (*corev1.Pod, error) { + secretVolumes, secretVolumeMounts, secretArgs := b.setupSecretVolumesAndArgs(secrets, dockerSecrets) + + return &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: b.PodName(), + Namespace: b.Namespace, + Labels: b.labels(map[string]string{ + BuildLabel: b.Name, + }), + OwnerReferences: []metav1.OwnerReference{ + *kmeta.NewControllerRef(b), + }, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: b.Spec.ServiceAccount, + Volumes: secretVolumes, + RestartPolicy: corev1.RestartPolicyNever, + Containers: []corev1.Container{ { - Name: "export", - Image: builderImage, - Command: []string{"/lifecycle/exporter"}, - Args: append([]string{ - "-layers=/layers", - "-helpers=false", - "-app=/workspace", - "-group=/layers/group.toml", - "-analyzed=/layers/analyzed.toml", - }, b.Spec.Tags...), - VolumeMounts: []corev1.VolumeMount{ - layersVolume, - workspaceVolume, - homeVolume, - }, - Env: []corev1.EnvVar{ - homeEnv, - }, + Name: "completion", + Image: config.CompletionImage, ImagePullPolicy: corev1.PullIfNotPresent, + Resources: b.Spec.Resources, }, + }, + InitContainers: []corev1.Container{ { - Name: "cache", - Image: builderImage, - Command: []string{"/lifecycle/cacher"}, - Args: []string{ - "-group=/layers/group.toml", - "-layers=/layers", - "-path=/cache", - }, - VolumeMounts: []corev1.VolumeMount{ - layersVolume, - cacheVolume, - }, + Name: "rebase", + Image: config.RebaseImage, + Args: args(a( + directExecute, + rebaseBinary, + "--run-image", + buildPodBuilderConfig.RunImage, + "--last-built-image", + b.Spec.LastBuild.Image), + secretArgs, + b.Spec.Tags, + ), ImagePullPolicy: corev1.PullIfNotPresent, + WorkingDir: "/workspace", + VolumeMounts: secretVolumeMounts, }, }, - ServiceAccountName: b.Spec.ServiceAccount, - Volumes: volumes, - ImagePullSecrets: buildPodBuilderConfig.BuilderSpec.ImagePullSecrets, }, + Status: corev1.PodStatus{}, }, nil } @@ -339,7 +467,7 @@ func dockerSecrets(secret corev1.Secret) bool { return secret.Annotations[DOCKERSecretAnnotationPrefix] != "" } -func (b *Build) setupSecretVolumesAndArgs(secrets []corev1.Secret, filter func(secret corev1.Secret) bool) ([]corev1.Volume, []corev1.VolumeMount, []string, error) { +func (b *Build) setupSecretVolumesAndArgs(secrets []corev1.Secret, filter func(secret corev1.Secret) bool) ([]corev1.Volume, []corev1.VolumeMount, []string) { var ( volumes []corev1.Volume volumeMounts []corev1.VolumeMount @@ -365,60 +493,46 @@ func (b *Build) setupSecretVolumesAndArgs(secrets []corev1.Secret, filter func(s MountPath: fmt.Sprintf(SecretPathName, secret.Name), }) - if secret.Annotations[DOCKERSecretAnnotationPrefix] != "" { - annotatedUrl := secret.Annotations[DOCKERSecretAnnotationPrefix] - secretType := "docker" - - args = append(args, fmt.Sprintf("-basic-%s=%s=%s", secretType, secret.Name, annotatedUrl)) - } else { + switch { + case secret.Annotations[DOCKERSecretAnnotationPrefix] != "": + args = append(args, + fmt.Sprintf("-basic-%s=%s=%s", "docker", secret.Name, secret.Annotations[DOCKERSecretAnnotationPrefix])) + case secret.Type == corev1.SecretTypeBasicAuth: annotatedUrl := secret.Annotations[GITSecretAnnotationPrefix] - secretType := "git" - - switch secret.Type { - case corev1.SecretTypeBasicAuth: - args = append(args, fmt.Sprintf("-basic-%s=%s=%s", secretType, secret.Name, annotatedUrl)) - case corev1.SecretTypeSSHAuth: - args = append(args, fmt.Sprintf("-ssh-%s=%s=%s", secretType, secret.Name, annotatedUrl)) - } + args = append(args, fmt.Sprintf("-basic-%s=%s=%s", "git", secret.Name, annotatedUrl)) + case secret.Type == corev1.SecretTypeSSHAuth: + annotatedUrl := secret.Annotations[GITSecretAnnotationPrefix] + args = append(args, fmt.Sprintf("-ssh-%s=%s=%s", "git", secret.Name, annotatedUrl)) + default: + //ignoring secret } } - return volumes, volumeMounts, args, nil + return volumes, volumeMounts, args } -func (b *Build) setupVolumes() []corev1.Volume { - volumes := []corev1.Volume{ - { - Name: cacheDirName, - VolumeSource: b.cacheVolume(), - }, - { - Name: layersDirName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, - { - Name: homeDir, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, - { - Name: workspaceDir, +func (bc *BuildPodBuilderConfig) legacy() bool { + return bc.PlatformAPI == "0.1" +} + +func builderSecretVolume(bbs BuildBuilderSpec) corev1.Volume { + if len(bbs.ImagePullSecrets) > 0 { + return corev1.Volume{ + Name: builderPullSecretsDirName, VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, + Secret: &corev1.SecretVolumeSource{ + SecretName: bbs.ImagePullSecrets[0].Name, + }, }, - }, - { - Name: platformDir, + } + } else { + return corev1.Volume{ + Name: builderPullSecretsDirName, VolumeSource: corev1.VolumeSource{ EmptyDir: &corev1.EmptyDirVolumeSource{}, }, - }, + } } - - return append(volumes, b.ImagePullSecretsVolume()) } func (b *Build) labels(additionalLabels map[string]string) map[string]string { @@ -433,7 +547,7 @@ func (b *Build) labels(additionalLabels map[string]string) map[string]string { return labels } -func combineArgs(args ...[]string) []string { +func args(args ...[]string) []string { var combined []string for _, a := range args { combined = append(combined, a...) @@ -441,20 +555,14 @@ func combineArgs(args ...[]string) []string { return combined } -const directExecute = "--" - -func rebaseArgs(rebaseBinary, runsImage, lastBuiltImage string, tags, secretArgs []string) []string { - return combineArgs( - []string{directExecute, rebaseBinary}, - secretArgs, - []string{"--run-image", runsImage, "--last-built-image", lastBuiltImage}, - tags, - ) +func a(args ...string) []string { + return args } -func buildInitArgs(buildInitBinary string, secretArgs []string) []string { - return combineArgs( - []string{directExecute, buildInitBinary}, - secretArgs, - ) +func steps(f func(step func(corev1.Container))) []corev1.Container { + containers := make([]corev1.Container, 0, 7) + f(func(container corev1.Container) { + containers = append(containers, container) + }) + return containers } diff --git a/pkg/apis/build/v1alpha1/build_pod_test.go b/pkg/apis/build/v1alpha1/build_pod_test.go index 246c9474a..bb685829d 100644 --- a/pkg/apis/build/v1alpha1/build_pod_test.go +++ b/pkg/apis/build/v1alpha1/build_pod_test.go @@ -128,396 +128,506 @@ func testBuildPod(t *testing.T, when spec.G, it spec.S) { } buildPodBuilderConfig := v1alpha1.BuildPodBuilderConfig{ - BuilderSpec: builderImageRef, StackID: "com.builder.stack.io", RunImage: "builderregistry.io/run", Uid: 2000, Gid: 3000, + PlatformAPI: "0.2", } when("BuildPod", func() { - it("creates a pod with a builder owner reference and build label", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + when("0.2+ platform api", func() { + it("creates a pod with a builder owner reference and build label", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - assert.Equal(t, pod.ObjectMeta, metav1.ObjectMeta{ - Name: build.PodName(), - Namespace: namespace, - Labels: map[string]string{ - "some/label": "to-pass-through", - "build.pivotal.io/build": buildName, - }, - OwnerReferences: []metav1.OwnerReference{ - *kmeta.NewControllerRef(build), - }, + assert.Equal(t, pod.ObjectMeta, metav1.ObjectMeta{ + Name: build.PodName(), + Namespace: namespace, + Labels: map[string]string{ + "some/label": "to-pass-through", + "build.pivotal.io/build": buildName, + }, + OwnerReferences: []metav1.OwnerReference{ + *kmeta.NewControllerRef(build), + }, + }) }) - }) - it("creates a pod with a correct service account", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("creates a pod with a correct service account", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - assert.Equal(t, serviceAccount, pod.Spec.ServiceAccountName) - }) + assert.Equal(t, serviceAccount, pod.Spec.ServiceAccountName) + }) - it("configures the FS Mount Group with the supplied group", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("configures the FS Mount Group with the supplied group", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - assert.Equal(t, buildPodBuilderConfig.Gid, *pod.Spec.SecurityContext.FSGroup) - }) + assert.Equal(t, buildPodBuilderConfig.Gid, *pod.Spec.SecurityContext.FSGroup) + }) - it("creates init containers with all the build steps", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("creates init containers with all the build steps", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - assert.Len(t, pod.Spec.InitContainers, len([]string{ - "prepare", - "detect", - "restore", - "analyze", - "build", - "export", - "cache", - })) - }) + var names []string + for _, container := range pod.Spec.InitContainers { + names = append(names, container.Name) + } - it("configures the workspace volume with a subPath", func() { - build.Spec.Source.SubPath = "some/path" + assert.Equal(t, []string{ + "prepare", + "detect", + "analyze", + "restore", + "build", + "export", + }, names) + }) - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("configures the workspace volume with a subPath", func() { + build.Spec.Source.SubPath = "some/path" - vol := getVolumeMountFromContainer(t, pod.Spec.InitContainers, "prepare", "workspace-dir") - assert.Equal(t, "/workspace", vol.MountPath) - assert.Equal(t, "", vol.SubPath) + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - for _, containerName := range []string{"detect", "analyze", "build", "export"} { - vol := getVolumeMountFromContainer(t, pod.Spec.InitContainers, containerName, "workspace-dir") + vol := volumeMountFromContainer(t, pod.Spec.InitContainers, "prepare", "workspace-dir") assert.Equal(t, "/workspace", vol.MountPath) - assert.Equal(t, "some/path", vol.SubPath) - } - }) + assert.Equal(t, "", vol.SubPath) - it("configures prepare with docker and git credentials", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + for _, containerName := range []string{"detect", "analyze", "build", "export"} { + vol := volumeMountFromContainer(t, pod.Spec.InitContainers, containerName, "workspace-dir") + assert.Equal(t, "/workspace", vol.MountPath) + assert.Equal(t, "some/path", vol.SubPath) + } + }) - assert.Equal(t, pod.Spec.InitContainers[0].Name, "prepare") - assert.Equal(t, pod.Spec.InitContainers[0].Image, config.BuildInitImage) - assert.Equal(t, []string{ - directExecute, - "build-init", - "-basic-git=git-secret-1=https://github.com", - "-ssh-git=git-secret-2=https://bitbucket.com", - "-basic-docker=docker-secret-1=acr.io", - }, pod.Spec.InitContainers[0].Args) - - assert.Contains(t, - pod.Spec.InitContainers[0].VolumeMounts, - corev1.VolumeMount{ - Name: "secret-volume-git-secret-1", - MountPath: "/var/build-secrets/git-secret-1", - }, - corev1.VolumeMount{ - Name: "secret-volume-git-secret-2", - MountPath: "/var/build-secrets/git-secret-2", - }, - corev1.VolumeMount{ - Name: "secret-volume-docker-secret-1", - MountPath: "/var/build-secrets/docker-secret-1", - }, - ) - }) + it("configures prepare with docker and git credentials", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - it("configures prepare with the build configuration", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + assert.Equal(t, pod.Spec.InitContainers[0].Name, "prepare") + assert.Equal(t, pod.Spec.InitContainers[0].Image, config.BuildInitImage) + assert.Equal(t, []string{ + directExecute, + "build-init", + "-basic-git=git-secret-1=https://github.com", + "-ssh-git=git-secret-2=https://bitbucket.com", + "-basic-docker=docker-secret-1=acr.io", + }, pod.Spec.InitContainers[0].Args) + + assert.Contains(t, + pod.Spec.InitContainers[0].VolumeMounts, + corev1.VolumeMount{ + Name: "secret-volume-git-secret-1", + MountPath: "/var/build-secrets/git-secret-1", + }, + corev1.VolumeMount{ + Name: "secret-volume-git-secret-2", + MountPath: "/var/build-secrets/git-secret-2", + }, + corev1.VolumeMount{ + Name: "secret-volume-docker-secret-1", + MountPath: "/var/build-secrets/docker-secret-1", + }, + ) + }) - assert.Equal(t, pod.Spec.InitContainers[0].Name, "prepare") - assert.Equal(t, pod.Spec.InitContainers[0].Image, config.BuildInitImage) - assert.Equal(t, buildPodBuilderConfig.Uid, *pod.Spec.InitContainers[0].SecurityContext.RunAsUser) - assert.Equal(t, buildPodBuilderConfig.Gid, *pod.Spec.InitContainers[0].SecurityContext.RunAsGroup) - assert.Contains(t, pod.Spec.InitContainers[0].Env, - corev1.EnvVar{ - Name: "PLATFORM_ENV_VARS", - Value: `[{"name":"keyA","value":"valueA"},{"name":"keyB","value":"valueB"}]`, - }) - assert.Contains(t, pod.Spec.InitContainers[0].Env, - corev1.EnvVar{ - Name: "IMAGE_TAG", - Value: "someimage/name", - }) - assert.Contains(t, pod.Spec.InitContainers[0].Env, - corev1.EnvVar{ - Name: "RUN_IMAGE", - Value: "builderregistry.io/run", + it("configures prepare with the build configuration", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) + + assert.Equal(t, pod.Spec.InitContainers[0].Name, "prepare") + assert.Equal(t, pod.Spec.InitContainers[0].Image, config.BuildInitImage) + assert.Equal(t, buildPodBuilderConfig.Uid, *pod.Spec.InitContainers[0].SecurityContext.RunAsUser) + assert.Equal(t, buildPodBuilderConfig.Gid, *pod.Spec.InitContainers[0].SecurityContext.RunAsGroup) + assert.Contains(t, pod.Spec.InitContainers[0].Env, + corev1.EnvVar{ + Name: "PLATFORM_ENV_VARS", + Value: `[{"name":"keyA","value":"valueA"},{"name":"keyB","value":"valueB"}]`, + }) + assert.Contains(t, pod.Spec.InitContainers[0].Env, + corev1.EnvVar{ + Name: "IMAGE_TAG", + Value: "someimage/name", + }) + assert.Contains(t, pod.Spec.InitContainers[0].Env, + corev1.EnvVar{ + Name: "RUN_IMAGE", + Value: "builderregistry.io/run", + }) + assert.Subset(t, pod.Spec.InitContainers[0].VolumeMounts, []corev1.VolumeMount{ + { + Name: "platform-dir", + MountPath: "/platform", + }, + { + Name: "workspace-dir", + MountPath: "/workspace", + }, + { + Name: "home-dir", + MountPath: "/builder/home", + }, + { + Name: "builder-pull-secrets-dir", + MountPath: "/builderPullSecrets", + ReadOnly: true, + }, }) - assert.Subset(t, pod.Spec.InitContainers[0].VolumeMounts, []corev1.VolumeMount{ - { - Name: "platform-dir", - MountPath: "/platform", - }, - { - Name: "workspace-dir", - MountPath: "/workspace", - }, - { - Name: "home-dir", - MountPath: "/builder/home", - }, - { - Name: "builder-pull-secrets-dir", - MountPath: "/builderPullSecrets", - ReadOnly: true, - }, }) - }) - it("configures the prepare step for git source", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("configures the prepare step for git source", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - assert.Equal(t, "prepare", pod.Spec.InitContainers[0].Name) - assert.Equal(t, config.BuildInitImage, pod.Spec.InitContainers[0].Image) - assert.Contains(t, pod.Spec.InitContainers[0].Env, - corev1.EnvVar{ - Name: "GIT_URL", - Value: build.Spec.Source.Git.URL, - }) - assert.Contains(t, pod.Spec.InitContainers[0].Env, - corev1.EnvVar{ - Name: "GIT_REVISION", - Value: build.Spec.Source.Git.Revision, - }, - ) - }) + assert.Equal(t, "prepare", pod.Spec.InitContainers[0].Name) + assert.Equal(t, config.BuildInitImage, pod.Spec.InitContainers[0].Image) + assert.Contains(t, pod.Spec.InitContainers[0].Env, + corev1.EnvVar{ + Name: "GIT_URL", + Value: build.Spec.Source.Git.URL, + }) + assert.Contains(t, pod.Spec.InitContainers[0].Env, + corev1.EnvVar{ + Name: "GIT_REVISION", + Value: build.Spec.Source.Git.Revision, + }, + ) + }) - it("configures prepare with the blob source", func() { - build.Spec.Source.Git = nil - build.Spec.Source.Blob = &v1alpha1.Blob{ - URL: "https://some-blobstore.example.com/some-blob", - } - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("configures prepare with the blob source", func() { + build.Spec.Source.Git = nil + build.Spec.Source.Blob = &v1alpha1.Blob{ + URL: "https://some-blobstore.example.com/some-blob", + } + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - assert.Equal(t, "prepare", pod.Spec.InitContainers[0].Name) - assert.Equal(t, config.BuildInitImage, pod.Spec.InitContainers[0].Image) - assert.Contains(t, pod.Spec.InitContainers[0].Env, - corev1.EnvVar{ - Name: "BLOB_URL", - Value: "https://some-blobstore.example.com/some-blob", - }) - }) + assert.Equal(t, "prepare", pod.Spec.InitContainers[0].Name) + assert.Equal(t, config.BuildInitImage, pod.Spec.InitContainers[0].Image) + assert.Contains(t, pod.Spec.InitContainers[0].Env, + corev1.EnvVar{ + Name: "BLOB_URL", + Value: "https://some-blobstore.example.com/some-blob", + }) + }) - it("configures prepare with the registry source and empty imagePullSecrets when not provided", func() { - build.Spec.Source.Git = nil - build.Spec.Source.Blob = nil - build.Spec.Source.Registry = &v1alpha1.Registry{ - Image: "some-registry.io/some-image", - } - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("configures prepare with the registry source and empty imagePullSecrets when not provided", func() { + build.Spec.Source.Git = nil + build.Spec.Source.Blob = nil + build.Spec.Source.Registry = &v1alpha1.Registry{ + Image: "some-registry.io/some-image", + } + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - assert.Equal(t, "prepare", pod.Spec.InitContainers[0].Name) - assert.Contains(t, pod.Spec.InitContainers[0].VolumeMounts, - corev1.VolumeMount{ - Name: "image-pull-secrets-dir", - MountPath: "/imagePullSecrets", - ReadOnly: true, - }) - assert.NotNil(t, *pod.Spec.Volumes[5].EmptyDir) - assert.Equal(t, config.BuildInitImage, pod.Spec.InitContainers[0].Image) - assert.Contains(t, pod.Spec.InitContainers[0].Env, - corev1.EnvVar{ - Name: "REGISTRY_IMAGE", - Value: "some-registry.io/some-image", - }) - }) + assert.Equal(t, "prepare", pod.Spec.InitContainers[0].Name) + assert.Contains(t, pod.Spec.InitContainers[0].VolumeMounts, + corev1.VolumeMount{ + Name: "image-pull-secrets-dir", + MountPath: "/imagePullSecrets", + ReadOnly: true, + }) + assert.NotNil(t, *pod.Spec.Volumes[5].EmptyDir) + assert.Equal(t, config.BuildInitImage, pod.Spec.InitContainers[0].Image) + assert.Contains(t, pod.Spec.InitContainers[0].Env, + corev1.EnvVar{ + Name: "REGISTRY_IMAGE", + Value: "some-registry.io/some-image", + }) + }) - it("configures prepare with the registry source and a secret volume when is imagePullSecrets provided", func() { - build.Spec.Source.Git = nil - build.Spec.Source.Blob = nil - build.Spec.Source.Registry = &v1alpha1.Registry{ - Image: "some-registry.io/some-image", - ImagePullSecrets: []corev1.LocalObjectReference{ - {Name: "foo"}, - }, - } - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("configures prepare with the registry source and a secret volume when is imagePullSecrets provided", func() { + build.Spec.Source.Git = nil + build.Spec.Source.Blob = nil + build.Spec.Source.Registry = &v1alpha1.Registry{ + Image: "some-registry.io/some-image", + ImagePullSecrets: []corev1.LocalObjectReference{ + {Name: "registry-secret"}, + }, + } + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - assert.Equal(t, "prepare", pod.Spec.InitContainers[0].Name) - assert.Contains(t, pod.Spec.InitContainers[0].VolumeMounts, - corev1.VolumeMount{ - Name: "image-pull-secrets-dir", - MountPath: "/imagePullSecrets", - ReadOnly: true, - }) - require.NotNil(t, *pod.Spec.Volumes[5].Secret) - assert.Equal(t, "foo", pod.Spec.Volumes[5].Secret.SecretName) - assert.Equal(t, config.BuildInitImage, pod.Spec.InitContainers[0].Image) - assert.Contains(t, pod.Spec.InitContainers[0].Env, - corev1.EnvVar{ - Name: "REGISTRY_IMAGE", - Value: "some-registry.io/some-image", + assert.Equal(t, "prepare", pod.Spec.InitContainers[0].Name) + assert.Contains(t, pod.Spec.InitContainers[0].VolumeMounts, + corev1.VolumeMount{ + Name: "image-pull-secrets-dir", + MountPath: "/imagePullSecrets", + ReadOnly: true, + }) + + match := 0 + for _, v := range pod.Spec.Volumes { + if v.Name == "image-pull-secrets-dir" { + require.NotNil(t, v.Secret) + assert.Equal(t, "registry-secret", v.Secret.SecretName) + match++ + } + } + assert.Equal(t, 1, match) + + assert.Equal(t, config.BuildInitImage, pod.Spec.InitContainers[0].Image) + assert.Contains(t, pod.Spec.InitContainers[0].Env, + corev1.EnvVar{ + Name: "REGISTRY_IMAGE", + Value: "some-registry.io/some-image", + }) + }) + + it("configures detect step", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) + + assert.Equal(t, pod.Spec.InitContainers[1].Name, "detect") + assert.Equal(t, pod.Spec.InitContainers[1].Image, builderImage) + assert.Equal(t, []string{ + "layers-dir", + "platform-dir", + "workspace-dir", + }, names(pod.Spec.InitContainers[1].VolumeMounts)) + }) + + it("configures analyze step", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) + + assert.Equal(t, pod.Spec.InitContainers[2].Name, "analyze") + assert.Equal(t, pod.Spec.InitContainers[2].Image, builderImage) + assert.Equal(t, []string{ + "layers-dir", + "workspace-dir", + "home-dir", + "cache-dir", + }, names(pod.Spec.InitContainers[2].VolumeMounts)) + assert.Equal(t, []string{ + "-layers=/layers", + "-helpers=false", + "-group=/layers/group.toml", + "-analyzed=/layers/analyzed.toml", + "-cache-dir=/cache", + build.Tag(), + }, pod.Spec.InitContainers[2].Args) + }) + + it("configures restore step", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) + + assert.Equal(t, pod.Spec.InitContainers[3].Name, "restore") + assert.Equal(t, pod.Spec.InitContainers[3].Image, builderImage) + assert.Equal(t, []string{ + "layers-dir", + "cache-dir", + }, names(pod.Spec.InitContainers[3].VolumeMounts)) + + assert.Equal(t, []string{ + "-group=/layers/group.toml", + "-layers=/layers", + "-cache-dir=/cache"}, + pod.Spec.InitContainers[3].Args) + }) + + it("configures build step", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) + + assert.Equal(t, pod.Spec.InitContainers[4].Name, "build") + assert.Equal(t, pod.Spec.InitContainers[4].Image, builderImage) + assert.Len(t, pod.Spec.InitContainers[4].VolumeMounts, len([]string{ + "layers-dir", + "platform-dir", + "workspace-dir", + })) + }) + + it("configures export step", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) + + assert.Equal(t, pod.Spec.InitContainers[5].Name, "export") + assert.Equal(t, pod.Spec.InitContainers[5].Image, builderImage) + assert.Equal(t, names(pod.Spec.InitContainers[5].VolumeMounts), []string{ + "layers-dir", + "workspace-dir", + "home-dir", + "cache-dir", }) - }) + assert.Equal(t, []string{ + "-layers=/layers", + "-helpers=false", + "-app=/workspace", + "-group=/layers/group.toml", + "-analyzed=/layers/analyzed.toml", + "-cache-dir=/cache", + build.Tag(), + "someimage/name:tag2", + "someimage/name:tag3", + }, pod.Spec.InitContainers[5].Args) + }) - it("configures detect step", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("configures the builder image in all lifecycle steps", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - assert.Equal(t, pod.Spec.InitContainers[1].Name, "detect") - assert.Equal(t, pod.Spec.InitContainers[1].Image, builderImage) - assert.Len(t, pod.Spec.InitContainers[1].VolumeMounts, len([]string{ - "layers-dir", - "platform-dir", - "workspace-dir", - })) - }) + for _, container := range pod.Spec.InitContainers { + if container.Name != "prepare" { + assert.Equal(t, builderImage, container.Image, fmt.Sprintf("image on container '%s'", container.Name)) + } + } + }) - it("configures restore step", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("configures the completion container with resources", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - assert.Equal(t, pod.Spec.InitContainers[2].Name, "restore") - assert.Equal(t, pod.Spec.InitContainers[2].Image, builderImage) - assert.Len(t, pod.Spec.InitContainers[2].VolumeMounts, len([]string{ - "layers-dir", - "home-dir", - })) - }) + completionContainer := pod.Spec.Containers[0] + assert.Equal(t, resources, completionContainer.Resources) + }) - it("configures analyze step", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("creates a pod with reusable cache when name is provided", func() { + pod, err := build.BuildPod(config, nil, buildPodBuilderConfig) + require.NoError(t, err) - assert.Equal(t, pod.Spec.InitContainers[3].Name, "analyze") - assert.Equal(t, pod.Spec.InitContainers[3].Image, builderImage) - assert.Len(t, pod.Spec.InitContainers[3].VolumeMounts, len([]string{ - "layers-dir", - "workspace-dir", - "home-dir", - })) - assert.Equal(t, []string{ - "-layers=/layers", - "-helpers=false", - "-group=/layers/group.toml", - "-analyzed=/layers/analyzed.toml", - build.Tag(), - }, pod.Spec.InitContainers[3].Args) - }) + require.Len(t, pod.Spec.Volumes, 7) + assert.Equal(t, corev1.Volume{ + Name: "cache-dir", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: "some-cache-name"}, + }, + }, pod.Spec.Volumes[0]) + }) - it("configures build step", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("creates a pod with empty cache when no name is provided", func() { + build.Spec.CacheName = "" + pod, err := build.BuildPod(config, nil, buildPodBuilderConfig) + require.NoError(t, err) - assert.Equal(t, pod.Spec.InitContainers[4].Name, "build") - assert.Equal(t, pod.Spec.InitContainers[4].Image, builderImage) - assert.Len(t, pod.Spec.InitContainers[4].VolumeMounts, len([]string{ - "layers-dir", - "platform-dir", - "workspace-dir", - })) - }) + require.Len(t, pod.Spec.Volumes, 7) + assert.Equal(t, corev1.Volume{ + Name: "cache-dir", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, pod.Spec.Volumes[0]) + }) - it("configures export step", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("attach volumes for secrets", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - assert.Equal(t, pod.Spec.InitContainers[5].Name, "export") - assert.Equal(t, pod.Spec.InitContainers[5].Image, builderImage) - assert.Len(t, pod.Spec.InitContainers[5].VolumeMounts, len([]string{ - "layers-dir", - "workspace-dir", - "home-dir", - })) - assert.Equal(t, []string{ - "-layers=/layers", - "-helpers=false", - "-app=/workspace", - "-group=/layers/group.toml", - "-analyzed=/layers/analyzed.toml", - build.Tag(), - "someimage/name:tag2", - "someimage/name:tag3", - }, pod.Spec.InitContainers[5].Args) - }) + assertSecretPresent(t, pod, "git-secret-1") + assertSecretPresent(t, pod, "docker-secret-1") + assertSecretNotPresent(t, pod, "random-secret-1") + }) - it("configures cache step", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("attach image pull secrets to pod", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - assert.Equal(t, pod.Spec.InitContainers[6].Name, "cache") - assert.Equal(t, pod.Spec.InitContainers[6].Image, builderImage) - assert.Len(t, pod.Spec.InitContainers[6].VolumeMounts, len([]string{ - "layers-dir", - "cache-dir", - })) + require.Len(t, pod.Spec.ImagePullSecrets, 1) + assert.Equal(t, corev1.LocalObjectReference{Name: "some-image-secret"}, pod.Spec.ImagePullSecrets[0]) + }) }) - it("configures the builder image in all lifecycle steps", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + when("0.1 platform api", func() { + buildPodBuilderConfig.PlatformAPI = "0.1" - for _, container := range pod.Spec.InitContainers { - if container.Name != "prepare" { - assert.Equal(t, builderImage, container.Image, fmt.Sprintf("image on container '%s'", container.Name)) - } - } - }) + it("creates init containers with all the build steps", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - it("configures the completion container with resources", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + var names []string + for _, container := range pod.Spec.InitContainers { + names = append(names, container.Name) + } - completionContainer := pod.Spec.Containers[0] - assert.Equal(t, resources, completionContainer.Resources) - }) + assert.Equal(t, []string{ + "prepare", + "detect", + "restore", + "analyze", + "build", + "export", + "cache", + }, names) + }) - it("creates a pod with reusable cache when name is provided", func() { - pod, err := build.BuildPod(config, nil, buildPodBuilderConfig) - require.NoError(t, err) + it("configures restore step", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - require.Len(t, pod.Spec.Volumes, 7) - assert.Equal(t, corev1.Volume{ - Name: "cache-dir", - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: "some-cache-name"}, - }, - }, pod.Spec.Volumes[0]) - }) + assert.Equal(t, "restore", pod.Spec.InitContainers[2].Name) + assert.Equal(t, builderImage, pod.Spec.InitContainers[2].Image) + assert.Equal(t, []string{ + "layers-dir", + "cache-dir", + }, names(pod.Spec.InitContainers[2].VolumeMounts)) + + assert.Equal(t, []string{ + "-group=/layers/group.toml", + "-layers=/layers", + "-path=/cache"}, + pod.Spec.InitContainers[2].Args) + }) - it("creates a pod with empty cache when no name is provided", func() { - build.Spec.CacheName = "" - pod, err := build.BuildPod(config, nil, buildPodBuilderConfig) - require.NoError(t, err) + it("configures analyze step", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - require.Len(t, pod.Spec.Volumes, 7) - assert.Equal(t, corev1.Volume{ - Name: "cache-dir", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, pod.Spec.Volumes[0]) - }) + assert.Equal(t, pod.Spec.InitContainers[3].Name, "analyze") + assert.Equal(t, pod.Spec.InitContainers[3].Image, builderImage) + assert.Equal(t, []string{ + "layers-dir", + "workspace-dir", + "home-dir", + }, names(pod.Spec.InitContainers[3].VolumeMounts)) + assert.Equal(t, []string{ + "-layers=/layers", + "-helpers=false", + "-group=/layers/group.toml", + "-analyzed=/layers/analyzed.toml", + build.Tag(), + }, pod.Spec.InitContainers[3].Args) + }) - it("attach volumes for secrets", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("configures export step", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - assertSecretPresent(t, pod, "git-secret-1") - assertSecretPresent(t, pod, "docker-secret-1") - assertSecretNotPresent(t, pod, "random-secret-1") - }) + assert.Equal(t, pod.Spec.InitContainers[5].Name, "export") + assert.Equal(t, pod.Spec.InitContainers[5].Image, builderImage) + assert.Equal(t, []string{ + "layers-dir", + "workspace-dir", + "home-dir", + }, names(pod.Spec.InitContainers[5].VolumeMounts)) + assert.Equal(t, []string{ + "-layers=/layers", + "-helpers=false", + "-app=/workspace", + "-group=/layers/group.toml", + "-analyzed=/layers/analyzed.toml", + build.Tag(), + "someimage/name:tag2", + "someimage/name:tag3", + }, pod.Spec.InitContainers[5].Args) + }) - it("attach image pull secrets to pod", func() { - pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) - require.NoError(t, err) + it("configures cache step", func() { + pod, err := build.BuildPod(config, secrets, buildPodBuilderConfig) + require.NoError(t, err) - require.Len(t, pod.Spec.ImagePullSecrets, 1) - assert.Equal(t, corev1.LocalObjectReference{Name: "some-image-secret"}, pod.Spec.ImagePullSecrets[0]) + assert.Equal(t, pod.Spec.InitContainers[6].Name, "cache") + assert.Equal(t, pod.Spec.InitContainers[6].Image, builderImage) + assert.Equal(t, []string{ + "layers-dir", + "cache-dir", + }, names(pod.Spec.InitContainers[6].VolumeMounts)) + }) }) when("creating a rebase pod", func() { @@ -555,11 +665,11 @@ func testBuildPod(t *testing.T, when spec.G, it spec.S) { Args: []string{ directExecute, "rebase", - "-basic-docker=docker-secret-1=acr.io", "--run-image", "builderregistry.io/run", "--last-built-image", build.Spec.LastBuild.Image, + "-basic-docker=docker-secret-1=acr.io", "someimage/name", "someimage/name:tag2", "someimage/name:tag3"}, ImagePullPolicy: corev1.PullIfNotPresent, WorkingDir: "/workspace", @@ -607,7 +717,7 @@ func isSecretPresent(t *testing.T, pod *corev1.Pod, secretName string) bool { return false } -func getVolumeMountFromContainer(t *testing.T, containers []corev1.Container, containerName string, volumeName string) corev1.VolumeMount { +func volumeMountFromContainer(t *testing.T, containers []corev1.Container, containerName string, volumeName string) corev1.VolumeMount { t.Helper() for _, container := range containers { if container.Name == containerName { @@ -621,3 +731,10 @@ func getVolumeMountFromContainer(t *testing.T, containers []corev1.Container, co t.Errorf("could not find volume mount with name %s in container %s", volumeName, containerName) return corev1.VolumeMount{} } + +func names(mounts []corev1.VolumeMount) (names []string) { + for _, m := range mounts { + names = append(names, m.Name) + } + return +} diff --git a/pkg/apis/build/v1alpha1/build_test.go b/pkg/apis/build/v1alpha1/build_test.go index 0b856cb63..2621f716c 100644 --- a/pkg/apis/build/v1alpha1/build_test.go +++ b/pkg/apis/build/v1alpha1/build_test.go @@ -1,47 +1,46 @@ -package v1alpha1_test +package v1alpha1 import ( "testing" - "github.com/pivotal/kpack/pkg/apis/build/v1alpha1" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestRebaseable(t *testing.T) { - build := &v1alpha1.Build{ + build := &Build{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - v1alpha1.BuildReasonAnnotation: v1alpha1.BuildReasonStack, + BuildReasonAnnotation: BuildReasonStack, }, }, } - require.False(t, build.Rebasable("any.stack")) + require.False(t, build.rebasable("any.stack")) - build = &v1alpha1.Build{ - Spec: v1alpha1.BuildSpec{ - LastBuild: &v1alpha1.LastBuild{ + build = &Build{ + Spec: BuildSpec{ + LastBuild: &LastBuild{ Image: "some/run", StackId: "matching.stack", }, }, } - require.False(t, build.Rebasable("matching.stack")) + require.False(t, build.rebasable("matching.stack")) - build = &v1alpha1.Build{ + build = &Build{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - v1alpha1.BuildReasonAnnotation: v1alpha1.BuildReasonStack, + BuildReasonAnnotation: BuildReasonStack, }, }, - Spec: v1alpha1.BuildSpec{ - LastBuild: &v1alpha1.LastBuild{ + Spec: BuildSpec{ + LastBuild: &LastBuild{ Image: "some/run", StackId: "matching.stack", }, }, - Status: v1alpha1.BuildStatus{}, + Status: BuildStatus{}, } - require.True(t, build.Rebasable("matching.stack")) + require.True(t, build.rebasable("matching.stack")) } diff --git a/pkg/apis/build/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/build/v1alpha1/zz_generated.deepcopy.go index 4204aece1..12b584d48 100644 --- a/pkg/apis/build/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/build/v1alpha1/zz_generated.deepcopy.go @@ -128,7 +128,6 @@ func (in *BuildList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BuildPodBuilderConfig) DeepCopyInto(out *BuildPodBuilderConfig) { *out = *in - in.BuilderSpec.DeepCopyInto(&out.BuilderSpec) return } diff --git a/pkg/buildpod/generator.go b/pkg/buildpod/generator.go index 8dbf21b5f..c2d7c271d 100644 --- a/pkg/buildpod/generator.go +++ b/pkg/buildpod/generator.go @@ -34,7 +34,16 @@ type Generator struct { ImageFetcher ImageFetcher } -func (g *Generator) Generate(build *v1alpha1.Build) (*v1.Pod, error) { +type BuildPodable interface { + GetName() string + GetNamespace() string + ServiceAccount() string + BuilderSpec() v1alpha1.BuildBuilderSpec + + BuildPod(v1alpha1.BuildPodImages, []corev1.Secret, v1alpha1.BuildPodBuilderConfig) (*corev1.Pod, error) +} + +func (g *Generator) Generate(build BuildPodable) (*v1.Pod, error) { secrets, err := g.fetchBuildSecrets(build) if err != nil { return nil, err @@ -48,14 +57,14 @@ func (g *Generator) Generate(build *v1alpha1.Build) (*v1.Pod, error) { return build.BuildPod(g.BuildPodConfig, secrets, buildPodBuilderConfig) } -func (g *Generator) fetchBuildSecrets(build *v1alpha1.Build) ([]corev1.Secret, error) { +func (g *Generator) fetchBuildSecrets(build BuildPodable) ([]corev1.Secret, error) { var secrets []corev1.Secret - serviceAccount, err := g.K8sClient.CoreV1().ServiceAccounts(build.Namespace).Get(build.Spec.ServiceAccount, metav1.GetOptions{}) + serviceAccount, err := g.K8sClient.CoreV1().ServiceAccounts(build.GetNamespace()).Get(build.ServiceAccount(), metav1.GetOptions{}) if err != nil { return nil, err } for _, secretRef := range serviceAccount.Secrets { - secret, err := g.K8sClient.CoreV1().Secrets(build.Namespace).Get(secretRef.Name, metav1.GetOptions{}) + secret, err := g.K8sClient.CoreV1().Secrets(build.GetNamespace()).Get(secretRef.Name, metav1.GetOptions{}) if err != nil { return nil, err } @@ -64,16 +73,17 @@ func (g *Generator) fetchBuildSecrets(build *v1alpha1.Build) ([]corev1.Secret, e return secrets, nil } -func (g *Generator) fetchBuilderConfig(build *v1alpha1.Build) (v1alpha1.BuildPodBuilderConfig, error) { +func (g *Generator) fetchBuilderConfig(build BuildPodable) (v1alpha1.BuildPodBuilderConfig, error) { keychain, err := g.KeychainFactory.KeychainForSecretRef(registry.SecretRef{ - Namespace: build.Namespace, - ImagePullSecrets: build.Spec.Builder.ImagePullSecrets, + Namespace: build.GetNamespace(), + ImagePullSecrets: build.BuilderSpec().ImagePullSecrets, + ServiceAccount: build.ServiceAccount(), }) if err != nil { return v1alpha1.BuildPodBuilderConfig{}, errors.Wrap(err, "unable to create builder image keychain") } - image, _, err := g.ImageFetcher.Fetch(keychain, build.Spec.Builder.Image) + image, _, err := g.ImageFetcher.Fetch(keychain, build.BuilderSpec().Image) if err != nil { return v1alpha1.BuildPodBuilderConfig{}, errors.Wrap(err, "unable to fetch remote builder image") } @@ -100,9 +110,9 @@ func (g *Generator) fetchBuilderConfig(build *v1alpha1.Build) (v1alpha1.BuildPod } return v1alpha1.BuildPodBuilderConfig{ - BuilderSpec: build.Spec.Builder, StackID: stackId, RunImage: metadata.Stack.RunImage.Image, + PlatformAPI: metadata.Lifecycle.API.PlatformVersion, Uid: uid, Gid: gid, }, nil diff --git a/pkg/buildpod/generator_test.go b/pkg/buildpod/generator_test.go index 22f6b5d11..41b07feb6 100644 --- a/pkg/buildpod/generator_test.go +++ b/pkg/buildpod/generator_test.go @@ -7,16 +7,16 @@ import ( ggcrv1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/sclevine/spec" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" "github.com/pivotal/kpack/pkg/apis/build/v1alpha1" "github.com/pivotal/kpack/pkg/buildpod" "github.com/pivotal/kpack/pkg/cnb" - "github.com/pivotal/kpack/pkg/duckbuilder" "github.com/pivotal/kpack/pkg/registry" "github.com/pivotal/kpack/pkg/registry/imagehelpers" "github.com/pivotal/kpack/pkg/registry/registryfakes" @@ -97,31 +97,55 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) { }, } - fakeK8sClient := fake.NewSimpleClientset(serviceAccount, dockerSecret, gitSecret, ignoredSecret) - - builder := &duckbuilder.DuckBuilder{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.BuilderKind, - APIVersion: "v1alpha1", - }, - Status: v1alpha1.BuilderStatus{ - LatestImage: "some/builde@sha256:1b2911dd8eabb4bdb0bda6705158daa4149adb5ca59dc990146772c4c6deecb4", + builderPullSecrets := []v1.LocalObjectReference{ + { + Name: "some-builder-pull-secrets", }, } + fakeK8sClient := fake.NewSimpleClientset(serviceAccount, dockerSecret, gitSecret, ignoredSecret) + it("returns pod config with secrets on build's service account", func() { secretRef := registry.SecretRef{ + ServiceAccount: serviceAccountName, Namespace: namespace, - ImagePullSecrets: builder.Spec.ImagePullSecrets, + ImagePullSecrets: builderPullSecrets, } keychain := ®istryfakes.FakeKeychain{} keychainFactory.AddKeychainForSecretRef(t, secretRef, keychain) image := randomImage(t) - image, _ = imagehelpers.SetStringLabel(image, metadata.StackMetadataLabel, "some.stack.id") - image, _ = imagehelpers.SetStringLabel(image, cnb.BuilderMetadataLabel, `{ "stack": { "runImage": { "image": "some-registry.io/run-image"} } }`) - image, _ = imagehelpers.SetEnv(image, "CNB_USER_ID", "1234") - image, _ = imagehelpers.SetEnv(image, "CNB_GROUP_ID", "5678") + var err error + image, err = imagehelpers.SetStringLabel(image, metadata.StackMetadataLabel, "some.stack.id") + require.NoError(t, err) + + image, err = imagehelpers.SetStringLabel(image, cnb.BuilderMetadataLabel, //language=json + `{ "stack": { "runImage": { "image": "some-registry.io/run-image"} } }`) + require.NoError(t, err) + + image, err = imagehelpers.SetStringLabel(image, cnb.BuilderMetadataLabel, //language=json + `{ + "stack": { + "runImage": { + "image": "some-registry.io/run-image" + } + }, + "lifecycle": { + "version": "0.9.0", + "api": { + "buildpack": "0.7", + "platform": "0.5" + } + } +}`) + require.NoError(t, err) + + image, err = imagehelpers.SetEnv(image, "CNB_USER_ID", "1234") + require.NoError(t, err) + + image, err = imagehelpers.SetEnv(image, "CNB_GROUP_ID", "5678") + require.NoError(t, err) + imageFetcher.AddImage("some/builde@sha256:1b2911dd8eabb4bdb0bda6705158daa4149adb5ca59dc990146772c4c6deecb4", image, keychain) buildPodConfig := v1alpha1.BuildPodImages{} @@ -132,58 +156,33 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) { ImageFetcher: imageFetcher, } - build := &v1alpha1.Build{ - ObjectMeta: metav1.ObjectMeta{ - Name: "simple-build", - Namespace: namespace, - }, - Spec: v1alpha1.BuildSpec{ - Tags: []string{ - "builderImage/name", - "additional/names", - }, - Builder: builder.BuildBuilderSpec(), - ServiceAccount: serviceAccountName, - Source: v1alpha1.SourceConfig{ - Git: &v1alpha1.Git{ - URL: "http://www.google.com", - Revision: "master", - }, - }, - CacheName: "some-cache-name", - Env: []corev1.EnvVar{ - { - Name: "ENV", - Value: "NAME", - }, - }, - Resources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("2"), - corev1.ResourceMemory: resource.MustParse("256M"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("1"), - corev1.ResourceMemory: resource.MustParse("128M"), - }, - }, + var build = &testBuildPodable{ + serviceAccount: serviceAccountName, + namespace: namespace, + buildBuilderSpec: v1alpha1.BuildBuilderSpec{ + Image: "some/builde@sha256:1b2911dd8eabb4bdb0bda6705158daa4149adb5ca59dc990146772c4c6deecb4", + ImagePullSecrets: builderPullSecrets, }, } + pod, err := generator.Generate(build) require.NoError(t, err) + assert.NotNil(t, pod) - expectedPod, err := build.BuildPod(buildPodConfig, []corev1.Secret{ - *gitSecret, - *dockerSecret, - }, v1alpha1.BuildPodBuilderConfig{ - BuilderSpec: builder.BuildBuilderSpec(), - StackID: "some.stack.id", - RunImage: "some-registry.io/run-image", - Uid: 1234, - Gid: 5678, - }) - require.NoError(t, err) - require.Equal(t, expectedPod, pod) + assert.Equal(t, []buildPodCall{{ + BuildPodImages: buildPodConfig, + Secrets: []corev1.Secret{ + *gitSecret, + *dockerSecret, + }, + BuildPodBuilderConfig: v1alpha1.BuildPodBuilderConfig{ + StackID: "some.stack.id", + RunImage: "some-registry.io/run-image", + Uid: 1234, + Gid: 5678, + PlatformAPI: "0.5", + }, + }}, build.buildPodCalls) }) }) } @@ -193,3 +192,41 @@ func randomImage(t *testing.T) ggcrv1.Image { require.NoError(t, err) return image } + +type testBuildPodable struct { + buildBuilderSpec v1alpha1.BuildBuilderSpec + serviceAccount string + namespace string + buildPodCalls []buildPodCall +} + +type buildPodCall struct { + BuildPodImages v1alpha1.BuildPodImages + Secrets []corev1.Secret + BuildPodBuilderConfig v1alpha1.BuildPodBuilderConfig +} + +func (tb *testBuildPodable) GetName() string { + panic("should not be used in this test") +} + +func (tb *testBuildPodable) GetNamespace() string { + return tb.namespace +} + +func (tb *testBuildPodable) ServiceAccount() string { + return tb.serviceAccount +} + +func (tb *testBuildPodable) BuilderSpec() v1alpha1.BuildBuilderSpec { + return tb.buildBuilderSpec +} + +func (tb *testBuildPodable) BuildPod(images v1alpha1.BuildPodImages, secrets []corev1.Secret, config v1alpha1.BuildPodBuilderConfig) (*corev1.Pod, error) { + tb.buildPodCalls = append(tb.buildPodCalls, buildPodCall{ + BuildPodImages: images, + Secrets: secrets, + BuildPodBuilderConfig: config, + }) + return &corev1.Pod{}, nil +} diff --git a/pkg/cnb/cnb_metadata.go b/pkg/cnb/cnb_metadata.go index 29dc8fe88..ed914fa8d 100644 --- a/pkg/cnb/cnb_metadata.go +++ b/pkg/cnb/cnb_metadata.go @@ -131,7 +131,7 @@ func readBuiltImage(appImage ggcrv1.Image, appImageId string) (BuiltImage, error return BuiltImage{}, err } - var layerMetadata metadata.LayersMetadata + var layerMetadata appLayersMetadata err = imagehelpers.GetLabel(appImage, metadata.LayerMetadataLabel, &layerMetadata) if err != nil { return BuiltImage{}, err @@ -162,3 +162,13 @@ func readBuiltImage(appImage ggcrv1.Image, appImageId string) (BuiltImage, error }, }, nil } + +type appLayersMetadata struct { + RunImage runImageAppMetadata `json:"runImage" toml:"run-image"` + Stack StackMetadata `json:"stack" toml:"stack"` +} + +type runImageAppMetadata struct { + TopLayer string `json:"topLayer" toml:"top-layer"` + Reference string `json:"reference" toml:"reference"` +} diff --git a/pkg/cnb/cnb_metadata_test.go b/pkg/cnb/cnb_metadata_test.go index b6ed836e8..7359f79ca 100644 --- a/pkg/cnb/cnb_metadata_test.go +++ b/pkg/cnb/cnb_metadata_test.go @@ -101,43 +101,119 @@ func testMetadataRetriever(t *testing.T, when spec.G, it spec.S) { Status: v1alpha1.BuildStatus{}, } - it("retrieves the metadata from the registry", func() { - appImageSecretRef := registry.SecretRef{ - ServiceAccount: build.Spec.ServiceAccount, - Namespace: build.Namespace, - } - appImageKeychain := ®istryfakes.FakeKeychain{} - keychainFactory.AddKeychainForSecretRef(t, appImageSecretRef, appImageKeychain) - - appImage := randomImage(t) - appImage, _ = imagehelpers.SetStringLabel(appImage, "io.buildpacks.build.metadata", `{"buildpacks": [{"id": "test.id", "version": "1.2.3"}]}`) - appImage, _ = imagehelpers.SetStringLabel(appImage, "io.buildpacks.lifecycle.metadata", `{"runImage":{"topLayer":"sha256:719f3f610dade1fdf5b4b2473aea0c6b1317497cf20691ab6d184a9b2fa5c409","reference":"localhost:5000/node@sha256:0fd6395e4fe38a0c089665cbe10f52fb26fc64b4b15e672ada412bd7ab5499a0"},"stack":{"runImage":{"image":"gcr.io:443/run:full-cnb"}}}`) - appImage, _ = imagehelpers.SetStringLabel(appImage, "io.buildpacks.stack.id", "io.buildpacks.stack.bionic") - imageFetcher.AddImage("reg.io/appimage/name", appImage, appImageKeychain) - - subject := cnb.RemoteMetadataRetriever{ - KeychainFactory: keychainFactory, - ImageFetcher: imageFetcher, - } - - result, err := subject.GetBuiltImage(build) - assert.NoError(t, err) - - metadata := result.BuildpackMetadata - require.Len(t, metadata, 1) - assert.Equal(t, "test.id", metadata[0].ID) - assert.Equal(t, "1.2.3", metadata[0].Version) - - createdAtTime, err := imagehelpers.GetCreatedAt(appImage) - assert.NoError(t, err) - - assert.Equal(t, createdAtTime, result.CompletedAt) - assert.Equal(t, "gcr.io:443/run@sha256:0fd6395e4fe38a0c089665cbe10f52fb26fc64b4b15e672ada412bd7ab5499a0", result.Stack.RunImage) - assert.Equal(t, "io.buildpacks.stack.bionic", result.Stack.ID) + when("images are built with lifecycle 0.5", func() { + + it("retrieves the metadata from the registry", func() { + appImageSecretRef := registry.SecretRef{ + ServiceAccount: build.Spec.ServiceAccount, + Namespace: build.Namespace, + } + appImageKeychain := ®istryfakes.FakeKeychain{} + keychainFactory.AddKeychainForSecretRef(t, appImageSecretRef, appImageKeychain) + + appImage := randomImage(t) + appImage, _ = imagehelpers.SetStringLabel(appImage, "io.buildpacks.build.metadata", `{"buildpacks": [{"id": "test.id", "version": "1.2.3"}]}`) + appImage, _ = imagehelpers.SetStringLabel(appImage, "io.buildpacks.lifecycle.metadata", `{ + "app": { + "sha": "sha256:119f3f610dade1fdf5b4b2473aea0c6b1317497cf20691ab6d184a9b2fa5c409" + }, + "runImage": { + "topLayer": "sha256:719f3f610dade1fdf5b4b2473aea0c6b1317497cf20691ab6d184a9b2fa5c409", + "reference": "localhost:5000/node@sha256:0fd6395e4fe38a0c089665cbe10f52fb26fc64b4b15e672ada412bd7ab5499a0" + }, + "stack": { + "runImage": { + "image": "gcr.io:443/run:full-cnb" + } + } +}`) + appImage, _ = imagehelpers.SetStringLabel(appImage, "io.buildpacks.stack.id", "io.buildpacks.stack.bionic") + imageFetcher.AddImage("reg.io/appimage/name", appImage, appImageKeychain) + + subject := cnb.RemoteMetadataRetriever{ + KeychainFactory: keychainFactory, + ImageFetcher: imageFetcher, + } + + result, err := subject.GetBuiltImage(build) + assert.NoError(t, err) + + metadata := result.BuildpackMetadata + require.Len(t, metadata, 1) + assert.Equal(t, "test.id", metadata[0].ID) + assert.Equal(t, "1.2.3", metadata[0].Version) + + createdAtTime, err := imagehelpers.GetCreatedAt(appImage) + assert.NoError(t, err) + + assert.Equal(t, createdAtTime, result.CompletedAt) + assert.Equal(t, "gcr.io:443/run@sha256:0fd6395e4fe38a0c089665cbe10f52fb26fc64b4b15e672ada412bd7ab5499a0", result.Stack.RunImage) + assert.Equal(t, "io.buildpacks.stack.bionic", result.Stack.ID) + + digest, err := appImage.Digest() + require.NoError(t, err) + assert.Equal(t, "reg.io/appimage/name@"+digest.String(), result.Identifier) + }) + }) - digest, err := appImage.Digest() - require.NoError(t, err) - assert.Equal(t, "reg.io/appimage/name@"+digest.String(), result.Identifier) + when("images are built with lifecycle 0.6+", func() { + + it("retrieves the metadata from the registry", func() { + appImageSecretRef := registry.SecretRef{ + ServiceAccount: build.Spec.ServiceAccount, + Namespace: build.Namespace, + } + appImageKeychain := ®istryfakes.FakeKeychain{} + keychainFactory.AddKeychainForSecretRef(t, appImageSecretRef, appImageKeychain) + + appImage := randomImage(t) + appImage, _ = imagehelpers.SetStringLabel(appImage, "io.buildpacks.build.metadata", `{"buildpacks": [{"id": "test.id", "version": "1.2.3"}]}`) + appImage, _ = imagehelpers.SetStringLabel(appImage, "io.buildpacks.lifecycle.metadata", `{ + "app": [ + { + "sha": "sha256:919f3f610dade1fdf5b4b2473aea0c6b1317497cf20691ab6d184a9b2fa5c409" + }, + { + "sha": "sha256:119f3f610dade1fdf5b4b2473aea0c6b1317497cf20691ab6d184a9b2fa5c409" + } + ], + "runImage": { + "topLayer": "sha256:719f3f610dade1fdf5b4b2473aea0c6b1317497cf20691ab6d184a9b2fa5c409", + "reference": "localhost:5000/node@sha256:0fd6395e4fe38a0c089665cbe10f52fb26fc64b4b15e672ada412bd7ab5499a0" + }, + "stack": { + "runImage": { + "image": "gcr.io:443/run:full-cnb" + } + } +}`) + appImage, _ = imagehelpers.SetStringLabel(appImage, "io.buildpacks.stack.id", "io.buildpacks.stack.bionic") + imageFetcher.AddImage("reg.io/appimage/name", appImage, appImageKeychain) + + subject := cnb.RemoteMetadataRetriever{ + KeychainFactory: keychainFactory, + ImageFetcher: imageFetcher, + } + + result, err := subject.GetBuiltImage(build) + assert.NoError(t, err) + + metadata := result.BuildpackMetadata + require.Len(t, metadata, 1) + assert.Equal(t, "test.id", metadata[0].ID) + assert.Equal(t, "1.2.3", metadata[0].Version) + + createdAtTime, err := imagehelpers.GetCreatedAt(appImage) + assert.NoError(t, err) + + assert.Equal(t, createdAtTime, result.CompletedAt) + assert.Equal(t, "gcr.io:443/run@sha256:0fd6395e4fe38a0c089665cbe10f52fb26fc64b4b15e672ada412bd7ab5499a0", result.Stack.RunImage) + assert.Equal(t, "io.buildpacks.stack.bionic", result.Stack.ID) + + digest, err := appImage.Digest() + require.NoError(t, err) + assert.Equal(t, "reg.io/appimage/name@"+digest.String(), result.Identifier) + }) }) }) }) diff --git a/pkg/reconciler/v1alpha1/build/build.go b/pkg/reconciler/v1alpha1/build/build.go index 6365ba9a4..ada180c34 100644 --- a/pkg/reconciler/v1alpha1/build/build.go +++ b/pkg/reconciler/v1alpha1/build/build.go @@ -15,6 +15,7 @@ import ( "github.com/pivotal/kpack/pkg/apis/build/v1alpha1" corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" + "github.com/pivotal/kpack/pkg/buildpod" "github.com/pivotal/kpack/pkg/client/clientset/versioned" v1alpha1informer "github.com/pivotal/kpack/pkg/client/informers/externalversions/build/v1alpha1" v1alpha1lister "github.com/pivotal/kpack/pkg/client/listers/build/v1alpha1" @@ -33,7 +34,7 @@ type MetadataRetriever interface { } type PodGenerator interface { - Generate(*v1alpha1.Build) (*corev1.Pod, error) + Generate(build buildpod.BuildPodable) (*corev1.Pod, error) } func NewController(opt reconciler.Options, k8sClient k8sclient.Interface, informer v1alpha1informer.BuildInformer, podInformer corev1Informers.PodInformer, metadataRetriever MetadataRetriever, podGenerator PodGenerator) *controller.Impl { diff --git a/pkg/reconciler/v1alpha1/build/build_test.go b/pkg/reconciler/v1alpha1/build/build_test.go index 5ad619b3c..458f5beb5 100644 --- a/pkg/reconciler/v1alpha1/build/build_test.go +++ b/pkg/reconciler/v1alpha1/build/build_test.go @@ -16,11 +16,11 @@ import ( clientgotesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/record" "knative.dev/pkg/controller" - "knative.dev/pkg/kmeta" rtesting "knative.dev/pkg/reconciler/testing" "github.com/pivotal/kpack/pkg/apis/build/v1alpha1" corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" + "github.com/pivotal/kpack/pkg/buildpod" "github.com/pivotal/kpack/pkg/client/clientset/versioned/fake" "github.com/pivotal/kpack/pkg/cnb" "github.com/pivotal/kpack/pkg/reconciler/testhelpers" @@ -717,16 +717,12 @@ func testBuildReconciler(t *testing.T, when spec.G, it spec.S) { type testPodGenerator struct { } -func (testPodGenerator) Generate(build *v1alpha1.Build) (*corev1.Pod, error) { +func (testPodGenerator) Generate(build buildpod.BuildPodable) (*corev1.Pod, error) { return &corev1.Pod{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ - Name: build.PodName(), - Namespace: build.Namespace, - Labels: build.Labels, - OwnerReferences: []metav1.OwnerReference{ - *kmeta.NewControllerRef(build), - }, + Name: build.GetName() + "-build-pod", + Namespace: build.GetNamespace(), }, Spec: corev1.PodSpec{ InitContainers: []corev1.Container{ @@ -740,7 +736,7 @@ func (testPodGenerator) Generate(build *v1alpha1.Build) (*corev1.Pod, error) { Name: "step-3", }, }, - ImagePullSecrets: build.Spec.Builder.ImagePullSecrets, + ImagePullSecrets: build.BuilderSpec().ImagePullSecrets, }, }, nil }