You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by GitBox <gi...@apache.org> on 2019/01/13 14:47:12 UTC

[camel-k] Diff for: [GitHub] lburgazzoli closed pull request #325: Release some predefined images for Knative

diff --git a/.gitignore b/.gitignore
index e43f8299..6361983f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 # Binary files
 /camel-k
 /kamel
+/publisher
 
 # Released Packages
 *.tar.gz
diff --git a/cmd/util/publisher/publisher.go b/cmd/util/publisher/publisher.go
new file mode 100644
index 00000000..0ce04621
--- /dev/null
+++ b/cmd/util/publisher/publisher.go
@@ -0,0 +1,199 @@
+/*
+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 main
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"sort"
+	"strings"
+	"time"
+
+	"github.com/apache/camel-k/deploy"
+	"github.com/apache/camel-k/pkg/apis"
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"github.com/apache/camel-k/pkg/builder"
+	"github.com/apache/camel-k/pkg/platform/images"
+	"github.com/apache/camel-k/pkg/util/camel"
+	"github.com/apache/camel-k/pkg/util/kubernetes"
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+	clientscheme "k8s.io/client-go/kubernetes/scheme"
+)
+
+// PublisherOptions --
+type PublisherOptions struct {
+	StartWith     string
+	EndWith       string
+	BuildAttempts int
+}
+
+// Publishes predefined images for all Camel components
+func main() {
+	options := PublisherOptions{}
+
+	var cmd = cobra.Command{
+		Use:   "publisher",
+		Short: "Publisher allows to publish base images before a release",
+		Run:   options.run,
+	}
+
+	cmd.Flags().StringVar(&options.StartWith, "start-with", "", "The component to start with")
+	cmd.Flags().StringVar(&options.EndWith, "end-with", "", "The component to end with")
+	cmd.Flags().IntVar(&options.BuildAttempts, "attempts", 5, "The maximum number of build attempts for each image")
+
+	panicIfErr(cmd.Execute())
+}
+
+func (options *PublisherOptions) run(cmd *cobra.Command, args []string) {
+	scheme := clientscheme.Scheme
+	panicIfErr(apis.AddToScheme(scheme))
+
+	platRun, err := kubernetes.LoadResourceFromYaml(scheme, deploy.Resources["platform-cr.yaml"])
+	panicIfErr(err)
+
+	p := platRun.(*v1alpha1.IntegrationPlatform)
+
+	started := options.StartWith == ""
+
+	keys := make([]string, 0, len(camel.Runtime.Artifacts))
+	for k := range camel.Runtime.Artifacts {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+
+	for _, k := range keys {
+		a := camel.Runtime.Artifacts[k]
+		if a.GroupID == "org.apache.camel" {
+			component := strings.TrimPrefix(a.ArtifactID, "camel-")
+			if options.StartWith == component {
+				started = true
+			}
+
+			if started {
+				fmt.Printf("building component %s\n", component)
+				options.buildWithAttempts(component, p.Spec.Build.CamelVersion)
+			} else {
+				fmt.Printf("skipping component %s\n", component)
+			}
+
+			if options.EndWith == component {
+				fmt.Println("reached final component")
+				break
+			}
+		}
+	}
+}
+
+func (options *PublisherOptions) buildWithAttempts(component string, camelVersion string) {
+	var err error
+	for i := 0; i < options.BuildAttempts; i++ {
+		err = options.build(component, camelVersion)
+		if err != nil {
+			sleepTime := 5 * (i + 1)
+			fmt.Printf("waiting %d seconds to recover from error %v\n", sleepTime, err)
+			time.Sleep(time.Duration(sleepTime) * time.Second)
+		} else {
+			return
+		}
+	}
+	panicIfErr(errors.Wrap(err, "build failed after maximum number of attempts"))
+}
+
+func (options *PublisherOptions) build(component string, camelVersion string) error {
+	dir, err := ioutil.TempDir(os.TempDir(), "camel-k-build-")
+	if err != nil {
+		return err
+	}
+	defer os.RemoveAll(dir)
+
+	dependencies := make([]string, 0)
+	for d := range images.StandardDependencies {
+		dependencies = append(dependencies, d)
+	}
+	dependencies = append(dependencies, images.BaseDependency)
+	dependencies = append(dependencies, "camel:"+component)
+
+	ctx := builder.Context{
+		C:    context.TODO(),
+		Path: dir,
+		Request: builder.Request{
+			Platform: v1alpha1.IntegrationPlatformSpec{
+				Build: v1alpha1.IntegrationPlatformBuildSpec{
+					CamelVersion: camelVersion,
+				},
+			},
+			Dependencies: dependencies,
+		},
+	}
+
+	err = builder.GenerateProject(&ctx)
+	if err != nil {
+		return err
+	}
+	err = builder.ComputeDependencies(&ctx)
+	if err != nil {
+		return err
+	}
+	err = builder.StandardPackager(&ctx)
+	if err != nil {
+		return err
+	}
+
+	archiveDir, archiveName := filepath.Split(ctx.Archive)
+	// nolint: gosec
+	dockerfile := `
+		FROM fabric8/s2i-java:2.3
+		ADD ` + archiveName + ` /deployments/
+	`
+
+	err = ioutil.WriteFile(path.Join(archiveDir, "Dockerfile"), []byte(dockerfile), 0777)
+	if err != nil {
+		return err
+	}
+
+	image := images.PredefinedImageNameFor(component)
+	buildCmd := exec.Command("docker", "build", "-t", image, archiveDir)
+	buildCmd.Stdout = os.Stdout
+	buildCmd.Stderr = os.Stderr
+	err = buildCmd.Run()
+	if err != nil {
+		return err
+	}
+
+	pushCmd := exec.Command("docker", "push", image)
+	pushCmd.Stdout = os.Stdout
+	pushCmd.Stderr = os.Stderr
+	err = pushCmd.Run()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func panicIfErr(err error) {
+	if err != nil {
+		panic(err)
+	}
+}
diff --git a/pkg/builder/s2i/s2i.go b/pkg/builder/s2i/s2i.go
index df68f3ff..8824e5df 100644
--- a/pkg/builder/s2i/s2i.go
+++ b/pkg/builder/s2i/s2i.go
@@ -17,7 +17,9 @@ limitations under the License.
 
 package s2i
 
-import "github.com/apache/camel-k/pkg/builder"
+import (
+	"github.com/apache/camel-k/pkg/builder"
+)
 
 // DefaultSteps --
 var DefaultSteps = []builder.Step{
diff --git a/pkg/controller/integration/build_context.go b/pkg/controller/integration/build_context.go
index 427794c0..abfb2fd5 100644
--- a/pkg/controller/integration/build_context.go
+++ b/pkg/controller/integration/build_context.go
@@ -21,16 +21,12 @@ import (
 	"context"
 	"fmt"
 
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/trait"
-
-	"github.com/sirupsen/logrus"
-
 	"github.com/apache/camel-k/pkg/util"
 	"github.com/apache/camel-k/pkg/util/digest"
-
 	"github.com/rs/xid"
-
-	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"github.com/sirupsen/logrus"
 )
 
 // NewBuildContextAction create an action that handles integration context build
diff --git a/pkg/controller/integration/util.go b/pkg/controller/integration/util.go
index 9c31ec82..e9fe9748 100644
--- a/pkg/controller/integration/util.go
+++ b/pkg/controller/integration/util.go
@@ -20,13 +20,17 @@ package integration
 import (
 	"context"
 
-	"github.com/apache/camel-k/pkg/util"
-	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
-
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"github.com/apache/camel-k/pkg/util"
 	"github.com/pkg/errors"
+	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
 )
 
+var allowedLookupLabels = map[string]bool{
+	v1alpha1.IntegrationContextTypePlatform: true,
+	v1alpha1.IntegrationContextTypeExternal: true,
+}
+
 // LookupContextForIntegration --
 func LookupContextForIntegration(ctx context.Context, c k8sclient.Reader, integration *v1alpha1.Integration) (*v1alpha1.IntegrationContext, error) {
 	if integration.Status.Context != "" {
@@ -50,7 +54,7 @@ func LookupContextForIntegration(ctx context.Context, c k8sclient.Reader, integr
 
 	for _, ctx := range ctxList.Items {
 		ctx := ctx // pin
-		if ctx.Labels["camel.apache.org/context.type"] == v1alpha1.IntegrationContextTypePlatform {
+		if allowed, ok := allowedLookupLabels[ctx.Labels["camel.apache.org/context.type"]]; ok && allowed {
 			ideps := len(integration.Status.Dependencies)
 			cdeps := len(ctx.Spec.Dependencies)
 
diff --git a/pkg/controller/integrationcontext/initialize.go b/pkg/controller/integrationcontext/initialize.go
index 80b9452a..f84ee442 100644
--- a/pkg/controller/integrationcontext/initialize.go
+++ b/pkg/controller/integrationcontext/initialize.go
@@ -22,6 +22,7 @@ import (
 
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/platform"
+	"github.com/apache/camel-k/pkg/trait"
 	"github.com/apache/camel-k/pkg/util/digest"
 	"github.com/sirupsen/logrus"
 )
@@ -52,10 +53,15 @@ func (action *initializeAction) Handle(ctx context.Context, ictx *v1alpha1.Integ
 
 	target := ictx.DeepCopy()
 
-	// by default the context should be build
-	target.Status.Phase = v1alpha1.IntegrationContextPhaseBuilding
+	_, err := trait.Apply(ctx, action.client, nil, target)
+	if err != nil {
+		return err
+	}
 
-	if target.Spec.Image != "" {
+	if target.Spec.Image == "" {
+		// by default the context should be build
+		target.Status.Phase = v1alpha1.IntegrationContextPhaseBuilding
+	} else {
 		// but in case it has been created from an image, mark the
 		// context as ready
 		target.Status.Phase = v1alpha1.IntegrationContextPhaseReady
diff --git a/pkg/controller/integrationplatform/create.go b/pkg/controller/integrationplatform/create.go
index fa203c2b..fd2ddec3 100644
--- a/pkg/controller/integrationplatform/create.go
+++ b/pkg/controller/integrationplatform/create.go
@@ -51,6 +51,11 @@ func (action *createAction) Handle(ctx context.Context, platform *v1alpha1.Integ
 		res := make([]string, 0, l)
 
 		for _, c := range platform.Spec.Resources.Contexts {
+			if c == p.NoContext {
+				// Signals nothing to install
+				continue
+			}
+
 			//
 			// Assuming that if the resource ends with a yaml extension, the full
 			// resource name is provided
@@ -62,10 +67,12 @@ func (action *createAction) Handle(ctx context.Context, platform *v1alpha1.Integ
 			res = append(res, c)
 		}
 
-		logrus.Info("Installing custom platform resources")
-		err := install.Resources(ctx, action.client, platform.Namespace, res...)
-		if err != nil {
-			return err
+		if len(res) > 0 {
+			logrus.Info("Installing custom platform resources")
+			err := install.Resources(ctx, action.client, platform.Namespace, res...)
+			if err != nil {
+				return err
+			}
 		}
 	} else {
 		logrus.Info("Installing default platform resources")
diff --git a/pkg/platform/images/doc.go b/pkg/platform/images/doc.go
new file mode 100644
index 00000000..cdf53c0a
--- /dev/null
+++ b/pkg/platform/images/doc.go
@@ -0,0 +1,19 @@
+/*
+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 images contains information for retrieval of platform predefined images
+package images
diff --git a/pkg/platform/images/images.go b/pkg/platform/images/images.go
new file mode 100644
index 00000000..03278abf
--- /dev/null
+++ b/pkg/platform/images/images.go
@@ -0,0 +1,92 @@
+/*
+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 images
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/apache/camel-k/pkg/util/camel"
+	"github.com/apache/camel-k/version"
+)
+
+// BaseRepository is the docker repository that contains images
+const (
+	BaseRepository = "camelk"
+	ImagePrefix    = "camel-base-knative-"
+)
+
+// BaseDependency is a required dependency that must be found in the list
+var BaseDependency = "camel-k:knative"
+
+// StandardDependencies are common dependencies included in the image
+var StandardDependencies = map[string]bool{
+	"camel:core":   true,
+	"runtime:jvm":  true,
+	"runtime:yaml": true,
+}
+
+// LookupPredefinedImage is used to find a suitable predefined image if available
+func LookupPredefinedImage(dependencies []string) string {
+
+	realDependencies := make([]string, 0)
+	baseDependencyFound := false
+	for _, d := range dependencies {
+		if _, std := StandardDependencies[d]; std {
+			continue
+		}
+		if d == BaseDependency {
+			baseDependencyFound = true
+			continue
+		}
+		realDependencies = append(realDependencies, d)
+	}
+
+	if !baseDependencyFound {
+		return ""
+	}
+	if len(realDependencies) == 0 {
+		return PredefinedImageNameFor("core")
+	}
+	if len(realDependencies) != 1 {
+		return ""
+	}
+
+	otherDep := realDependencies[0]
+	camelPrefix := "camel:"
+	if !strings.HasPrefix(otherDep, camelPrefix) {
+		return ""
+	}
+	comp := strings.TrimPrefix(otherDep, camelPrefix)
+	if !isInCamelCatalog(comp) {
+		return ""
+	}
+	return PredefinedImageNameFor(comp)
+}
+
+// PredefinedImageNameFor --
+func PredefinedImageNameFor(comp string) string {
+	return fmt.Sprintf("%s/%s%s:%s", BaseRepository, ImagePrefix, comp, version.Version)
+}
+
+func isInCamelCatalog(comp string) bool {
+	if _, ok := camel.Runtime.Artifacts["camel-"+comp]; ok {
+		return true
+	}
+	return false
+}
diff --git a/pkg/platform/images/images_test.go b/pkg/platform/images/images_test.go
new file mode 100644
index 00000000..5a64b873
--- /dev/null
+++ b/pkg/platform/images/images_test.go
@@ -0,0 +1,80 @@
+/*
+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 images
+
+import (
+	"strconv"
+	"testing"
+
+	"github.com/apache/camel-k/version"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestImageLookup(t *testing.T) {
+	cases := []struct {
+		dependencies []string
+		image        string
+	}{
+		{
+			dependencies: []string{"camel:telegram"},
+		},
+		{
+			dependencies: []string{"camel:telegram", "camel:core"},
+		},
+		{
+			dependencies: []string{"camel:telegram", "camel:core", "camel-k:knative"},
+			image:        BaseRepository + "/" + ImagePrefix + "telegram:" + version.Version,
+		},
+		{
+			dependencies: []string{"camel:telegram", "camel-k:knative"},
+			image:        BaseRepository + "/" + ImagePrefix + "telegram:" + version.Version,
+		},
+		{
+			dependencies: []string{"camel:telegram", "camel:core", "camel-k:knative", "camel:dropbox"},
+		},
+		{
+			dependencies: []string{"camel:core", "camel-k:knative"},
+			image:        BaseRepository + "/" + ImagePrefix + "core:" + version.Version,
+		},
+		{
+			dependencies: []string{"camel:dropbox", "camel:core", "camel-k:knative", "runtime:jvm"},
+			image:        BaseRepository + "/" + ImagePrefix + "dropbox:" + version.Version,
+		},
+		{
+			dependencies: []string{"camel:dropbox", "camel:core", "camel-k:knative", "runtime:jvm", "runtime:yaml"},
+			image:        BaseRepository + "/" + ImagePrefix + "dropbox:" + version.Version,
+		},
+		{
+			dependencies: []string{"camel:dropbox", "camel:core", "runtime:jvm", "runtime:yaml"},
+		},
+		{
+			dependencies: []string{"camel:dropbox", "camel:core", "camel-k:knative", "runtime:jvm", "runtime:groovy"},
+		},
+		{
+			dependencies: []string{"camel:cippalippa", "camel:core", "camel-k:knative"},
+		},
+	}
+
+	for i, tc := range cases {
+		testcase := tc
+		t.Run("case-"+strconv.Itoa(i), func(t *testing.T) {
+			assert.Equal(t, testcase.image, LookupPredefinedImage(testcase.dependencies))
+		})
+	}
+
+}
diff --git a/pkg/platform/resources.go b/pkg/platform/resources.go
index 292776fb..a0b208bf 100644
--- a/pkg/platform/resources.go
+++ b/pkg/platform/resources.go
@@ -32,6 +32,9 @@ var KnativeContexts = []string{
 	"platform-integration-context-knative.yaml",
 }
 
+// NoContext is a placeholder for a not-present context
+const NoContext = "none"
+
 // GetContexts --
 func GetContexts() []string {
 	return append(DefaultContexts, KnativeContexts...)
diff --git a/pkg/trait/catalog.go b/pkg/trait/catalog.go
index 47decb06..0984975b 100644
--- a/pkg/trait/catalog.go
+++ b/pkg/trait/catalog.go
@@ -38,6 +38,7 @@ type Catalog struct {
 	tRoute        Trait
 	tIngress      Trait
 	tOwner        Trait
+	tImages       Trait
 	tBuilder      Trait
 	tSpringBoot   Trait
 	tIstio        Trait
@@ -56,6 +57,7 @@ func NewCatalog(ctx context.Context, c client.Client) *Catalog {
 		tRoute:        newRouteTrait(),
 		tIngress:      newIngressTrait(),
 		tOwner:        newOwnerTrait(),
+		tImages:       newImagesTrait(),
 		tBuilder:      newBuilderTrait(),
 		tSpringBoot:   newSpringBootTrait(),
 		tIstio:        newIstioTrait(),
@@ -84,6 +86,7 @@ func (c *Catalog) allTraits() []Trait {
 		c.tRoute,
 		c.tIngress,
 		c.tOwner,
+		c.tImages,
 		c.tBuilder,
 		c.tSpringBoot,
 		c.tIstio,
@@ -98,6 +101,7 @@ func (c *Catalog) traitsFor(environment *Environment) []Trait {
 		return []Trait{
 			c.tDebug,
 			c.tDependencies,
+			c.tImages,
 			c.tBuilder,
 			c.tEnvironment,
 			c.tClasspath,
@@ -111,6 +115,7 @@ func (c *Catalog) traitsFor(environment *Environment) []Trait {
 		return []Trait{
 			c.tDebug,
 			c.tDependencies,
+			c.tImages,
 			c.tBuilder,
 			c.tEnvironment,
 			c.tClasspath,
@@ -124,6 +129,7 @@ func (c *Catalog) traitsFor(environment *Environment) []Trait {
 		return []Trait{
 			c.tDebug,
 			c.tDependencies,
+			c.tImages,
 			c.tBuilder,
 			c.tEnvironment,
 			c.tClasspath,
diff --git a/pkg/trait/images.go b/pkg/trait/images.go
new file mode 100644
index 00000000..3ed4c0bc
--- /dev/null
+++ b/pkg/trait/images.go
@@ -0,0 +1,65 @@
+/*
+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 trait
+
+import (
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"github.com/apache/camel-k/pkg/platform/images"
+)
+
+type imagesTrait struct {
+	BaseTrait `property:",squash"`
+}
+
+func newImagesTrait() *imagesTrait {
+	return &imagesTrait{
+		BaseTrait: BaseTrait{
+			id: ID("images"),
+		},
+	}
+}
+
+func (t *imagesTrait) Configure(e *Environment) (bool, error) {
+	if t.Enabled == nil || !*t.Enabled {
+		// Disabled by default
+		return false, nil
+	}
+
+	if e.IntegrationContextInPhase("") {
+		return true, nil
+	}
+
+	return false, nil
+}
+
+func (t *imagesTrait) Apply(e *Environment) error {
+	// Try to lookup a image from predefined images
+	image := images.LookupPredefinedImage(e.Context.Spec.Dependencies)
+	if image == "" {
+		return nil
+	}
+
+	// Change the context type to external
+	if e.Context.Labels == nil {
+		e.Context.Labels = make(map[string]string)
+	}
+	e.Context.Labels["camel.apache.org/context.type"] = v1alpha1.IntegrationContextTypeExternal
+
+	e.Context.Spec.Image = image
+	return nil
+}
diff --git a/script/Makefile b/script/Makefile
index a3815f77..e53e3060 100644
--- a/script/Makefile
+++ b/script/Makefile
@@ -1,6 +1,6 @@
 build: build-runtime build-operator build-kamel build-compile-integration-tests test
 
-build-go: build-embed-resources build-operator build-kamel
+build-go: build-embed-resources build-operator build-kamel build-publisher
 
 build-operator: build-embed-resources
 	go build -o camel-k ./cmd/manager/*.go
@@ -8,6 +8,9 @@ build-operator: build-embed-resources
 build-kamel:
 	go build -o kamel ./cmd/kamel/*.go
 
+build-publisher:
+	go build -o publisher ./cmd/util/publisher/*.go
+
 build-embed-resources:
 	./script/embed_resources.sh deploy
 
@@ -17,7 +20,7 @@ build-compile-integration-tests:
 build-runtime:
 	./mvnw clean install -f ./runtime/pom.xml
 
-release: clean prepare-release build images-build images-push cross-compile package-examples git-tag
+release: clean prepare-release build images-build images-push cross-compile package-examples git-tag publish-base-images
 
 prepare-release:
 	./script/prepare_release.sh
@@ -36,6 +39,9 @@ package-examples:
 git-tag:
 	./script/git_tag.sh
 
+publish-base-images:
+	./script/publish_base_images.sh
+
 dep:
 	dep ensure -v
 
@@ -76,4 +82,4 @@ check-integration:
 lint:
 	golangci-lint run
 
-.PHONY: build build-operator build-kamel build-embed-resources build-runtime dep codegen images images-build images-push test check test-integration check-integration clean release prepare-release cross-compile package-examples new-version git-tag increment-snapshot install-minishift
+.PHONY: build build-operator build-kamel build-embed-resources build-runtime dep codegen images images-build images-push test check test-integration check-integration clean release prepare-release cross-compile package-examples new-version git-tag publish-base-images increment-snapshot install-minishift
diff --git a/script/publish_base_images.sh b/script/publish_base_images.sh
new file mode 100755
index 00000000..4915dee6
--- /dev/null
+++ b/script/publish_base_images.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+set -e
+
+location=$(dirname $0)
+rootdir=$(realpath $location/../)
+
+echo "Start publishing base images"
+
+$rootdir/publisher
+
+echo "All base images have been published"


With regards,
Apache Git Services