Skip to content

Commit

Permalink
Build config (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
EnriqueL8 authored Jun 26, 2020
1 parent 7129292 commit 1e2c221
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 165 deletions.
2 changes: 1 addition & 1 deletion pkg/component/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ func PushLocal(client *occlient.Client, componentName string, applicationName st
compInfo := common.ComponentInfo{
PodName: pod.Name,
}
err = sync.CopyFile(client, path, compInfo, targetPath, files, globExps, map[string][]byte{})
err = sync.CopyFile(client, path, compInfo, targetPath, files, globExps)
if err != nil {
s.End(false)
return errors.Wrap(err, "unable push files to pod")
Expand Down
184 changes: 53 additions & 131 deletions pkg/devfile/adapters/kubernetes/component/adapter.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package component

import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
Expand All @@ -21,8 +23,6 @@ import (
"github.com/pkg/errors"
"k8s.io/klog"

"github.com/openshift/odo/pkg/occlient"

"github.com/openshift/odo/pkg/component"
"github.com/openshift/odo/pkg/config"
"github.com/openshift/odo/pkg/devfile/adapters/common"
Expand All @@ -34,6 +34,7 @@ import (
"github.com/openshift/odo/pkg/exec"
"github.com/openshift/odo/pkg/kclient"
"github.com/openshift/odo/pkg/log"
"github.com/openshift/odo/pkg/occlient"
odoutil "github.com/openshift/odo/pkg/odo/util"
"github.com/openshift/odo/pkg/sync"
"github.com/openshift/odo/pkg/url"
Expand All @@ -56,165 +57,87 @@ type Adapter struct {
devfileRunCmd string
}

func (a Adapter) generateBuildContainer(containerName, imageTag string) corev1.Container {
buildImage := "quay.io/buildah/stable:latest"

// TODO(Optional): Init container before the buildah bud to copy over the files.
//command := []string{"buildah"}
//commandArgs := []string{"bud"}
command := []string{"tail"}
commandArgs := []string{"-f", "/dev/null"}

// TODO: Edit dockerfile env value if mounting it sometwhere else
envVars := []corev1.EnvVar{
{Name: "Tag", Value: imageTag},
}

isPrivileged := true
resourceReqs := corev1.ResourceRequirements{}
const dockerfilePath string = "Dockerfile"

container := kclient.GenerateContainer(containerName, buildImage, isPrivileged, command, commandArgs, envVars, resourceReqs, nil)
func (a Adapter) runBuildConfig(client *occlient.Client, parameters common.BuildParameters) (err error) {
buildName := a.ComponentName

container.VolumeMounts = []corev1.VolumeMount{
{Name: "varlibcontainers", MountPath: "/var/lib/containers"},
{Name: kclient.OdoSourceVolume, MountPath: kclient.OdoSourceVolumeMount},
commonObjectMeta := metav1.ObjectMeta{
Name: buildName,
}

return *container
}

func (a Adapter) createBuildDeployment(labels map[string]string, container corev1.Container) (err error) {

objectMeta := kclient.CreateObjectMeta(a.ComponentName, a.Client.Namespace, labels, nil)
podTemplateSpec := kclient.GeneratePodTemplateSpec(objectMeta, []corev1.Container{container})

// TODO: For openshift, need to specify a service account that allows priviledged containers
saEnv := os.Getenv("BUILD_SERVICE_ACCOUNT")
if saEnv != "" {
podTemplateSpec.Spec.ServiceAccountName = saEnv
}

libContainersVolume := corev1.Volume{
Name: "varlibcontainers",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
}

podTemplateSpec.Spec.Volumes = append(podTemplateSpec.Spec.Volumes, libContainersVolume)

deploymentSpec := kclient.GenerateDeploymentSpec(*podTemplateSpec)
klog.V(3).Infof("Creating deployment %v", deploymentSpec.Template.GetName())

_, err = a.Client.CreateDeployment(*deploymentSpec)
_, err = client.CreateDockerBuildConfigWithBinaryInput(commonObjectMeta, dockerfilePath, parameters.Tag, []corev1.EnvVar{})
if err != nil {
return err
}
klog.V(3).Infof("Successfully created component %v", deploymentSpec.Template.GetName())

return nil
}

func (a Adapter) executeBuildAndPush(syncFolder string, imageTag string, compInfo common.ComponentInfo) (err error) {
// Running buildah bud and buildah push
buildahBud := "buildah bud -f ./Dockerfile -t $Tag ."
command := []string{adaptersCommon.ShellExecutable, "-c", "cd " + syncFolder + " && " + buildahBud}
defer func() {
// This will delete both the BuildConfig and any builds using that BuildConfig
derr := client.DeleteBuildConfig(commonObjectMeta)
if err == nil {
err = derr
}
}()

// TODO: Add spinner
err = exec.ExecuteCommand(&a.Client, compInfo, command, false)
syncAdapter := sync.New(a.AdapterContext, &a.Client)
reader, err := syncAdapter.SyncFilesBuild(parameters, dockerfilePath)
if err != nil {
return errors.Wrapf(err, "failed to build image for component with name: %s", a.ComponentName)
}

values := strings.Split(imageTag, "/")
tag := imageTag
buildahPush := "buildah push "

// Need to change this IF to be more robust
if len(values) == 3 && strings.Contains(values[0], "openshift") {
// This needs a valid service account: e.g builder for openshift
// --creds flag arg has the format username:password
// we want to use serviceaccount:token
buildahPush += "--creds "
saEnv := os.Getenv("BUILD_SERVICE_ACCOUNT")
if saEnv != "" {
buildahPush += saEnv
} else {
buildahBud += "dummy-username"
}
buildahPush += ":$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) "
return err
}

//TODO: handle dockerhub case and creds!!
buildahPush += "--tls-verify=false " + tag + " docker://" + tag
command = []string{adaptersCommon.ShellExecutable, "-c", buildahPush}

//TODO: Add Spinner
err = exec.ExecuteCommand(&a.Client, compInfo, command, false)
bc, err := client.RunBuildConfigWithBinaryInput(buildName, reader)
if err != nil {
return errors.Wrapf(err, "failed to push build image to the registry for component with name: %s", a.ComponentName)
return err
}

return nil
}
reader, writer := io.Pipe()
s := log.Spinner("Waiting for build to finish")

// Build image for devfile project
func (a Adapter) Build(parameters common.BuildParameters) (err error) {
containerName := a.ComponentName + "-container"
buildContainer := a.generateBuildContainer(containerName, parameters.Tag)
labels := map[string]string{
"component": a.ComponentName,
}
var cmdOutput string
// This Go routine will automatically pipe the output from WaitForBuildToFinish to
// our logger.
go func() {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()

err = a.createBuildDeployment(labels, buildContainer)
if err != nil {
return errors.Wrap(err, "error while creating buildah deployment")
}
if log.IsDebug() {
_, err := fmt.Fprintln(os.Stdout, line)
if err != nil {
log.Errorf("Unable to print to stdout: %v", err)
}
}

// Delete deployment
defer func() {
derr := a.Delete(labels)
if err == nil {
err = errors.Wrapf(derr, "failed to delete build step for component with name: %s", a.ComponentName)
cmdOutput += fmt.Sprintln(line)
}

}()

_, err = a.Client.WaitForDeploymentRollout(a.ComponentName)
if err != nil {
return errors.Wrap(err, "error while waiting for deployment rollout")
}

// Wait for Pod to be in running state otherwise we can't sync data or exec commands to it.
pod, err := a.waitAndGetComponentPod(false)
if err != nil {
return errors.Wrapf(err, "unable to get pod for component %s", a.ComponentName)
}

// Need to wait for container to start
time.Sleep(10 * time.Second)

// Sync files to volume
log.Infof("\nSyncing to component %s", a.ComponentName)
// Get a sync adapter. Check if project files have changed and sync accordingly
syncAdapter := sync.New(a.AdapterContext, &a.Client)
compInfo := common.ComponentInfo{
ContainerName: containerName,
PodName: pod.GetName(),
if err := client.WaitForBuildToFinish(bc.Name, writer); err != nil {
s.End(false)
return errors.Wrapf(err, "unable to build image using BuildConfig %s, error: %s", buildName, cmdOutput)
}

syncFolder, err := syncAdapter.SyncFilesBuild(parameters, compInfo)
s.End(true)
return
}

// Build image for devfile project
func (a Adapter) Build(parameters common.BuildParameters) (err error) {
client, err := occlient.New()
if err != nil {
return errors.Wrapf(err, "failed to sync to component with name %s", a.ComponentName)
return err
}

err = a.executeBuildAndPush(syncFolder, parameters.Tag, compInfo)
isBuildConfigSupported, err := client.IsBuildConfigSupported()
if err != nil {
return err
}

return
if isBuildConfigSupported {
return a.runBuildConfig(client, parameters)
}

return errors.New("unable to build image, only Openshift BuildConfig build is supported")
}

func determinePort(envSpecificInfo envinfo.EnvSpecificInfo) string {
Expand Down Expand Up @@ -302,7 +225,6 @@ func (a Adapter) waitForManifestDeployCompletion(applicationName string, gvr sch

// Build image for devfile project
func (a Adapter) Deploy(parameters common.DeployParameters) (err error) {

namespace := a.Client.Namespace
applicationName := a.ComponentName + "-deploy"
deploymentManifest := &unstructured.Unstructured{}
Expand Down
48 changes: 48 additions & 0 deletions pkg/occlient/occlient.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,31 @@ func (c *Client) GetPortsFromBuilderImage(componentType string) ([]string, error
return portList, nil
}

// RunBuildConfigWithBinary
func (c *Client) RunBuildConfigWithBinaryInput(name string, r io.Reader) (*buildv1.Build, error) {
// TODO: investigate this issue
// Error: no kind is registered for the type v1.BinaryBuildRequestOptions in scheme "k8s.io/client-go/kubernetes/scheme/register.go:69"
// options := &buildv1.BinaryBuildRequestOptions{
// ObjectMeta: metav1.ObjectMeta{
// Name: name,
// Namespace: c.Namespace,
// },
// }
result := &buildv1.Build{}
err := c.buildClient.RESTClient().
Post().
Namespace(c.Namespace).
Resource("buildconfigs").
Name(name).
SubResource("instantiatebinary").
Body(r).
//VersionedParams(options, scheme.ParameterCodec).
Do().
Into(result)

return result, err
}

// RunLogout logs out the current user from cluster
func (c *Client) RunLogout(stdout io.Writer) error {
output, err := c.userClient.Users().Get("~", metav1.GetOptions{})
Expand Down Expand Up @@ -3112,6 +3137,23 @@ func (c *Client) GetPVCFromName(pvcName string) (*corev1.PersistentVolumeClaim,
return c.kubeClient.CoreV1().PersistentVolumeClaims(c.Namespace).Get(pvcName, metav1.GetOptions{})
}

// CreateDockerBuildConfigWithBinaryAndDockerfile creates a BuildConfig which accepts
// as input a binary which contains a dockerfile at a specific location. It will build
// the source with Dockerfile, and push the image using tag.
// envVars is the array containing the environment variables
func (c *Client) CreateDockerBuildConfigWithBinaryInput(commonObjectMeta metav1.ObjectMeta, dockerfilePath string, outputImageTag string, envVars []corev1.EnvVar) (bc buildv1.BuildConfig, err error) {
bc = generateDockerBuildConfigWithBinaryInput(commonObjectMeta, dockerfilePath, outputImageTag)

if len(envVars) > 0 {
bc.Spec.Strategy.SourceStrategy.Env = envVars
}
_, err = c.buildClient.BuildConfigs(c.Namespace).Create(&bc)
if err != nil {
return bc, errors.Wrapf(err, "unable to create BuildConfig for %s", commonObjectMeta.Name)
}
return bc, err
}

// CreateBuildConfig creates a buildConfig using the builderImage as well as gitURL.
// envVars is the array containing the environment variables
func (c *Client) CreateBuildConfig(commonObjectMeta metav1.ObjectMeta, builderImage string, gitURL string, gitRef string, envVars []corev1.EnvVar) (buildv1.BuildConfig, error) {
Expand Down Expand Up @@ -3298,6 +3340,12 @@ func isSubDir(baseDir, otherDir string) bool {
return matches
}

// IsBuildConfigSupported checks if buildconfig resource type is present on the cluster
func (c *Client) IsBuildConfigSupported() (bool, error) {

return c.isResourceSupported("build.openshift.io", "v1", "buildconfigs")
}

// IsRouteSupported checks if route resource type is present on the cluster
func (c *Client) IsRouteSupported() (bool, error) {

Expand Down
32 changes: 32 additions & 0 deletions pkg/occlient/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,38 @@ func generateGitDeploymentConfig(commonObjectMeta metav1.ObjectMeta, image strin
return dc
}

// generateDockerBuildConfigWithBinaryInput creates a BuildConfig which accepts a Binary (usualy archive)
// with a Dockerfile at the path specified. It will run a build using docker, and push the resulting image
// to the tag specified.
func generateDockerBuildConfigWithBinaryInput(commonObjectMeta metav1.ObjectMeta, dockerfilePath, outputImageTag string) buildv1.BuildConfig {
buildSource := buildv1.BuildSource{
Binary: &buildv1.BinaryBuildSource{},
Type: buildv1.BuildSourceBinary,
}

return buildv1.BuildConfig{
ObjectMeta: commonObjectMeta,
Spec: buildv1.BuildConfigSpec{
CommonSpec: buildv1.CommonSpec{
Output: buildv1.BuildOutput{
To: &corev1.ObjectReference{
Kind: "DockerImage",
Name: outputImageTag,
},
// OPTIONAL: PushSecrets and ImageLabels
},
Source: buildSource,
Strategy: buildv1.BuildStrategy{
Type: buildv1.DockerBuildStrategyType,
DockerStrategy: &buildv1.DockerBuildStrategy{
DockerfilePath: dockerfilePath,
},
},
},
},
}
}

// generateBuildConfig creates a BuildConfig for Git URL's being passed into Odo
func generateBuildConfig(commonObjectMeta metav1.ObjectMeta, gitURL, gitRef, imageName, imageNamespace string) buildv1.BuildConfig {

Expand Down
7 changes: 6 additions & 1 deletion pkg/odo/cli/component/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/openshift/odo/pkg/log"
"github.com/openshift/odo/pkg/occlient"
"github.com/openshift/odo/pkg/odo/util/completion"
"github.com/openshift/odo/pkg/odo/util/experimental"
"github.com/openshift/odo/pkg/url"
"github.com/pkg/errors"

Expand Down Expand Up @@ -70,7 +71,11 @@ func NewCmdComponent(name, fullName string) *cobra.Command {
// add flags from 'get' to component command
componentCmd.Flags().AddFlagSet(componentGetCmd.Flags())

componentCmd.AddCommand(componentGetCmd, createCmd, deleteCmd, describeCmd, deployCmd, linkCmd, unlinkCmd, listCmd, logCmd, pushCmd, updateCmd, watchCmd)
componentCmd.AddCommand(componentGetCmd, createCmd, deleteCmd, describeCmd, linkCmd, unlinkCmd, listCmd, logCmd, pushCmd, updateCmd, watchCmd)

if experimental.IsExperimentalModeEnabled() {
componentCmd.AddCommand(deployCmd)
}

// Add a defined annotation in order to appear in the help menu
componentCmd.Annotations = map[string]string{"command": "main"}
Expand Down
Loading

0 comments on commit 1e2c221

Please sign in to comment.