Skip to content
This repository has been archived by the owner on Nov 15, 2022. It is now read-only.

Commit

Permalink
(feat): s2i build using local code
Browse files Browse the repository at this point in the history
Now user can provide just the image name and code directory and kedge
build will create the configs and trigger binary build using openshift
s2i source build strategy.
  • Loading branch information
surajssd committed Dec 11, 2017
1 parent 0f5093e commit 09e521f
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 13 deletions.
30 changes: 24 additions & 6 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ import (

"github.com/kedgeproject/kedge/pkg/build"

log "github.com/Sirupsen/logrus"
"github.com/spf13/cobra"
)

var Dockerfile string
var DockerImage string
var DockerImage, BuilderImage string
var DockerContext string
var PushImage bool
var PushImage, s2iBuild bool

var buildCmd = &cobra.Command{
Use: "build",
Expand All @@ -38,9 +39,24 @@ var buildCmd = &cobra.Command{
fmt.Println("Please specify the container image name using flag '--image' or '-i'")
os.Exit(-1)
}
if err := build.BuildPushDockerImage(Dockerfile, DockerImage, DockerContext, PushImage); err != nil {
fmt.Println(err)
os.Exit(-1)

if s2iBuild {
if PushImage {
log.Warnf("Using source to image strategy for build, image will be by default pushed to internal container registry, so ignoring this flag")
}
if BuilderImage == "" {
fmt.Println("Please specify the builder image name using flag '--builder-image' or '-b'")
os.Exit(-1)
}
if err := build.BuildS2I(DockerImage, DockerContext, BuilderImage); err != nil {
fmt.Println(err)
os.Exit(-1)
}
} else {
if err := build.BuildPushDockerImage(Dockerfile, DockerImage, DockerContext, PushImage); err != nil {
fmt.Println(err)
os.Exit(-1)
}
}
},
}
Expand All @@ -50,7 +66,9 @@ func init() {
buildCmd.Flags().StringVarP(&Dockerfile, "file", "f", "Dockerfile", "Specify Dockerfile for doing builds, Dockerfile path is relative to context")
buildCmd.Flags().StringVarP(&DockerImage, "image", "i", "", "Image name and tag of resulting image")
buildCmd.Flags().StringVarP(&DockerContext, "context", "c", ".", "Path to a directory containing a Dockerfile, it is build context that is sent to the Docker daemon")
buildCmd.Flags().BoolVarP(&PushImage, "push", "p", false, "Add this flag if you want to push the image")
buildCmd.Flags().BoolVarP(&PushImage, "push", "p", false, "Add this flag if you want to push the image. Note: Ignored when s2i build strategy used")
buildCmd.Flags().BoolVarP(&s2iBuild, "s2i", "", false, "If this is enabled then Source to Image build strategy is used")
buildCmd.Flags().StringVarP(&BuilderImage, "builder-image", "b", "", "Name of a Docker image to use as a builder. Note: This is only useful when using s2i build strategy")

RootCmd.AddCommand(buildCmd)
}
19 changes: 19 additions & 0 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,22 @@ command before running this build command:
```console
$ eval $(minikube docker-env)
```

### Build using source to image

You can hand off the image building work to OpenShift's source to image utility, without having
to write any Dockerfile.

To do build using OpensShift's s2i run following command:

```console
$ kedge build --s2i --image pyappth -b centos/python-35-centos7:3.5
```

Deconstructing above command, we can see that to enable using OpenShift's s2i use the boolean
flag `--s2i` then give the output name of the ImageStream that will be created using flag
`--image` and finally also provide what builder image to use to do builds of the code using
flag `-b`.

**Note**: Flag `-b` is valid only when using s2i mode. And build flag `-p` is not valid when
in s2i mode.
154 changes: 154 additions & 0 deletions pkg/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,16 @@ import (

log "github.com/Sirupsen/logrus"
dockerlib "github.com/fsouza/go-dockerclient"
"github.com/ghodss/yaml"
os_build_v1 "github.com/openshift/origin/pkg/build/apis/build/v1"
os_image_v1 "github.com/openshift/origin/pkg/image/apis/image/v1"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
_ "k8s.io/kubernetes/pkg/api/install"
kapi "k8s.io/kubernetes/pkg/api/v1"

"github.com/kedgeproject/kedge/pkg/cmd"
"github.com/kedgeproject/kedge/pkg/spec"
)

func BuildPushDockerImage(dockerfile, image, context string, push bool) error {
Expand Down Expand Up @@ -174,3 +183,148 @@ func CreateTarball(source, target string) error {
return err
})
}

// getImageTag get tag name from image name
// if no tag is specified return 'latest'
func GetImageTag(image string) string {
// format: registry_host:registry_port/repo_name/image_name:image_tag
// example:
// 1) myregistryhost:5000/fedora/httpd:version1.0
// 2) myregistryhost:5000/fedora/httpd
// 3) myregistryhost/fedora/httpd:version1.0
// 4) myregistryhost/fedora/httpd
// 5) fedora/httpd
// 6) httpd
imageAndTag := image

imageTagSplit := strings.Split(image, "/")
if len(imageTagSplit) >= 2 {
imageAndTag = imageTagSplit[len(imageTagSplit)-1]
}

p := strings.Split(imageAndTag, ":")
if len(p) == 2 {
return p[1]
}
return "latest"
}

