package strategy
import (
"fmt"
"path/filepath"
"strconv"
"github.com/golang/glog"
buildapi "github.com/openshift/origin/pkg/build/api"
"github.com/openshift/origin/pkg/build/builder/cmd/dockercfg"
imageapi "github.com/openshift/origin/pkg/image/api"
"github.com/openshift/origin/pkg/util/namer"
"github.com/openshift/origin/pkg/version"
kapi "k8s.io/kubernetes/pkg/api"
kvalidation "k8s.io/kubernetes/pkg/util/validation"
)
const (
// dockerSocketPath is the default path for the Docker socket inside the builder container
dockerSocketPath = "/var/run/docker.sock"
DockerPushSecretMountPath = "/var/run/secrets/openshift.io/push"
DockerPullSecretMountPath = "/var/run/secrets/openshift.io/pull"
SecretBuildSourceBaseMountPath = "/var/run/secrets/openshift.io/build"
SourceImagePullSecretMountPath = "/var/run/secrets/openshift.io/source-image"
sourceSecretMountPath = "/var/run/secrets/openshift.io/source"
)
var whitelistEnvVarNames = []string{"BUILD_LOGLEVEL", "GIT_SSL_NO_VERIFY"}
// FatalError is an error which can't be retried.
type FatalError string
// Error implements the error interface.
func (e FatalError) Error() string {
return string(e)
}
// IsFatal returns true if the error is fatal
func IsFatal(err error) bool {
_, isFatal := err.(FatalError)
return isFatal
}
// setupDockerSocket configures the pod to support the host's Docker socket
func setupDockerSocket(podSpec *kapi.Pod) {
dockerSocketVolume := kapi.Volume{
Name: "docker-socket",
VolumeSource: kapi.VolumeSource{
HostPath: &kapi.HostPathVolumeSource{
Path: dockerSocketPath,
},
},
}
dockerSocketVolumeMount := kapi.VolumeMount{
Name: "docker-socket",
MountPath: dockerSocketPath,
}
podSpec.Spec.Volumes = append(podSpec.Spec.Volumes,
dockerSocketVolume)
podSpec.Spec.Containers[0].VolumeMounts =
append(podSpec.Spec.Containers[0].VolumeMounts,
dockerSocketVolumeMount)
}
// mountSecretVolume is a helper method responsible for actual mounting secret
// volumes into a pod.
func mountSecretVolume(pod *kapi.Pod, secretName, mountPath, volumeSuffix string) {
volumeName := namer.GetName(secretName, volumeSuffix, kvalidation.DNS1123SubdomainMaxLength)
volume := kapi.Volume{
Name: volumeName,
VolumeSource: kapi.VolumeSource{
Secret: &kapi.SecretVolumeSource{
SecretName: secretName,
},
},
}
volumeMount := kapi.VolumeMount{
Name: volumeName,
MountPath: mountPath,
ReadOnly: true,
}
pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, volumeMount)
}
// setupDockerSecrets mounts Docker Registry secrets into Pod running the build,
// allowing Docker to authenticate against private registries or Docker Hub.
func setupDockerSecrets(pod *kapi.Pod, pushSecret, pullSecret *kapi.LocalObjectReference, imageSources []buildapi.ImageSource) {
if pushSecret != nil {
mountSecretVolume(pod, pushSecret.Name, DockerPushSecretMountPath, "push")
pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
{Name: dockercfg.PushAuthType, Value: DockerPushSecretMountPath},
}...)
glog.V(3).Infof("%s will be used for docker push in %s", DockerPushSecretMountPath, pod.Name)
}
if pullSecret != nil {
mountSecretVolume(pod, pullSecret.Name, DockerPullSecretMountPath, "pull")
pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
{Name: dockercfg.PullAuthType, Value: DockerPullSecretMountPath},
}...)
glog.V(3).Infof("%s will be used for docker pull in %s", DockerPullSecretMountPath, pod.Name)
}
for i, imageSource := range imageSources {
if imageSource.PullSecret == nil {
continue
}
mountPath := filepath.Join(SourceImagePullSecretMountPath, strconv.Itoa(i))
mountSecretVolume(pod, imageSource.PullSecret.Name, mountPath, fmt.Sprintf("%s%d", "source-image", i))
pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
{Name: fmt.Sprintf("%s%d", dockercfg.PullSourceAuthType, i), Value: mountPath},
}...)
glog.V(3).Infof("%s will be used for docker pull in %s", mountPath, pod.Name)
}
}
// setupSourceSecrets mounts SSH key used for accessing private SCM to clone
// application source code during build.
func setupSourceSecrets(pod *kapi.Pod, sourceSecret *kapi.LocalObjectReference) {
if sourceSecret == nil {
return
}
mountSecretVolume(pod, sourceSecret.Name, sourceSecretMountPath, "source")
glog.V(3).Infof("Installed source secrets in %s, in Pod %s/%s", sourceSecretMountPath, pod.Namespace, pod.Name)
pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
{Name: "SOURCE_SECRET_PATH", Value: sourceSecretMountPath},
}...)
}
// setupSecrets mounts the secrets referenced by the SecretBuildSource
// into a builder container. It also sets an environment variable that contains
// a name of the secret and the destination directory.
func setupSecrets(pod *kapi.Pod, secrets []buildapi.SecretBuildSource) {
for _, s := range secrets {
mountSecretVolume(pod, s.Secret.Name, filepath.Join(SecretBuildSourceBaseMountPath, s.Secret.Name), "build")
glog.V(3).Infof("%s will be used as a build secret in %s", s.Secret.Name, SecretBuildSourceBaseMountPath)
}
}
// addSourceEnvVars adds environment variables related to the source code
// repository to builder container
func addSourceEnvVars(source buildapi.BuildSource, output *[]kapi.EnvVar) {
sourceVars := []kapi.EnvVar{}
if source.Git != nil {
sourceVars = append(sourceVars, kapi.EnvVar{Name: "SOURCE_REPOSITORY", Value: source.Git.URI})
sourceVars = append(sourceVars, kapi.EnvVar{Name: "SOURCE_URI", Value: source.Git.URI})
}
if len(source.ContextDir) > 0 {
sourceVars = append(sourceVars, kapi.EnvVar{Name: "SOURCE_CONTEXT_DIR", Value: source.ContextDir})
}
if source.Git != nil && len(source.Git.Ref) > 0 {
sourceVars = append(sourceVars, kapi.EnvVar{Name: "SOURCE_REF", Value: source.Git.Ref})
}
*output = append(*output, sourceVars...)
}
func addOriginVersionVar(output *[]kapi.EnvVar) {
version := kapi.EnvVar{Name: buildapi.OriginVersion, Value: version.Get().String()}
*output = append(*output, version)
}
// addOutputEnvVars adds env variables that provide information about the output
// target for the build
func addOutputEnvVars(buildOutput *kapi.ObjectReference, output *[]kapi.EnvVar) error {
if buildOutput == nil {
return nil
}
// output must always be a DockerImage type reference at this point.
if buildOutput.Kind != "DockerImage" {
return fmt.Errorf("invalid build output kind %s, must be DockerImage", buildOutput.Kind)
}
ref, err := imageapi.ParseDockerImageReference(buildOutput.Name)
if err != nil {
return err
}
registry := ref.Registry
ref.Registry = ""
image := ref.String()
outputVars := []kapi.EnvVar{
{Name: "OUTPUT_REGISTRY", Value: registry},
{Name: "OUTPUT_IMAGE", Value: image},
}
*output = append(*output, outputVars...)
return nil
}
// setupAdditionalSecrets creates secret volume mounts in the given pod for the given list of secrets
func setupAdditionalSecrets(pod *kapi.Pod, secrets []buildapi.SecretSpec) {
for _, secretSpec := range secrets {
mountSecretVolume(pod, secretSpec.SecretSource.Name, secretSpec.MountPath, "secret")
glog.V(3).Infof("Installed additional secret in %s, in Pod %s/%s", secretSpec.MountPath, pod.Namespace, pod.Name)
}
}
// mergeTrustedEnvWithoutDuplicates merges two environment lists without having
// duplicate items in the output list. Only trusted environment variables
// will be merged.
func mergeTrustedEnvWithoutDuplicates(source []kapi.EnvVar, output *[]kapi.EnvVar) {
// filter out all environment variables except trusted/well known
// values, because we do not want random environment variables being
// fed into the privileged STI container via the BuildConfig definition.
type sourceMapItem struct {
index int
value string
}
index := 0
filteredSourceMap := make(map[string]sourceMapItem)
filteredSource := []kapi.EnvVar{}
for _, env := range source {
for _, acceptable := range whitelistEnvVarNames {
if env.Name == acceptable {
filteredSource = append(filteredSource, env)
filteredSourceMap[env.Name] = sourceMapItem{index, env.Value}
index++
break
}
}
}
result := *output
for i, env := range result {
// If the value exists in output, override it and remove it
// from the source list
if v, found := filteredSourceMap[env.Name]; found {
result[i].Value = v.value
filteredSource = append(filteredSource[:v.index], filteredSource[v.index+1:]...)
}
}
*output = append(result, filteredSource...)
}
// getContainerVerbosity returns the defined BUILD_LOGLEVEL value
func getContainerVerbosity(containerEnv []kapi.EnvVar) (verbosity string) {
for _, env := range containerEnv {
if env.Name == "BUILD_LOGLEVEL" {
verbosity = env.Value
break
}
}
return
}
// getPodLabels creates labels for the Build Pod
func getPodLabels(build *buildapi.Build) map[string]string {
return map[string]string{buildapi.BuildLabel: buildapi.LabelValue(build.Name)}
}