Skip to content

Commit

Permalink
Support Knative Build CRD
Browse files Browse the repository at this point in the history
Currently focused on supporting Docker build artifacts

Signed-off-by: David Gageot <david@gageot.net>
  • Loading branch information
dgageot committed Aug 9, 2018
1 parent 62a4fef commit e17d117
Show file tree
Hide file tree
Showing 27 changed files with 2,340 additions and 3 deletions.
18 changes: 18 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@
name = "github.com/docker/distribution"
revision = "83389a148052d74ac602f5f1d62f86ff2f3c4aa5"

[[constraint]]
[[override]]
name = "k8s.io/apimachinery"
version = "kubernetes-1.11.0"

[[constraint]]
[[override]]
name = "k8s.io/client-go"
version = "kubernetes-1.11.0"

[[constraint]]
[[override]]
name = "k8s.io/api"
version = "kubernetes-1.11.0"

Expand All @@ -74,3 +74,7 @@
[[constraint]]
name = "github.com/google/go-github"
version = "15.0.0"

[[constraint]]
name = "github.com/knative/build"
revision = "dd8f92ed1cc2e49d98c26a94270a40c9ea7da9b8"
9 changes: 9 additions & 0 deletions examples/annotated-skaffold.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ build:
# namespace: default
# timeout: 20m

# Docker artifacts can be built on a Kubernetes cluster with Knative Build CRD.
# Example
# knative:
# gcsBucket: k8s-skaffold
# namespace: default
# secret: ""
# secretName: knative-secret
# serviceAccountName: knative-sa

# The deploy section has all the information needed to deploy. Along with build:
# it is a required section.
deploy:
Expand Down
88 changes: 88 additions & 0 deletions pkg/skaffold/build/knative.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
Copyright 2018 The Skaffold Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package build

import (
"context"
"fmt"
"io"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/knative"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2"
"github.com/pkg/errors"
)

// KnativeBuilder can build docker artifacts on Kubernetes, using knative build CRD.
type KnativeBuilder struct {
*v1alpha2.KnativeBuild
}

// NewKnativeBuilder creates a KnativeBuilder.
func NewKnativeBuilder(cfg *v1alpha2.KnativeBuild) *KnativeBuilder {
return &KnativeBuilder{
KnativeBuild: cfg,
}
}

// Labels gives labels to be set on artifacts deployed with knative.
func (k *KnativeBuilder) Labels() map[string]string {
return map[string]string{
constants.Labels.Builder: "knative",
}
}

// Build builds a list of artifacts with knative.
func (k *KnativeBuilder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*v1alpha2.Artifact) ([]Artifact, error) {
// TODO(dgageot): parallel builds
var builds []Artifact

for _, artifact := range artifacts {
fmt.Fprintf(out, "Building [%s]...\n", artifact.ImageName)

initialTag, err := knative.BuildArtifact(ctx, out, artifact, k.KnativeBuild)
if err != nil {
return nil, errors.Wrapf(err, "kaniko build for [%s]", artifact.ImageName)
}

digest, err := docker.RemoteDigest(initialTag)
if err != nil {
return nil, errors.Wrap(err, "getting digest")
}

tag, err := tagger.GenerateFullyQualifiedImageName(artifact.Workspace, &tag.Options{
ImageName: artifact.ImageName,
Digest: digest,
})
if err != nil {
return nil, errors.Wrap(err, "generating tag")
}

if err := docker.AddTag(initialTag, tag); err != nil {
return nil, errors.Wrap(err, "tagging image")
}

builds = append(builds, Artifact{
ImageName: artifact.ImageName,
Tag: tag,
})
}

return builds, nil
}
156 changes: 156 additions & 0 deletions pkg/skaffold/build/knative/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
Copyright 2018 The Skaffold Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package knative

import (
"context"
"fmt"
"io"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/knative/build/pkg/apis/build/v1alpha1"
build_v1alpha1 "github.com/knative/build/pkg/client/clientset/versioned/typed/build/v1alpha1"
"github.com/knative/build/pkg/logs"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
)