func GetImageName(image string) string {
imageAndTag := image

imageTagSplit := strings.Split(image, "/")
if len(imageTagSplit) >= 2 {
imageAndTag = imageTagSplit[len(imageTagSplit)-1]
}
p := strings.Split(imageAndTag, ":")
if len(p) <= 2 {
return p[0]
}

return image
}

func BuildS2I(image, context, builderImage string) error {

name := GetImageName(image)
labels := map[string]string{
spec.BuildLabelKey: name,
}
annotations := map[string]string{
"openshift.io/generated-by": "KedgeBuildS2I",
}

is := os_image_v1.ImageStream{
TypeMeta: metav1.TypeMeta{
Kind: "ImageStream",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: labels,
Annotations: annotations,
},
Spec: os_image_v1.ImageStreamSpec{
Tags: []os_image_v1.TagReference{
{Name: GetImageTag(image)},
},
},
}

bc := os_build_v1.BuildConfig{
TypeMeta: metav1.TypeMeta{
Kind: "BuildConfig",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: labels,
Annotations: annotations,
},
Spec: os_build_v1.BuildConfigSpec{
CommonSpec: os_build_v1.CommonSpec{
Strategy: os_build_v1.BuildStrategy{
Type: "Binary",
SourceStrategy: &os_build_v1.SourceBuildStrategy{
From: kapi.ObjectReference{
Kind: "DockerImage",
Name: builderImage,
},
},
},
Output: os_build_v1.BuildOutput{
To: &kapi.ObjectReference{
Kind: "ImageStreamTag",
Name: name + ":" + GetImageTag(image),
},
},
},
},
}

isyaml, err := yaml.Marshal(is)
if err != nil {
return err
}

bcyaml, err := yaml.Marshal(bc)
if err != nil {
return err
}

log.Debugf("ImageStream for output image: \n%s\n", string(isyaml))
log.Debugf("BuildConfig: \n%s\n", string(bcyaml))

args := []string{"apply", "-f", "-"}
err = cmd.RunClusterCommand(args, isyaml, true)
if err != nil {
return err
}
err = cmd.RunClusterCommand(args, bcyaml, true)
if err != nil {
cleanup(name)
return err
}

log.Infof("Starting build for %q", image)
cmd := []string{"oc", "start-build", image, "--from-dir=" + context, "-F"}
if err := RunCommand(cmd); err != nil {
return err
}

return nil
}

func cleanup(name string) {
log.Infof("Cleaning up build since error occurred while building")

delBc := []string{"oc", "delete", "buildconfig", name}
if err := RunCommand(delBc); err != nil {
log.Debugf("error while deleting buildconfig: %v", err)
}

delIs := []string{"oc", "delete", "imagestream", name}
if err := RunCommand(delIs); err != nil {
log.Debugf("error while deleting imagestream: %v", err)
}
}
13 changes: 11 additions & 2 deletions pkg/build/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,17 @@ as input.
func PushImage(fullImageName string) error {
log.Infof("Pushing image %q", fullImageName)

cmd := exec.Command("docker", "push", fullImageName)
command := []string{"docker", "push", fullImageName}
if err := RunCommand(command); err != nil {
return err
}
log.Infof("Successfully pushed image %q", fullImageName)

return nil
}

func RunCommand(command []string) error {
cmd := exec.Command(command[0], command[1:]...)
cmdReader, err := cmd.StdoutPipe()
if err != nil {
return err
Expand All @@ -58,7 +68,6 @@ func PushImage(fullImageName string) error {
if err != nil {
return fmt.Errorf("%s, %s", strings.TrimSpace(stderr.String()), err)
}
log.Infof("Successfully pushed image %q", fullImageName)

return nil
}
6 changes: 3 additions & 3 deletions pkg/cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func CreateArtifacts(paths []string, generate bool, args ...string) error {
// e.g. If the command and arguments are "apply --namespace staging", then the
// final command becomes "kubectl apply --namespace staging -f -"
arguments := append(args, "-f", "-")
err = runClusterCommand(arguments, data, useOC)
err = RunClusterCommand(arguments, data, useOC)
if err != nil {
return errors.Wrap(err, "failed to execute command")
}
Expand Down Expand Up @@ -118,7 +118,7 @@ func CreateArtifacts(paths []string, generate bool, args ...string) error {
// e.g. If the command and arguments are "apply --namespace staging", then the
// final command becomes "kubectl apply --namespace staging -f absolute-filename"
arguments := append(args, "-f", file)
err = runClusterCommand(arguments, nil, useOC)
err = RunClusterCommand(arguments, nil, useOC)
if err != nil {
return errors.Wrap(err, "failed to execute command")
}
Expand All @@ -130,7 +130,7 @@ func CreateArtifacts(paths []string, generate bool, args ...string) error {

// runClusterCommand calls kubectl or oc binary.
// Boolean flag useOC controls if oc or kubectl will be used
func runClusterCommand(args []string, data []byte, useOC bool) error {
func RunClusterCommand(args []string, data []byte, useOC bool) error {
executable := "kubectl"
if useOC {
executable = "oc"
Expand Down
7 changes: 5 additions & 2 deletions pkg/spec/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ import (

// allLabelKey is the key that Kedge injects in every Kubernetes resource that
// it generates as an ObjectMeta label
const appLabelKey = "app"
const appVersion = "appversion"
const (
appLabelKey = "app"
appVersion = "appversion"
BuildLabelKey = "build"
)

// Fix

Expand Down

0 comments on commit 09e521f

Please sign in to comment.