package cmd import ( "fmt" "io" "io/ioutil" "os" "os/exec" "path/filepath" docker "github.com/fsouza/go-dockerclient" "github.com/golang/glog" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/runtime" s2iapi "github.com/openshift/source-to-image/pkg/api" "github.com/openshift/origin/pkg/build/api" "github.com/openshift/origin/pkg/build/api/validation" bld "github.com/openshift/origin/pkg/build/builder" "github.com/openshift/origin/pkg/build/builder/cmd/scmauth" "github.com/openshift/origin/pkg/client" dockerutil "github.com/openshift/origin/pkg/cmd/util/docker" "github.com/openshift/origin/pkg/generate/git" "github.com/openshift/origin/pkg/version" ) type builder interface { Build(dockerClient bld.DockerClient, sock string, buildsClient client.BuildInterface, build *api.Build, gitClient bld.GitClient, cgLimits *s2iapi.CGroupLimits) error } type builderConfig struct { out io.Writer build *api.Build sourceSecretDir string dockerClient *docker.Client dockerEndpoint string buildsClient client.BuildInterface } func newBuilderConfigFromEnvironment(out io.Writer) (*builderConfig, error) { cfg := &builderConfig{} var err error cfg.out = out // build (BUILD) buildStr := os.Getenv("BUILD") glog.V(4).Infof("$BUILD env var is %s \n", buildStr) cfg.build = &api.Build{} if err := runtime.DecodeInto(kapi.Codecs.UniversalDecoder(), []byte(buildStr), cfg.build); err != nil { return nil, fmt.Errorf("unable to parse build: %v", err) } if errs := validation.ValidateBuild(cfg.build); len(errs) > 0 { return nil, errors.NewInvalid(unversioned.GroupKind{Kind: "Build"}, cfg.build.Name, errs) } glog.V(4).Infof("Build: %#v", cfg.build) masterVersion := os.Getenv(api.OriginVersion) thisVersion := version.Get().String() if len(masterVersion) != 0 && masterVersion != thisVersion { glog.V(3).Infof("warning: OpenShift server version %q differs from this image %q\n", masterVersion, thisVersion) } else { glog.V(4).Infof("Master version %q, Builder version %q", masterVersion, thisVersion) } // sourceSecretsDir (SOURCE_SECRET_PATH) cfg.sourceSecretDir = os.Getenv("SOURCE_SECRET_PATH") // dockerClient and dockerEndpoint (DOCKER_HOST) // usually not set, defaults to docker socket cfg.dockerClient, cfg.dockerEndpoint, err = dockerutil.NewHelper().GetClient() if err != nil { return nil, fmt.Errorf("no Docker configuration defined: %v", err) } // buildsClient (KUBERNETES_SERVICE_HOST, KUBERNETES_SERVICE_PORT) clientConfig, err := restclient.InClusterConfig() if err != nil { return nil, fmt.Errorf("cannot connect to the server: %v", err) } osClient, err := client.New(clientConfig) if err != nil { return nil, fmt.Errorf("failed to get client: %v", err) } cfg.buildsClient = osClient.Builds(cfg.build.Namespace) return cfg, nil } func (c *builderConfig) setupGitEnvironment() (string, []string, error) { var sourceSecretDir string var errSecret error // For now, we only handle git. If not specified, we're done gitSource := c.build.Spec.Source.Git if gitSource == nil { return "", []string{}, nil } sourceSecret := c.build.Spec.Source.SourceSecret gitEnv := []string{"GIT_ASKPASS=true"} // If a source secret is present, set it up and add its environment variables if sourceSecret != nil { // TODO: this should be refactored to let each source type manage which secrets // it accepts sourceURL, err := git.ParseRepository(gitSource.URI) if err != nil { return "", nil, fmt.Errorf("cannot parse build URL: %s", gitSource.URI) } scmAuths := scmauth.GitAuths(sourceURL) // TODO: remove when not necessary to fix up the secret dir permission sourceSecretDir, errSecret = fixSecretPermissions(c.sourceSecretDir) if errSecret != nil { return sourceSecretDir, nil, fmt.Errorf("cannot fix source secret permissions: %v", errSecret) } secretsEnv, overrideURL, err := scmAuths.Setup(sourceSecretDir) if err != nil { return sourceSecretDir, nil, fmt.Errorf("cannot setup source secret: %v", err) } if overrideURL != nil { c.build.Annotations[bld.OriginalSourceURLAnnotationKey] = gitSource.URI gitSource.URI = overrideURL.String() } gitEnv = append(gitEnv, secretsEnv...) } if gitSource.HTTPProxy != nil && len(*gitSource.HTTPProxy) > 0 { gitEnv = append(gitEnv, fmt.Sprintf("HTTP_PROXY=%s", *gitSource.HTTPProxy)) gitEnv = append(gitEnv, fmt.Sprintf("http_proxy=%s", *gitSource.HTTPProxy)) } if gitSource.HTTPSProxy != nil && len(*gitSource.HTTPSProxy) > 0 { gitEnv = append(gitEnv, fmt.Sprintf("HTTPS_PROXY=%s", *gitSource.HTTPSProxy)) gitEnv = append(gitEnv, fmt.Sprintf("https_proxy=%s", *gitSource.HTTPSProxy)) } if gitSource.NoProxy != nil && len(*gitSource.NoProxy) > 0 { gitEnv = append(gitEnv, fmt.Sprintf("NO_PROXY=%s", *gitSource.NoProxy)) gitEnv = append(gitEnv, fmt.Sprintf("no_proxy=%s", *gitSource.NoProxy)) } return sourceSecretDir, bld.MergeEnv(os.Environ(), gitEnv), nil } // execute is responsible for running a build func (c *builderConfig) execute(b builder) error { secretTmpDir, gitEnv, err := c.setupGitEnvironment() if err != nil { return err } gitClient := git.NewRepositoryWithEnv(gitEnv) cgLimits, err := bld.GetCGroupLimits() if err != nil { return fmt.Errorf("failed to retrieve cgroup limits: %v", err) } glog.V(4).Infof("Running build with cgroup limits: %#v", *cgLimits) if err := b.Build(c.dockerClient, c.dockerEndpoint, c.buildsClient, c.build, gitClient, cgLimits); err != nil { return fmt.Errorf("build error: %v", err) } if c.build.Spec.Output.To == nil || len(c.build.Spec.Output.To.Name) == 0 { fmt.Fprintf(c.out, "Build complete, no image push requested\n") } os.RemoveAll(secretTmpDir) return nil } // fixSecretPermissions loweres access permissions to very low acceptable level // TODO: this method should be removed as soon as secrets permissions are fixed upstream // Kubernetes issue: https://github.com/kubernetes/kubernetes/issues/4789 func fixSecretPermissions(secretsDir string) (string, error) { secretTmpDir, err := ioutil.TempDir("", "tmpsecret") if err != nil { return "", err } cmd := exec.Command("cp", "-R", ".", secretTmpDir) cmd.Dir = secretsDir if err := cmd.Run(); err != nil { return "", err } secretFiles, err := ioutil.ReadDir(secretTmpDir) if err != nil { return "", err } for _, file := range secretFiles { if err := os.Chmod(filepath.Join(secretTmpDir, file.Name()), 0600); err != nil { return "", err } } return secretTmpDir, nil } type dockerBuilder struct{} // Build starts a Docker build. func (dockerBuilder) Build(dockerClient bld.DockerClient, sock string, buildsClient client.BuildInterface, build *api.Build, gitClient bld.GitClient, cgLimits *s2iapi.CGroupLimits) error { return bld.NewDockerBuilder(dockerClient, buildsClient, build, gitClient, cgLimits).Build() } type s2iBuilder struct{} // Build starts an S2I build. func (s2iBuilder) Build(dockerClient bld.DockerClient, sock string, buildsClient client.BuildInterface, build *api.Build, gitClient bld.GitClient, cgLimits *s2iapi.CGroupLimits) error { return bld.NewS2IBuilder(dockerClient, sock, buildsClient, build, gitClient, cgLimits).Build() } func runBuild(out io.Writer, builder builder) error { cfg, err := newBuilderConfigFromEnvironment(out) if err != nil { return err } return cfg.execute(builder) } // RunDockerBuild creates a docker builder and runs its build func RunDockerBuild(out io.Writer) error { return runBuild(out, dockerBuilder{}) } // RunS2IBuild creates a S2I builder and runs its build func RunS2IBuild(out io.Writer) error { return runBuild(out, s2iBuilder{}) }