// BuildArtifact builds an artifact with knative Build CRD
func BuildArtifact(ctx context.Context, out io.Writer, a *v1alpha2.Artifact, cfg *v1alpha2.KnativeBuild) (string, error) {
if err := testBuildCRD(); err != nil {
logrus.Errorln("Build CRD is not installed")
logrus.Errorln("Follow the installation guide: https://github.com/knative/build#getting-started")
logrus.Errorln("Usually, it's as simple as: kubectl create -f https://storage.googleapis.com/build-crd/latest/release.yaml")
return "", errors.Wrap(err, "Build CRD is not installed")
}

loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
clientConfig, err := kubeConfig.ClientConfig()
if err != nil {
return "", errors.Wrap(err, "getting clientConfig")
}

deleteSecret, err := setupSecret(clientConfig, cfg)
if err != nil {
return "", errors.Wrap(err, "setting up secret")
}
defer deleteSecret()

deleteServiceAccount, err := setupServiceAccount(clientConfig, cfg)
if err != nil {
return "", errors.Wrap(err, "setting up service account")
}
defer deleteServiceAccount()

randomID := util.RandomID()
zipName := fmt.Sprintf("context-%s.tar.gz", randomID)
// TODO(dgageot): delete sources
if err := docker.UploadContextToGCS(ctx, a.Workspace, a.DockerArtifact, cfg.GCSBucket, zipName); err != nil {
return "", errors.Wrap(err, "uploading tar to gcs")
}

client, err := build_v1alpha1.NewForConfig(clientConfig)
if err != nil {
return "", errors.Wrap(err, "getting build client")
}

imageDst := fmt.Sprintf("%s:%s", a.ImageName, randomID)
build := buildFor(imageDst, zipName, a, cfg)

builds := client.Builds(cfg.Namespace)
build, err = builds.Create(build)
if err != nil {
return "", errors.Wrap(err, "creating build")
}
defer func() {
if err := builds.Delete(build.Name, &metav1.DeleteOptions{}); err != nil {
logrus.Debugln("deleting build", err)
}
}()

if err := logs.Tail(ctx, out, build.Name, build.Namespace); err != nil {
return "", errors.Wrap(err, "streaming logs")
}

if err := waitForCompletion(builds, build); err != nil {
return "", errors.Wrap(err, "waiting for completion")
}

return imageDst, nil
}

func waitForCompletion(builds build_v1alpha1.BuildInterface, build *v1alpha1.Build) error {
for {
done, err := builds.Get(build.Name, metav1.GetOptions{})
if err != nil {
return errors.Wrap(err, "getting status")
}

switch buildStatus(done) {
case v1.ConditionFalse:
return errors.New("build failed")
case v1.ConditionTrue:
return nil
}
}
}

func buildStatus(build *v1alpha1.Build) v1.ConditionStatus {
for _, condition := range build.Status.Conditions {
if condition.Type == v1alpha1.BuildSucceeded {
return condition.Status
}
}

return v1.ConditionUnknown
}

func buildFor(imageName, tarName string, a *v1alpha2.Artifact, cfg *v1alpha2.KnativeBuild) *v1alpha1.Build {
return &v1alpha1.Build{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "skaffold-docker-build",
Namespace: cfg.Namespace,
},
Spec: v1alpha1.BuildSpec{
ServiceAccountName: cfg.ServiceAccountName,
Source: &v1alpha1.SourceSpec{
Custom: &v1.Container{
Image: "gcr.io/cloud-builders/gsutil", // TODO(dgageot) way too big
Command: []string{"sh", "-c"},
Args: []string{fmt.Sprintf("gsutil cp gs://%s/%s - | tar xvz", cfg.GCSBucket, tarName)},
},
},
Steps: []v1.Container{
{
Name: "build-and-push",
Image: constants.DefaultKanikoImage,
Args: []string{
"--dockerfile=/workspace/" + a.ArtifactType.DockerArtifact.DockerfilePath,
"--destination=" + imageName,
},
},
},
},
}
}
36 changes: 36 additions & 0 deletions pkg/skaffold/build/knative/crd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
Copyright 2018 The Skaffold Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package knative

import (
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const buildCRDNamespace = "knative-build"

// testBuildCRD verifies that the Build CRD is installed.
func testBuildCRD() error {
client, err := kubernetes.GetClientset()
if err != nil {
return errors.Wrap(err, "getting kubernetes client")
}

_, err = client.CoreV1().Namespaces().Get(buildCRDNamespace, metav1.GetOptions{})
return err
}
Loading

0 comments on commit e17d117

Please sign in to comment.