You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by lb...@apache.org on 2018/09/28 13:00:54 UTC
[camel-k] 01/06: First working version of Kaniko builder
This is an automated email from the ASF dual-hosted git repository.
lburgazzoli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-k.git
commit 07ea1787eb145bd5478ce4955bc35bf6b4413652
Author: nferraro <ni...@gmail.com>
AuthorDate: Fri Sep 28 12:17:52 2018 +0200
First working version of Kaniko builder
---
deploy/builder-pvc.yaml | 12 ++
deploy/operator-deployment.yaml | 7 +
deploy/resources.go | 23 +++
pkg/build/build_manager.go | 30 ++-
pkg/build/build_types.go | 15 +-
pkg/build/packager/base.go | 172 ++++++++++++++++
pkg/{install/operator.go => build/packager/doc.go} | 26 +--
pkg/build/packager/factory.go | 49 +++++
.../incremental.go} | 45 ++---
.../operator.go => build/packager/types.go} | 30 +--
pkg/build/publish/kaniko_publisher.go | 225 +++++++++++++++++++++
pkg/build/publish/s2i_publisher.go | 97 +--------
pkg/client/cmd/install.go | 4 +-
pkg/install/common.go | 11 +-
pkg/install/operator.go | 54 ++++-
pkg/platform/build.go | 16 +-
pkg/stub/action/platform/initialize.go | 23 +--
pkg/util/minishift/minishift.go | 60 ++++++
.../operator.go => util/openshift/openshift.go} | 37 ++--
19 files changed, 729 insertions(+), 207 deletions(-)
diff --git a/deploy/builder-pvc.yaml b/deploy/builder-pvc.yaml
new file mode 100644
index 0000000..8e6ca4b
--- /dev/null
+++ b/deploy/builder-pvc.yaml
@@ -0,0 +1,12 @@
+kind: PersistentVolumeClaim
+apiVersion: v1
+metadata:
+ name: camel-k-builder
+ labels:
+ app: "camel-k"
+spec:
+ accessModes:
+ - ReadWriteMany
+ resources:
+ requests:
+ storage: 1Gi
diff --git a/deploy/operator-deployment.yaml b/deploy/operator-deployment.yaml
index e7a5180..c6e259f 100644
--- a/deploy/operator-deployment.yaml
+++ b/deploy/operator-deployment.yaml
@@ -33,3 +33,10 @@ spec:
fieldPath: metadata.namespace
- name: OPERATOR_NAME
value: "camel-k-operator"
+ volumeMounts:
+ - mountPath: /workspace
+ name: camel-k-builder
+ volumes:
+ - name: camel-k-builder
+ persistentVolumeClaim:
+ claimName: camel-k-builder
diff --git a/deploy/resources.go b/deploy/resources.go
index a3274cb..39ab8ee 100644
--- a/deploy/resources.go
+++ b/deploy/resources.go
@@ -24,6 +24,22 @@ var Resources map[string]string
func init() {
Resources = make(map[string]string)
+ Resources["builder-pvc.yaml"] =
+ `
+kind: PersistentVolumeClaim
+apiVersion: v1
+metadata:
+ name: camel-k-builder
+ labels:
+ app: "camel-k"
+spec:
+ accessModes:
+ - ReadWriteMany
+ resources:
+ requests:
+ storage: 1Gi
+
+`
Resources["camel-catalog.yaml"] =
`
artifacts:
@@ -2227,6 +2243,13 @@ spec:
fieldPath: metadata.namespace
- name: OPERATOR_NAME
value: "camel-k-operator"
+ volumeMounts:
+ - mountPath: /workspace
+ name: camel-k-builder
+ volumes:
+ - name: camel-k-builder
+ persistentVolumeClaim:
+ claimName: camel-k-builder
`
Resources["operator-role-binding.yaml"] =
diff --git a/pkg/build/build_manager.go b/pkg/build/build_manager.go
index 895575b..83c0f8a 100644
--- a/pkg/build/build_manager.go
+++ b/pkg/build/build_manager.go
@@ -28,14 +28,16 @@ type Manager struct {
ctx context.Context
builds sync.Map
assembler Assembler
+ packager Packager
publisher Publisher
}
-// NewManager creates an instance of the build manager using the given builder
-func NewManager(ctx context.Context, assembler Assembler, publisher Publisher) *Manager {
+// NewManager creates an instance of the build manager using the given assembler, packager and publisher
+func NewManager(ctx context.Context, assembler Assembler, packager Packager, publisher Publisher) *Manager {
return &Manager{
ctx: ctx,
assembler: assembler,
+ packager: packager,
publisher: publisher,
}
}
@@ -68,7 +70,21 @@ func (m *Manager) Start(request Request) {
}
}
- publishChannel := m.publisher.Publish(request, assembled)
+ packageChannel := m.packager.Package(request, assembled)
+ var packaged PackagedOutput
+ select {
+ case <-m.ctx.Done():
+ m.builds.Store(request.Identifier, canceledBuildInfo(request))
+ return
+ case packaged = <-packageChannel:
+ if packaged.Error != nil {
+ m.builds.Store(request.Identifier, failedPackageBuildInfo(request, packaged))
+ return
+ }
+ }
+ defer m.packager.Cleanup(packaged)
+
+ publishChannel := m.publisher.Publish(request, assembled, packaged)
var published PublishedOutput
select {
case <-m.ctx.Done():
@@ -114,6 +130,14 @@ func failedAssembleBuildInfo(request Request, output AssembledOutput) Result {
}
}
+func failedPackageBuildInfo(request Request, output PackagedOutput) Result {
+ return Result{
+ Request: request,
+ Error: output.Error,
+ Status: StatusError,
+ }
+}
+
func failedPublishBuildInfo(request Request, output PublishedOutput) Result {
return Result{
Request: request,
diff --git a/pkg/build/build_types.go b/pkg/build/build_types.go
index 22ff178..bf83245 100644
--- a/pkg/build/build_types.go
+++ b/pkg/build/build_types.go
@@ -63,6 +63,19 @@ type Assembler interface {
Assemble(Request) <-chan AssembledOutput
}
+// PackagedOutput is the new image layer that needs to be pushed
+type PackagedOutput struct {
+ Error error
+ BaseImage string
+ TarFile string
+}
+
+// A Packager produces the image layer that needs to be pushed
+type Packager interface {
+ Package(Request, AssembledOutput) <-chan PackagedOutput
+ Cleanup(PackagedOutput)
+}
+
// PublishedOutput is the output of the publish phase
type PublishedOutput struct {
Error error
@@ -71,7 +84,7 @@ type PublishedOutput struct {
// A Publisher publishes a docker image of a build request
type Publisher interface {
- Publish(Request, AssembledOutput) <-chan PublishedOutput
+ Publish(Request, AssembledOutput, PackagedOutput) <-chan PublishedOutput
}
// Status --
diff --git a/pkg/build/packager/base.go b/pkg/build/packager/base.go
new file mode 100644
index 0000000..38cfdc7
--- /dev/null
+++ b/pkg/build/packager/base.go
@@ -0,0 +1,172 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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 packager
+
+import (
+ "context"
+ "io/ioutil"
+ "os"
+ "path"
+ "time"
+
+ "github.com/apache/camel-k/pkg/build"
+ "github.com/apache/camel-k/pkg/util/maven"
+ "github.com/apache/camel-k/pkg/util/tar"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ sharedDir = "/workspace"
+ artifactDirPrefix = "layer-"
+)
+
+type commonPackager struct {
+ buffer chan packageOperation
+ uploadedArtifactsSelector
+}
+
+type packageOperation struct {
+ request build.Request
+ assembled build.AssembledOutput
+ output chan build.PackagedOutput
+}
+
+type uploadedArtifactsSelector func([]build.ClasspathEntry) (string, []build.ClasspathEntry, error)
+
+func newBasePackager(ctx context.Context, rootImage string) build.Packager {
+ identitySelector := func(entries []build.ClasspathEntry) (string, []build.ClasspathEntry, error) {
+ return rootImage, entries, nil
+ }
+ return newBasePackagerWithSelector(ctx, identitySelector)
+}
+
+func newBasePackagerWithSelector(ctx context.Context, uploadedArtifactsSelector uploadedArtifactsSelector) *commonPackager {
+ pack := commonPackager{
+ buffer: make(chan packageOperation, 100),
+ uploadedArtifactsSelector: uploadedArtifactsSelector,
+ }
+ go pack.packageCycle(ctx)
+ return &pack
+}
+
+func (b *commonPackager) Package(request build.Request, assembled build.AssembledOutput) <-chan build.PackagedOutput {
+ res := make(chan build.PackagedOutput, 1)
+ op := packageOperation{
+ request: request,
+ assembled: assembled,
+ output: res,
+ }
+ b.buffer <- op
+ return res
+}
+
+func (b *commonPackager) Cleanup(output build.PackagedOutput) {
+ parentDir, _ := path.Split(output.TarFile)
+ err := os.RemoveAll(parentDir)
+ if err != nil {
+ logrus.Warn("Could not remove temporary directory ", parentDir)
+ }
+}
+
+func (b *commonPackager) packageCycle(ctx context.Context) {
+ for {
+ select {
+ case <-ctx.Done():
+ b.buffer = nil
+ return
+ case op := <-b.buffer:
+ now := time.Now()
+ logrus.Info("Starting a new image packaging")
+ res := b.execute(op.request, op.assembled)
+ elapsed := time.Now().Sub(now)
+
+ if res.Error != nil {
+ logrus.Error("Error during packaging (total time ", elapsed.Seconds(), " seconds): ", res.Error)
+ } else {
+ logrus.Info("Packaging completed in ", elapsed.Seconds(), " seconds")
+ }
+
+ op.output <- res
+ }
+ }
+}
+
+func (b *commonPackager) execute(request build.Request, assembled build.AssembledOutput) build.PackagedOutput {
+ baseImageName, selectedArtifacts, err := b.uploadedArtifactsSelector(assembled.Classpath)
+ if err != nil {
+ return build.PackagedOutput{Error: err}
+ }
+
+ tarFile, err := b.createTar(assembled, selectedArtifacts)
+ if err != nil {
+ return build.PackagedOutput{Error: err}
+ }
+
+ return build.PackagedOutput{
+ BaseImage: baseImageName,
+ TarFile: tarFile,
+ }
+}
+
+func (b *commonPackager) createTar(assembled build.AssembledOutput, selectedArtifacts []build.ClasspathEntry) (string, error) {
+ artifactDir, err := ioutil.TempDir(sharedDir, artifactDirPrefix)
+ if err != nil {
+ return "", errors.Wrap(err, "could not create temporary dir for packaged artifacts")
+ }
+
+ tarFileName := path.Join(artifactDir, "occi.tar")
+ tarAppender, err := tar.NewAppender(tarFileName)
+ if err != nil {
+ return "", err
+ }
+ defer tarAppender.Close()
+
+ tarDir := "dependencies/"
+ for _, entry := range selectedArtifacts {
+ gav, err := maven.ParseGAV(entry.ID)
+ if err != nil {
+ return "", nil
+ }
+
+ tarPath := path.Join(tarDir, gav.GroupID)
+ _, err = tarAppender.AddFile(entry.Location, tarPath)
+ if err != nil {
+ return "", err
+ }
+ }
+
+ cp := ""
+ for _, entry := range assembled.Classpath {
+ gav, err := maven.ParseGAV(entry.ID)
+ if err != nil {
+ return "", nil
+ }
+ tarPath := path.Join(tarDir, gav.GroupID)
+ _, fileName := path.Split(entry.Location)
+ fileName = path.Join(tarPath, fileName)
+ cp += fileName + "\n"
+ }
+
+ err = tarAppender.AppendData([]byte(cp), "classpath")
+ if err != nil {
+ return "", err
+ }
+
+ return tarFileName, nil
+}
diff --git a/pkg/install/operator.go b/pkg/build/packager/doc.go
similarity index 57%
copy from pkg/install/operator.go
copy to pkg/build/packager/doc.go
index eb3ac4c..cfa21c6 100644
--- a/pkg/install/operator.go
+++ b/pkg/build/packager/doc.go
@@ -15,27 +15,5 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package install
-
-// Operator --
-func Operator(namespace string) error {
- return Resources(namespace,
- "operator-service-account.yaml",
- "operator-role-openshift.yaml", // TODO distinguish between Openshift and Kubernetes
- "operator-role-binding.yaml",
- "operator-deployment.yaml",
- "operator-service.yaml",
- )
-}
-
-// Platform installs the platform custom resource
-func Platform(namespace string) error {
- return Resource(namespace, "platform-cr.yaml")
-}
-
-// Example --
-func Example(namespace string) error {
- return Resources(namespace,
- "cr-example.yaml",
- )
-}
+// Package packager contains strategies for building layers of a Docker image
+package packager
diff --git a/pkg/build/packager/factory.go b/pkg/build/packager/factory.go
new file mode 100644
index 0000000..48ad665
--- /dev/null
+++ b/pkg/build/packager/factory.go
@@ -0,0 +1,49 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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 packager
+
+import (
+ "context"
+ "github.com/apache/camel-k/pkg/build"
+)
+
+const (
+ s2iRootImage = "fabric8/s2i-java:2.3"
+ //javaRootImage = "fabric8/java-jboss-openjdk8-jdk:1.5.1"
+ javaRootImage = "fabric8/java-alpine-openjdk8-jdk:1.5.1"
+)
+
+// NewS2IStandardPackager creates a standard packager for S2I builds
+func NewS2IStandardPackager(ctx context.Context) build.Packager {
+ return newBasePackager(ctx, s2iRootImage)
+}
+
+// NewS2IIncrementalPackager creates a incremental packager for S2I builds
+func NewS2IIncrementalPackager(ctx context.Context, lister PublishedImagesLister) build.Packager {
+ return newIncrementalPackager(ctx, lister, s2iRootImage)
+}
+
+// NewJavaStandardPackager creates a standard packager for Java Docker builds
+func NewJavaStandardPackager(ctx context.Context) build.Packager {
+ return newBasePackager(ctx, javaRootImage)
+}
+
+// NewJavaIncrementalPackager creates a incremental packager for Java Docker builds
+func NewJavaIncrementalPackager(ctx context.Context, lister PublishedImagesLister) build.Packager {
+ return newIncrementalPackager(ctx, lister, javaRootImage)
+}
diff --git a/pkg/build/publish/s2i_incremental_publisher.go b/pkg/build/packager/incremental.go
similarity index 62%
rename from pkg/build/publish/s2i_incremental_publisher.go
rename to pkg/build/packager/incremental.go
index ae996e4..cb9334b 100644
--- a/pkg/build/publish/s2i_incremental_publisher.go
+++ b/pkg/build/packager/incremental.go
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package publish
+package packager
import (
"context"
@@ -23,36 +23,31 @@ import (
"github.com/apache/camel-k/pkg/build"
)
-type s2iIncrementalPublisher struct {
- s2iPublisher *s2iPublisher
- lister PublishedImagesLister
+type incrementalPackager struct {
+ commonPackager *commonPackager
+ lister PublishedImagesLister
+ rootImage string
}
-// PublishedImage represent a base image that can be used as starting point
-type PublishedImage struct {
- Image string
- Classpath []string
-}
-
-// PublishedImagesLister allows to list all images already published
-type PublishedImagesLister interface {
- ListPublishedImages() ([]PublishedImage, error)
+// newIncrementalPackager creates a new packager that is able to create a layer on top of a existing image
+func newIncrementalPackager(ctx context.Context, lister PublishedImagesLister, rootImage string) build.Packager {
+ layeredPackager := incrementalPackager{
+ lister: lister,
+ rootImage: rootImage,
+ }
+ layeredPackager.commonPackager = newBasePackagerWithSelector(ctx, layeredPackager.selectArtifactsToUpload)
+ return &layeredPackager
}
-// NewS2IIncrementalPublisher creates a new publisher that is able to do a Openshift S2I binary builds on top of other builds
-func NewS2IIncrementalPublisher(ctx context.Context, namespace string, lister PublishedImagesLister) build.Publisher {
- layeredPublisher := s2iIncrementalPublisher{
- lister: lister,
- }
- layeredPublisher.s2iPublisher = newS2IPublisher(ctx, namespace, layeredPublisher.selectArtifactsToUpload)
- return &layeredPublisher
+func (p *incrementalPackager) Package(req build.Request, assembled build.AssembledOutput) <-chan build.PackagedOutput {
+ return p.commonPackager.Package(req, assembled)
}
-func (p *s2iIncrementalPublisher) Publish(req build.Request, assembled build.AssembledOutput) <-chan build.PublishedOutput {
- return p.s2iPublisher.Publish(req, assembled)
+func (p *incrementalPackager) Cleanup(output build.PackagedOutput) {
+ p.commonPackager.Cleanup(output)
}
-func (p *s2iIncrementalPublisher) selectArtifactsToUpload(entries []build.ClasspathEntry) (string, []build.ClasspathEntry, error) {
+func (p *incrementalPackager) selectArtifactsToUpload(entries []build.ClasspathEntry) (string, []build.ClasspathEntry, error) {
images, err := p.lister.ListPublishedImages()
if err != nil {
return "", nil, err
@@ -71,10 +66,10 @@ func (p *s2iIncrementalPublisher) selectArtifactsToUpload(entries []build.Classp
}
// return default selection
- return baseImage, entries, nil
+ return p.rootImage, entries, nil
}
-func (p *s2iIncrementalPublisher) findBestImage(images []PublishedImage, entries []build.ClasspathEntry) (*PublishedImage, map[string]bool) {
+func (p *incrementalPackager) findBestImage(images []PublishedImage, entries []build.ClasspathEntry) (*PublishedImage, map[string]bool) {
if len(images) == 0 {
return nil, nil
}
diff --git a/pkg/install/operator.go b/pkg/build/packager/types.go
similarity index 57%
copy from pkg/install/operator.go
copy to pkg/build/packager/types.go
index eb3ac4c..4965e28 100644
--- a/pkg/install/operator.go
+++ b/pkg/build/packager/types.go
@@ -15,27 +15,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package install
+package packager
-// Operator --
-func Operator(namespace string) error {
- return Resources(namespace,
- "operator-service-account.yaml",
- "operator-role-openshift.yaml", // TODO distinguish between Openshift and Kubernetes
- "operator-role-binding.yaml",
- "operator-deployment.yaml",
- "operator-service.yaml",
- )
+// PublishedImage represent a base image that can be used as starting point
+type PublishedImage struct {
+ Image string
+ Classpath []string
}
-// Platform installs the platform custom resource
-func Platform(namespace string) error {
- return Resource(namespace, "platform-cr.yaml")
-}
-
-// Example --
-func Example(namespace string) error {
- return Resources(namespace,
- "cr-example.yaml",
- )
-}
+// PublishedImagesLister allows to list all images already published
+type PublishedImagesLister interface {
+ ListPublishedImages() ([]PublishedImage, error)
+}
\ No newline at end of file
diff --git a/pkg/build/publish/kaniko_publisher.go b/pkg/build/publish/kaniko_publisher.go
new file mode 100644
index 0000000..7c5a511
--- /dev/null
+++ b/pkg/build/publish/kaniko_publisher.go
@@ -0,0 +1,225 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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 publish
+
+import (
+ tarutils "archive/tar"
+ "context"
+ "github.com/apache/camel-k/pkg/util/kubernetes"
+ "io"
+ "io/ioutil"
+ "os"
+ "path"
+ "time"
+
+ "github.com/apache/camel-k/pkg/build"
+ "github.com/operator-framework/operator-sdk/pkg/sdk"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+type kanikoPublisher struct {
+ buffer chan kanikoPublishOperation
+ namespace string
+ registry string
+}
+
+type kanikoPublishOperation struct {
+ request build.Request
+ assembled build.AssembledOutput
+ packaged build.PackagedOutput
+ output chan build.PublishedOutput
+}
+
+// NewKanikoPublisher creates a new publisher doing a Kaniko image push
+func NewKanikoPublisher(ctx context.Context, namespace string, registry string) build.Publisher {
+ publisher := kanikoPublisher{
+ buffer: make(chan kanikoPublishOperation, 100),
+ namespace: namespace,
+ registry: registry,
+ }
+ go publisher.publishCycle(ctx)
+ return &publisher
+}
+
+func (b *kanikoPublisher) Publish(request build.Request, assembled build.AssembledOutput, packaged build.PackagedOutput) <-chan build.PublishedOutput {
+ res := make(chan build.PublishedOutput, 1)
+ op := kanikoPublishOperation{
+ request: request,
+ assembled: assembled,
+ packaged: packaged,
+ output: res,
+ }
+ b.buffer <- op
+ return res
+}
+
+func (b *kanikoPublisher) publishCycle(ctx context.Context) {
+ for {
+ select {
+ case <-ctx.Done():
+ b.buffer = nil
+ return
+ case op := <-b.buffer:
+ now := time.Now()
+ logrus.Info("Starting a new image publication")
+ res := b.execute(op.request, op.assembled, op.packaged)
+ elapsed := time.Now().Sub(now)
+
+ if res.Error != nil {
+ logrus.Error("Error during publication (total time ", elapsed.Seconds(), " seconds): ", res.Error)
+ } else {
+ logrus.Info("Publication completed in ", elapsed.Seconds(), " seconds")
+ }
+
+ op.output <- res
+ }
+ }
+}
+
+func (b *kanikoPublisher) execute(request build.Request, assembled build.AssembledOutput, packaged build.PackagedOutput) build.PublishedOutput {
+ image, err := b.publish(packaged.TarFile, packaged.BaseImage, request)
+ if err != nil {
+ return build.PublishedOutput{Error: err}
+ }
+
+ return build.PublishedOutput{Image: image}
+}
+
+func (b *kanikoPublisher) publish(tarFile string, baseImageName string, source build.Request) (string, error) {
+ image := b.registry + "/" + b.namespace + "/camel-k-" + source.Identifier.Name + ":" + source.Identifier.Qualifier
+ contextDir, err := b.prepareContext(tarFile, baseImageName)
+ if err != nil {
+ return "", err
+ }
+ pod := v1.Pod{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: v1.SchemeGroupVersion.String(),
+ Kind: "Pod",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: b.namespace,
+ Name: "camel-k-" + source.Identifier.Name,
+ },
+ Spec: v1.PodSpec{
+ Containers: []v1.Container{
+ {
+ Name: "kaniko",
+ Image: "gcr.io/kaniko-project/executor:latest",
+ Args: []string{
+ "--dockerfile=Dockerfile",
+ "--context=" + contextDir,
+ "--destination=" + image,
+ "--insecure",
+ },
+ VolumeMounts: []v1.VolumeMount{
+ {
+ Name: "camel-k-builder",
+ MountPath: "/workspace",
+ },
+ },
+ },
+ },
+ RestartPolicy: v1.RestartPolicyNever,
+ Volumes: []v1.Volume{
+ {
+ Name: "camel-k-builder",
+ VolumeSource: v1.VolumeSource{
+ PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
+ ClaimName: "camel-k-builder",
+ ReadOnly: true,
+ },
+ },
+ },
+ },
+ },
+ }
+
+ sdk.Delete(&pod)
+ err = sdk.Create(&pod)
+ if err != nil {
+ return "", errors.Wrap(err, "cannot create kaniko builder pod")
+ }
+
+ err = kubernetes.WaitCondition(&pod, func(obj interface{}) (bool, error) {
+ if val, ok := obj.(*v1.Pod); ok {
+ if val.Status.Phase == v1.PodSucceeded {
+ return true, nil
+ } else if val.Status.Phase == v1.PodFailed {
+ return false, errors.New("build failed")
+ }
+ }
+ return false, nil
+ }, 5*time.Minute)
+
+ if err != nil {
+ return "", err
+ }
+
+ return image, nil
+}
+
+func (b *kanikoPublisher) prepareContext(tarName string, baseImage string) (string, error) {
+ baseDir, _ := path.Split(tarName)
+ contextDir := path.Join(baseDir, "context")
+ if err := b.unTar(tarName, contextDir); err != nil {
+ return "", err
+ }
+
+ dockerFileContent := []byte(`
+ FROM ` + baseImage + `
+ ADD . /deployments
+ `)
+ if err := ioutil.WriteFile(path.Join(contextDir, "Dockerfile"), dockerFileContent, 0777); err != nil {
+ return "", err
+ }
+ return contextDir, nil
+}
+
+func (b *kanikoPublisher) unTar(tarName string, dir string) error {
+ file, err := os.Open(tarName)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ reader := tarutils.NewReader(file)
+ for {
+ header, err := reader.Next()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return err
+ }
+ targetName := path.Join(dir, header.Name)
+ targetDir, _ := path.Split(targetName)
+ if err := os.MkdirAll(targetDir, 0777); err != nil {
+ return err
+ }
+ buffer, err := ioutil.ReadAll(reader)
+ if err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(targetName, buffer, os.FileMode(header.Mode)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/pkg/build/publish/s2i_publisher.go b/pkg/build/publish/s2i_publisher.go
index 97147a7..09a0105 100644
--- a/pkg/build/publish/s2i_publisher.go
+++ b/pkg/build/publish/s2i_publisher.go
@@ -20,14 +20,11 @@ package publish
import (
"context"
"io/ioutil"
- "path"
"time"
"github.com/apache/camel-k/pkg/build"
"github.com/apache/camel-k/pkg/util/kubernetes"
"github.com/apache/camel-k/pkg/util/kubernetes/customclient"
- "github.com/apache/camel-k/pkg/util/maven"
- "github.com/apache/camel-k/pkg/util/tar"
buildv1 "github.com/openshift/api/build/v1"
imagev1 "github.com/openshift/api/image/v1"
"github.com/operator-framework/operator-sdk/pkg/sdk"
@@ -39,49 +36,34 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
-const (
- artifactDirPrefix = "s2i-"
- baseImage = "fabric8/s2i-java:2.3"
-)
-
type s2iPublisher struct {
- buffer chan publishOperation
+ buffer chan s2iPublishOperation
namespace string
- uploadedArtifactsSelector
}
-type publishOperation struct {
+type s2iPublishOperation struct {
request build.Request
assembled build.AssembledOutput
+ packaged build.PackagedOutput
output chan build.PublishedOutput
}
-type uploadedArtifactsSelector func([]build.ClasspathEntry) (string, []build.ClasspathEntry, error)
-
// NewS2IPublisher creates a new publisher doing a Openshift S2I binary build
func NewS2IPublisher(ctx context.Context, namespace string) build.Publisher {
- identitySelector := func(entries []build.ClasspathEntry) (string, []build.ClasspathEntry, error) {
- return baseImage, entries, nil
- }
- return newS2IPublisher(ctx, namespace, identitySelector)
-}
-
-// NewS2IPublisher creates a new publisher doing a Openshift S2I binary build
-func newS2IPublisher(ctx context.Context, namespace string, uploadedArtifactsSelector uploadedArtifactsSelector) *s2iPublisher {
publisher := s2iPublisher{
- buffer: make(chan publishOperation, 100),
- namespace: namespace,
- uploadedArtifactsSelector: uploadedArtifactsSelector,
+ buffer: make(chan s2iPublishOperation, 100),
+ namespace: namespace,
}
go publisher.publishCycle(ctx)
return &publisher
}
-func (b *s2iPublisher) Publish(request build.Request, assembled build.AssembledOutput) <-chan build.PublishedOutput {
+func (b *s2iPublisher) Publish(request build.Request, assembled build.AssembledOutput, packaged build.PackagedOutput) <-chan build.PublishedOutput {
res := make(chan build.PublishedOutput, 1)
- op := publishOperation{
+ op := s2iPublishOperation{
request: request,
assembled: assembled,
+ packaged: packaged,
output: res,
}
b.buffer <- op
@@ -97,7 +79,7 @@ func (b *s2iPublisher) publishCycle(ctx context.Context) {
case op := <-b.buffer:
now := time.Now()
logrus.Info("Starting a new image publication")
- res := b.execute(op.request, op.assembled)
+ res := b.execute(op.request, op.assembled, op.packaged)
elapsed := time.Now().Sub(now)
if res.Error != nil {
@@ -111,18 +93,8 @@ func (b *s2iPublisher) publishCycle(ctx context.Context) {
}
}
-func (b *s2iPublisher) execute(request build.Request, assembled build.AssembledOutput) build.PublishedOutput {
- baseImageName, selectedArtifacts, err := b.uploadedArtifactsSelector(assembled.Classpath)
- if err != nil {
- return build.PublishedOutput{Error: err}
- }
-
- tarFile, err := b.createTar(assembled, selectedArtifacts)
- if err != nil {
- return build.PublishedOutput{Error: err}
- }
-
- image, err := b.publish(tarFile, baseImageName, request)
+func (b *s2iPublisher) execute(request build.Request, assembled build.AssembledOutput, packaged build.PackagedOutput) build.PublishedOutput {
+ image, err := b.publish(packaged.TarFile, packaged.BaseImage, request)
if err != nil {
return build.PublishedOutput{Error: err}
}
@@ -253,50 +225,3 @@ func (b *s2iPublisher) publish(tarFile string, imageName string, source build.Re
}
return is.Status.DockerImageRepository + ":" + source.Identifier.Qualifier, nil
}
-
-func (b *s2iPublisher) createTar(assembled build.AssembledOutput, selectedArtifacts []build.ClasspathEntry) (string, error) {
- artifactDir, err := ioutil.TempDir("", artifactDirPrefix)
- if err != nil {
- return "", errors.Wrap(err, "could not create temporary dir for s2i artifacts")
- }
-
- tarFileName := path.Join(artifactDir, "occi.tar")
- tarAppender, err := tar.NewAppender(tarFileName)
- if err != nil {
- return "", err
- }
- defer tarAppender.Close()
-
- tarDir := "dependencies/"
- for _, entry := range selectedArtifacts {
- gav, err := maven.ParseGAV(entry.ID)
- if err != nil {
- return "", nil
- }
-
- tarPath := path.Join(tarDir, gav.GroupID)
- _, err = tarAppender.AddFile(entry.Location, tarPath)
- if err != nil {
- return "", err
- }
- }
-
- cp := ""
- for _, entry := range assembled.Classpath {
- gav, err := maven.ParseGAV(entry.ID)
- if err != nil {
- return "", nil
- }
- tarPath := path.Join(tarDir, gav.GroupID)
- _, fileName := path.Split(entry.Location)
- fileName = path.Join(tarPath, fileName)
- cp += fileName + "\n"
- }
-
- err = tarAppender.AppendData([]byte(cp), "classpath")
- if err != nil {
- return "", err
- }
-
- return tarFileName, nil
-}
diff --git a/pkg/client/cmd/install.go b/pkg/client/cmd/install.go
index 0d20021..abe61c5 100644
--- a/pkg/client/cmd/install.go
+++ b/pkg/client/cmd/install.go
@@ -41,6 +41,7 @@ func newCmdInstall(rootCmdOptions *RootCmdOptions) *cobra.Command {
cmd.Flags().BoolVar(&options.clusterSetupOnly, "cluster-setup", false, "Execute cluster-wide operations only (may require admin rights)")
cmd.Flags().BoolVar(&options.exampleSetup, "example", false, "Install example integration")
+ cmd.Flags().StringVar(&options.registry, "registry", "", "A Docker registry that can be used to publish images")
cmd.ParseFlags(os.Args)
return &cmd
@@ -50,6 +51,7 @@ type installCmdOptions struct {
*RootCmdOptions
clusterSetupOnly bool
exampleSetup bool
+ registry string
}
func (o *installCmdOptions) install(cmd *cobra.Command, args []string) error {
@@ -69,7 +71,7 @@ func (o *installCmdOptions) install(cmd *cobra.Command, args []string) error {
return err
}
- err = install.Platform(namespace)
+ err = install.Platform(namespace, o.registry)
if err != nil {
return err
}
diff --git a/pkg/install/common.go b/pkg/install/common.go
index bef7f6f..6d7c61f 100644
--- a/pkg/install/common.go
+++ b/pkg/install/common.go
@@ -24,6 +24,7 @@ import (
"github.com/operator-framework/operator-sdk/pkg/sdk"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
)
// Resources installs named resources from the project resource directory
@@ -43,11 +44,16 @@ func Resource(namespace string, name string) error {
return err
}
+ return RuntimeObject(namespace, obj)
+}
+
+// RuntimeObject installs a single runtime object
+func RuntimeObject(namespace string, obj runtime.Object) error {
if metaObject, ok := obj.(metav1.Object); ok {
metaObject.SetNamespace(namespace)
}
- err = sdk.Create(obj)
+ err := sdk.Create(obj)
if err != nil && errors.IsAlreadyExists(err) {
// Don't recreate Service object
if obj.GetObjectKind().GroupVersionKind().Kind == "Service" {
@@ -60,6 +66,9 @@ func Resource(namespace string, name string) error {
if obj.GetObjectKind().GroupVersionKind().Kind == v1alpha1.IntegrationPlatformKind {
return nil
}
+ if obj.GetObjectKind().GroupVersionKind().Kind == "PersistentVolumeClaim" {
+ return nil
+ }
return sdk.Update(obj)
}
return err
diff --git a/pkg/install/operator.go b/pkg/install/operator.go
index eb3ac4c..f0ec970 100644
--- a/pkg/install/operator.go
+++ b/pkg/install/operator.go
@@ -17,20 +17,68 @@ limitations under the License.
package install
+import (
+ "errors"
+ "github.com/apache/camel-k/deploy"
+ "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+ "github.com/apache/camel-k/pkg/util/kubernetes"
+ "github.com/apache/camel-k/pkg/util/minishift"
+ "github.com/apache/camel-k/pkg/util/openshift"
+)
+
// Operator --
func Operator(namespace string) error {
+ isOpenshift, err := openshift.IsOpenShift()
+ if err != nil {
+ return err
+ }
+ var operatorRole string
+ if isOpenshift {
+ operatorRole = "operator-role-openshift.yaml"
+ } else {
+ operatorRole = "operator-role-kubernetes.yaml"
+ }
return Resources(namespace,
"operator-service-account.yaml",
- "operator-role-openshift.yaml", // TODO distinguish between Openshift and Kubernetes
+ operatorRole,
"operator-role-binding.yaml",
+ "builder-pvc.yaml",
"operator-deployment.yaml",
"operator-service.yaml",
)
}
// Platform installs the platform custom resource
-func Platform(namespace string) error {
- return Resource(namespace, "platform-cr.yaml")
+func Platform(namespace string, registry string) error {
+ isOpenshift, err := openshift.IsOpenShift()
+ if err != nil {
+ return err
+ }
+ if isOpenshift {
+ return Resource(namespace, "platform-cr.yaml")
+ }
+ platform, err := kubernetes.LoadResourceFromYaml(deploy.Resources["platform-cr.yaml"])
+ if err != nil {
+ return err
+ }
+ if pl, ok := platform.(*v1alpha1.IntegrationPlatform); !ok {
+ panic("cannot find integration platform template")
+ } else {
+ if registry == "" {
+ // This operation should be done here in the installer
+ // because the operator is not allowed to look into the "kube-system" namespace
+ minishiftRegistry, err := minishift.FindRegistry()
+ if err != nil {
+ return err
+ }
+ if minishiftRegistry == nil {
+ return errors.New("cannot find automatically a registry where to push images")
+ }
+ registry = *minishiftRegistry
+ }
+ pl.Spec.Build.Registry = registry
+ return RuntimeObject(namespace, pl)
+ }
}
// Example --
diff --git a/pkg/platform/build.go b/pkg/platform/build.go
index 994ced6..6e75ad2 100644
--- a/pkg/platform/build.go
+++ b/pkg/platform/build.go
@@ -23,6 +23,7 @@ import (
"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
"github.com/apache/camel-k/pkg/build"
"github.com/apache/camel-k/pkg/build/assemble"
+ "github.com/apache/camel-k/pkg/build/packager"
"github.com/apache/camel-k/pkg/build/publish"
"github.com/operator-framework/operator-sdk/pkg/sdk"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -44,8 +45,13 @@ func GetPlatformBuildManager(ctx context.Context, namespace string) (*build.Mana
assembler := assemble.NewMavenAssembler(ctx)
if pl.Spec.Build.PublishStrategy == v1alpha1.IntegrationPlatformBuildPublishStrategyS2I {
- publisher := publish.NewS2IIncrementalPublisher(ctx, namespace, newContextLister(namespace))
- buildManager = build.NewManager(ctx, assembler, publisher)
+ packaging := packager.NewS2IIncrementalPackager(ctx, newContextLister(namespace))
+ publisher := publish.NewS2IPublisher(ctx, namespace)
+ buildManager = build.NewManager(ctx, assembler, packaging, publisher)
+ } else if pl.Spec.Build.PublishStrategy == v1alpha1.IntegrationPlatformBuildPublishStrategyKaniko && pl.Spec.Build.Registry != "" {
+ packaging := packager.NewJavaStandardPackager(ctx)
+ publisher := publish.NewKanikoPublisher(ctx, namespace, pl.Spec.Build.Registry)
+ buildManager = build.NewManager(ctx, assembler, packaging, publisher)
}
if buildManager == nil {
@@ -66,14 +72,14 @@ func newContextLister(namespace string) contextLister {
}
}
-func (l contextLister) ListPublishedImages() ([]publish.PublishedImage, error) {
+func (l contextLister) ListPublishedImages() ([]packager.PublishedImage, error) {
list := v1alpha1.NewIntegrationContextList()
err := sdk.List(l.namespace, &list, sdk.WithListOptions(&metav1.ListOptions{}))
if err != nil {
return nil, err
}
- images := make([]publish.PublishedImage, 0)
+ images := make([]packager.PublishedImage, 0)
for _, ctx := range list.Items {
if ctx.Status.Phase != v1alpha1.IntegrationContextPhaseReady || ctx.Labels == nil {
continue
@@ -82,7 +88,7 @@ func (l contextLister) ListPublishedImages() ([]publish.PublishedImage, error) {
continue
}
- images = append(images, publish.PublishedImage{
+ images = append(images, packager.PublishedImage{
Image: ctx.Status.Image,
Classpath: ctx.Status.Classpath,
})
diff --git a/pkg/stub/action/platform/initialize.go b/pkg/stub/action/platform/initialize.go
index 4215daa..07ea31f 100644
--- a/pkg/stub/action/platform/initialize.go
+++ b/pkg/stub/action/platform/initialize.go
@@ -18,12 +18,12 @@ limitations under the License.
package platform
import (
+ "errors"
"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
platformutils "github.com/apache/camel-k/pkg/platform"
- "github.com/operator-framework/operator-sdk/pkg/k8sclient"
+ "github.com/apache/camel-k/pkg/util/openshift"
"github.com/operator-framework/operator-sdk/pkg/sdk"
"github.com/sirupsen/logrus"
- "k8s.io/apimachinery/pkg/api/errors"
)
// NewInitializeAction returns a action that initializes the platform configuration when not provided by the user
@@ -63,9 +63,9 @@ func (action *initializeAction) Handle(platform *v1alpha1.IntegrationPlatform) e
// update missing fields in the resource
if target.Spec.Cluster == "" {
// determine the kind of cluster the platform in installed into
- if openshift, err := action.isOpenshift(); err != nil {
+ if isOpenshift, err := openshift.IsOpenShift(); err != nil {
return err
- } else if openshift {
+ } else if isOpenshift {
target.Spec.Cluster = v1alpha1.IntegrationPlatformClusterOpenShift
} else {
target.Spec.Cluster = v1alpha1.IntegrationPlatformClusterKubernetes
@@ -77,26 +77,19 @@ func (action *initializeAction) Handle(platform *v1alpha1.IntegrationPlatform) e
target.Spec.Build.PublishStrategy = v1alpha1.IntegrationPlatformBuildPublishStrategyS2I
} else {
target.Spec.Build.PublishStrategy = v1alpha1.IntegrationPlatformBuildPublishStrategyKaniko
- // TODO discover registry location
}
}
+ if target.Spec.Build.PublishStrategy == v1alpha1.IntegrationPlatformBuildPublishStrategyKaniko && target.Spec.Build.Registry == "" {
+ return errors.New("no registry specified for publishing images")
+ }
+
// next status
logrus.Info("Platform ", target.Name, " transitioning to state ", v1alpha1.IntegrationPlatformPhaseCreating)
target.Status.Phase = v1alpha1.IntegrationPlatformPhaseCreating
return sdk.Update(target)
}
-func (action *initializeAction) isOpenshift() (bool, error) {
- _, err := k8sclient.GetKubeClient().Discovery().ServerResourcesForGroupVersion("image.openshift.io/v1")
- if err != nil && errors.IsNotFound(err) {
- return false, nil
- } else if err != nil {
- return false, err
- }
- return true, nil
-}
-
func (action *initializeAction) isDuplicate(thisPlatform *v1alpha1.IntegrationPlatform) (bool, error) {
platforms, err := platformutils.ListPlatforms(thisPlatform.Namespace)
if err != nil {
diff --git a/pkg/util/minishift/minishift.go b/pkg/util/minishift/minishift.go
new file mode 100644
index 0000000..9e5465c
--- /dev/null
+++ b/pkg/util/minishift/minishift.go
@@ -0,0 +1,60 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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 minishift contains utilities for Minishift deployments
+package minishift
+
+import (
+ "github.com/operator-framework/operator-sdk/pkg/sdk"
+ "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "strconv"
+)
+
+const (
+ registryNamespace = "kube-system"
+)
+
+// FindRegistry returns the Minishift registry location if any
+func FindRegistry() (*string, error) {
+ svcs := v1.ServiceList{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: v1.SchemeGroupVersion.String(),
+ Kind: "Service",
+ },
+ }
+ options := metav1.ListOptions{
+ LabelSelector: "kubernetes.io/minikube-addons=registry",
+ }
+ if err := sdk.List(registryNamespace, &svcs, sdk.WithListOptions(&options)); err != nil {
+ return nil, err
+ }
+ if len(svcs.Items) == 0 {
+ return nil, nil
+ }
+ svc := svcs.Items[0]
+ ip := svc.Spec.ClusterIP
+ portStr := ""
+ if len(svc.Spec.Ports) > 0 {
+ port := svc.Spec.Ports[0].Port
+ if port > 0 && port != 80 {
+ portStr = ":" + strconv.FormatInt(int64(port), 10)
+ }
+ }
+ registry := ip + portStr
+ return ®istry, nil
+}
diff --git a/pkg/install/operator.go b/pkg/util/openshift/openshift.go
similarity index 57%
copy from pkg/install/operator.go
copy to pkg/util/openshift/openshift.go
index eb3ac4c..f5b69b0 100644
--- a/pkg/install/operator.go
+++ b/pkg/util/openshift/openshift.go
@@ -15,27 +15,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package install
+package openshift
-// Operator --
-func Operator(namespace string) error {
- return Resources(namespace,
- "operator-service-account.yaml",
- "operator-role-openshift.yaml", // TODO distinguish between Openshift and Kubernetes
- "operator-role-binding.yaml",
- "operator-deployment.yaml",
- "operator-service.yaml",
- )
-}
+import (
+ "github.com/operator-framework/operator-sdk/pkg/k8sclient"
+ "k8s.io/apimachinery/pkg/api/errors"
+)
-// Platform installs the platform custom resource
-func Platform(namespace string) error {
- return Resource(namespace, "platform-cr.yaml")
-}
-
-// Example --
-func Example(namespace string) error {
- return Resources(namespace,
- "cr-example.yaml",
- )
-}
+// IsOpenShift returns true if we are connected to a OpenShift cluster
+func IsOpenShift() (bool, error) {
+ _, err := k8sclient.GetKubeClient().Discovery().ServerResourcesForGroupVersion("image.openshift.io/v1")
+ if err != nil && errors.IsNotFound(err) {
+ return false, nil
+ } else if err != nil {
+ return false, err
+ }
+ return true, nil
+}
\ No newline at end of file