[camel-k] branch master updated: add an option to always generate a docker image #246

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[camel-k] branch master updated: add an option to always generate a docker image #246

nferraro
This is an automated email from the ASF dual-hosted git repository.

nferraro pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-k.git


The following commit(s) were added to refs/heads/master by this push:
     new 1e1565d  add an option to always generate a docker image #246
1e1565d is described below

commit 1e1565d776b0e93b5761fc6dfd9291a51ff047bc
Author: lburgazzoli <[hidden email]>
AuthorDate: Tue Dec 4 17:51:26 2018 +0100

    add an option to always generate a docker image #246
---
 Gopkg.lock                                         |   9 +
 pkg/apis/camel/v1alpha1/types.go                   |  10 +-
 pkg/builder/builder.go                             |   9 +
 pkg/builder/builder_steps.go                       | 106 +++++---
 pkg/builder/builder_types.go                       |  51 ++--
 pkg/builder/builder_utils.go                       |  17 +-
 pkg/builder/springboot/initializer.go              |   3 -
 pkg/stub/action/context/build.go                   |  12 +-
 .../integration/{build.go => build_context.go}     |  45 ++--
 pkg/stub/action/integration/build_image.go         | 144 +++++++++++
 pkg/stub/action/integration/initialize.go          |   7 +-
 pkg/stub/action/integration/monitor.go             |   6 +-
 pkg/stub/handler.go                                |   3 +-
 pkg/trait/builder.go                               |  38 ++-
 pkg/trait/catalog.go                               |   8 +-
 pkg/trait/deployment.go                            | 153 +++++++----
 pkg/trait/types.go                                 |   7 +-
 pkg/util/tar/appender.go                           |  14 +-
 test/build_manager_integration_test.go             |   7 -
 test/testing_env.go                                |  16 --
 vendor/github.com/scylladb/go-set/LICENSE          | 177 +++++++++++++
 vendor/github.com/scylladb/go-set/strset/strset.go | 279 +++++++++++++++++++++
 22 files changed, 958 insertions(+), 163 deletions(-)

diff --git a/Gopkg.lock b/Gopkg.lock
index 6e9a08d..a097acd 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -493,6 +493,14 @@
   version = "v1.2.1"
 
 [[projects]]
+  digest = "1:6f3ce746342be7b14a2d1ca33a4a11fd6cb0300e5d34c766f01e19e936fb10af"
+  name = "github.com/scylladb/go-set"
+  packages = ["strset"]
+  pruneopts = "NUT"
+  revision = "e560bb8f49bb7f34d4f59b7e771f6e1307c329da"
+  version = "v1.0.2"
+
+[[projects]]
   digest = "1:ecf78eacf406c42f07f66d6b79fda24d2b92dc711bfd0760d0c931678f9621fe"
   name = "github.com/sirupsen/logrus"
   packages = ["."]
@@ -935,6 +943,7 @@
     "github.com/pkg/errors",
     "github.com/radovskyb/watcher",
     "github.com/rs/xid",
+    "github.com/scylladb/go-set/strset",
     "github.com/sirupsen/logrus",
     "github.com/spf13/cobra",
     "github.com/stoewer/go-strcase",
diff --git a/pkg/apis/camel/v1alpha1/types.go b/pkg/apis/camel/v1alpha1/types.go
index b549e1d..f2b506f 100644
--- a/pkg/apis/camel/v1alpha1/types.go
+++ b/pkg/apis/camel/v1alpha1/types.go
@@ -130,8 +130,10 @@ const (
  // IntegrationKind --
  IntegrationKind string = "Integration"
 
- // IntegrationPhaseBuilding --
- IntegrationPhaseBuilding IntegrationPhase = "Building"
+ // IntegrationPhaseBuildingContext --
+ IntegrationPhaseBuildingContext IntegrationPhase = "Building Context"
+ // IntegrationPhaseBuildingImage --
+ IntegrationPhaseBuildingImage IntegrationPhase = "Building Image"
  // IntegrationPhaseDeploying --
  IntegrationPhaseDeploying IntegrationPhase = "Deploying"
  // IntegrationPhaseRunning --
@@ -290,3 +292,7 @@ type Artifact struct {
  Location string `json:"location,omitempty" yaml:"location,omitempty"`
  Target   string `json:"target,omitempty" yaml:"target,omitempty"`
 }
+
+func (in *Artifact) String() string {
+ return in.ID
+}
diff --git a/pkg/builder/builder.go b/pkg/builder/builder.go
index db82150..f4dad8b 100644
--- a/pkg/builder/builder.go
+++ b/pkg/builder/builder.go
@@ -151,6 +151,10 @@ func (b *defaultBuilder) submit(request Request) {
  Image:            "fabric8/s2i-java:2.3", // TODO: externalize
  }
 
+ if request.Image != "" {
+ c.Image = request.Image
+ }
+
  // Sort steps by phase
  sort.SliceStable(request.Steps, func(i, j int) bool {
  return request.Steps[i].Phase() < request.Steps[j].Phase()
@@ -201,4 +205,9 @@ func (b *defaultBuilder) submit(request Request) {
  b.request.Store(request.Meta.Name, r)
 
  b.log.Infof("request to build context %s executed in %f seconds", request.Meta.Name, r.Task.Elapsed().Seconds())
+ b.log.Infof("dependencies       : %s", request.Dependencies)
+ b.log.Infof("artifacts          : %s", ArtifactIDs(c.Artifacts))
+ b.log.Infof("artifacts selected : %s", ArtifactIDs(c.SelectedArtifacts))
+ b.log.Infof("requested image    : %s", request.Image)
+ b.log.Infof("resolved image     : %s", c.Image)
 }
diff --git a/pkg/builder/builder_steps.go b/pkg/builder/builder_steps.go
index cac7e75..4a24e02 100644
--- a/pkg/builder/builder_steps.go
+++ b/pkg/builder/builder_steps.go
@@ -25,6 +25,8 @@ import (
  "path"
  "strings"
 
+ "github.com/scylladb/go-set/strset"
+
  "github.com/rs/xid"
 
  "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
@@ -171,49 +173,58 @@ func ComputeDependencies(ctx *Context) error {
 }
 
 // ArtifactsSelector --
-type ArtifactsSelector func([]v1alpha1.Artifact) (string, []v1alpha1.Artifact, error)
+type ArtifactsSelector func(ctx *Context) error
 
 // StandardPackager --
 func StandardPackager(ctx *Context) error {
- return packager(ctx, func(libraries []v1alpha1.Artifact) (string, []v1alpha1.Artifact, error) {
- return ctx.Image, libraries, nil
+ return packager(ctx, func(ctx *Context) error {
+ ctx.SelectedArtifacts = ctx.Artifacts
+
+ return nil
  })
 }
 
 // IncrementalPackager --
 func IncrementalPackager(ctx *Context) error {
+ if ctx.HasRequiredImage() {
+ //
+ // If the build requires a specific image, don't try to determine the
+ // base image using artifact so just use the standard packages
+ //
+ return StandardPackager(ctx)
+ }
+
  images, err := ListPublishedImages(ctx.Namespace)
  if err != nil {
  return err
  }
 
- return packager(ctx, func(libraries []v1alpha1.Artifact) (string, []v1alpha1.Artifact, error) {
- bestImage, commonLibs := FindBestImage(images, libraries)
- if bestImage != nil {
- selectedClasspath := make([]v1alpha1.Artifact, 0)
- for _, entry := range libraries {
+ return packager(ctx, func(ctx *Context) error {
+ ctx.SelectedArtifacts = ctx.Artifacts
+
+ bestImage, commonLibs := FindBestImage(images, ctx.Request.Dependencies, ctx.Artifacts)
+ if bestImage.Image != "" {
+ selectedArtifacts := make([]v1alpha1.Artifact, 0)
+ for _, entry := range ctx.Artifacts {
  if _, isCommon := commonLibs[entry.ID]; !isCommon {
- selectedClasspath = append(selectedClasspath, entry)
+ selectedArtifacts = append(selectedArtifacts, entry)
  }
  }
 
- return bestImage.Image, selectedClasspath, nil
+ ctx.Image = bestImage.Image
+ ctx.SelectedArtifacts = selectedArtifacts
  }
 
- // return default selection
- return ctx.Image, libraries, nil
+ return nil
  })
 }
 
 // ClassPathPackager --
 func packager(ctx *Context, selector ArtifactsSelector) error {
- imageName, selectedArtifacts, err := selector(ctx.Artifacts)
+ err := selector(ctx)
  if err != nil {
  return err
  }
- if imageName == "" {
- imageName = ctx.Image
- }
 
  tarFileName := path.Join(ctx.Path, "package", "occi.tar")
  tarFileDir := path.Dir(tarFileName)
@@ -229,7 +240,7 @@ func packager(ctx *Context, selector ArtifactsSelector) error {
  }
  defer tarAppender.Close()
 
- for _, entry := range selectedArtifacts {
+ for _, entry := range ctx.SelectedArtifacts {
  _, tarFileName := path.Split(entry.Target)
  tarFilePath := path.Dir(entry.Target)
 
@@ -239,19 +250,23 @@ func packager(ctx *Context, selector ArtifactsSelector) error {
  }
  }
 
- if ctx.ComputeClasspath {
+ for _, entry := range ctx.Request.Resources {
+ if err := tarAppender.AddData(entry.Content, entry.Target); err != nil {
+ return err
+ }
+ }
+
+ if ctx.ComputeClasspath && len(ctx.Artifacts) > 0 {
  cp := ""
  for _, entry := range ctx.Artifacts {
- cp += path.Join(entry.Target) + "\n"
+ cp += entry.Target + "\n"
  }
 
- err = tarAppender.AppendData([]byte(cp), "classpath")
- if err != nil {
+ if err := tarAppender.AddData([]byte(cp), "classpath"); err != nil {
  return err
  }
  }
 
- ctx.Image = imageName
  ctx.Archive = tarFileName
 
  return nil
@@ -275,38 +290,67 @@ func ListPublishedImages(namespace string) ([]PublishedImage, error) {
  }
 
  images = append(images, PublishedImage{
- Image:     ctx.Status.Image,
- Artifacts: ctx.Status.Artifacts,
+ Image:        ctx.Status.Image,
+ Artifacts:    ctx.Status.Artifacts,
+ Dependencies: ctx.Spec.Dependencies,
  })
  }
  return images, nil
 }
 
 // FindBestImage --
-func FindBestImage(images []PublishedImage, entries []v1alpha1.Artifact) (*PublishedImage, map[string]bool) {
+func FindBestImage(images []PublishedImage, dependencies []string, artifacts []v1alpha1.Artifact) (PublishedImage, map[string]bool) {
+ var bestImage PublishedImage
+
  if len(images) == 0 {
- return nil, nil
+ return bestImage, nil
  }
- requiredLibs := make(map[string]bool, len(entries))
- for _, entry := range entries {
+
+ requiredLibs := make(map[string]bool, len(artifacts))
+ for _, entry := range artifacts {
  requiredLibs[entry.ID] = true
  }
 
- var bestImage PublishedImage
+ requiredRuntimes := strset.New()
+ for _, entry := range dependencies {
+ if strings.HasPrefix(entry, "runtime:") {
+ requiredRuntimes.Add(entry)
+ }
+ }
+
  bestImageCommonLibs := make(map[string]bool)
  bestImageSurplusLibs := 0
+
  for _, image := range images {
+ runtimes := strset.New()
+ for _, entry := range image.Dependencies {
+ if strings.HasPrefix(entry, "runtime:") {
+ runtimes.Add(entry)
+ }
+ }
+
+ //
+ // check if the image has the same runtime requirements to avoid the heuristic
+ // selector to include unwanted runtime bits such as spring-boot (which may have
+ // an additional artifact only thus it may match)
+ //
+ if !requiredRuntimes.IsSubset(runtimes) {
+ continue
+ }
+
  common := make(map[string]bool)
  for _, artifact := range image.Artifacts {
  if _, ok := requiredLibs[artifact.ID]; ok {
  common[artifact.ID] = true
  }
  }
+
  numCommonLibs := len(common)
  surplus := len(image.Artifacts) - numCommonLibs
 
  if numCommonLibs != len(image.Artifacts) && surplus >= numCommonLibs/3 {
- // Heuristic approach: if there are too many unrelated libraries, just use the base image
+ // Heuristic approach: if there are too many unrelated libraries, just use
+ // the base image
  continue
  }
 
@@ -317,7 +361,7 @@ func FindBestImage(images []PublishedImage, entries []v1alpha1.Artifact) (*Publi
  }
  }
 
- return &bestImage, bestImageCommonLibs
+ return bestImage, bestImageCommonLibs
 }
 
 // Notify --
diff --git a/pkg/builder/builder_types.go b/pkg/builder/builder_types.go
index 1f970f0..47c59e0 100644
--- a/pkg/builder/builder_types.go
+++ b/pkg/builder/builder_types.go
@@ -94,15 +94,22 @@ func NewStep(ID string, phase int32, task StepTask) Step {
  return &s
 }
 
+// Resource --
+type Resource struct {
+ Target  string
+ Content []byte
+}
+
 // Request --
 type Request struct {
  Meta         v1.ObjectMeta
  Platform     v1alpha1.IntegrationPlatformSpec
- Code         v1alpha1.SourceSpec
  Dependencies []string
  Repositories []string
  Steps        []Step
  BuildDir     string
+ Image        string
+ Resources    []Resource
 }
 
 // Task --
@@ -128,23 +135,39 @@ type Result struct {
 
 // Context --
 type Context struct {
- C                context.Context
- Request          Request
- Image            string
- Error            error
- Namespace        string
- Project          maven.Project
- Path             string
- Artifacts        []v1alpha1.Artifact
- Archive          string
- ComputeClasspath bool
- MainClass        string
+ C                 context.Context
+ Request           Request
+ Image             string
+ Error             error
+ Namespace         string
+ Project           maven.Project
+ Path              string
+ Artifacts         []v1alpha1.Artifact
+ SelectedArtifacts []v1alpha1.Artifact
+ Archive           string
+ ComputeClasspath  bool
+ MainClass         string
+}
+
+// HasRequiredImage --
+func (c *Context) HasRequiredImage() bool {
+ return c.Request.Image != ""
+}
+
+// GetImage --
+func (c *Context) GetImage() string {
+ if c.Request.Image != "" {
+ return c.Request.Image
+ }
+
+ return c.Image
 }
 
 // PublishedImage --
 type PublishedImage struct {
- Image     string
- Artifacts []v1alpha1.Artifact
+ Image        string
+ Artifacts    []v1alpha1.Artifact
+ Dependencies []string
 }
 
 // Status --
diff --git a/pkg/builder/builder_utils.go b/pkg/builder/builder_utils.go
index 1a9fb62..8ba81c5 100644
--- a/pkg/builder/builder_utils.go
+++ b/pkg/builder/builder_utils.go
@@ -17,7 +17,11 @@ limitations under the License.
 
 package builder
 
-import "os"
+import (
+ "os"
+
+ "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+)
 
 // MavenExtraOptions --
 func MavenExtraOptions() string {
@@ -26,3 +30,14 @@ func MavenExtraOptions() string {
  }
  return "-Dcamel.noop=true"
 }
+
+// ArtifactIDs --
+func ArtifactIDs(artifacts []v1alpha1.Artifact) []string {
+ result := make([]string, 0, len(artifacts))
+
+ for _, a := range artifacts {
+ result = append(result, a.ID)
+ }
+
+ return result
+}
diff --git a/pkg/builder/springboot/initializer.go b/pkg/builder/springboot/initializer.go
index de40ea6..2b8b5af 100644
--- a/pkg/builder/springboot/initializer.go
+++ b/pkg/builder/springboot/initializer.go
@@ -23,9 +23,6 @@ import (
 
 // Initialize --
 func Initialize(ctx *builder.Context) error {
- // set the base image
- //ctx.Image = "kamel-k/s2i-boot:" + version.Version
-
  // no need to compute classpath as we do use spring boot own
  // loader: PropertiesLauncher
  ctx.ComputeClasspath = false
diff --git a/pkg/stub/action/context/build.go b/pkg/stub/action/context/build.go
index 9625545..b3e9226 100644
--- a/pkg/stub/action/context/build.go
+++ b/pkg/stub/action/context/build.go
@@ -79,10 +79,10 @@ func (action *buildAction) Handle(context *v1alpha1.IntegrationContext) error {
  target := context.DeepCopy()
  target.Status.Phase = v1alpha1.IntegrationContextPhaseError
 
- logrus.Info("Context ", target.Name, " transitioning to state ", v1alpha1.IntegrationContextPhaseError)
+ logrus.Info("Context ", target.Name, " transitioning to state ", target.Status.Phase)
 
  // remove the build from cache
- b.Purge(r)
+ defer b.Purge(r)
 
  return sdk.Update(target)
  case builder.StatusCompleted:
@@ -100,7 +100,10 @@ func (action *buildAction) Handle(context *v1alpha1.IntegrationContext) error {
  })
  }
 
- logrus.Info("Context ", target.Name, " transitioning to state ", v1alpha1.IntegrationContextPhaseReady)
+ logrus.Info("Context ", target.Name, " transitioning to state ", target.Status.Phase)
+
+ // remove the build from cache
+ defer b.Purge(r)
 
  if err := sdk.Update(target); err != nil {
  return err
@@ -108,9 +111,6 @@ func (action *buildAction) Handle(context *v1alpha1.IntegrationContext) error {
  if err := action.informIntegrations(target); err != nil {
  return err
  }
-
- // remove the build from cache
- b.Purge(r)
  }
 
  return nil
diff --git a/pkg/stub/action/integration/build.go b/pkg/stub/action/integration/build_context.go
similarity index 84%
rename from pkg/stub/action/integration/build.go
rename to pkg/stub/action/integration/build_context.go
index 85eb881..679945e 100644
--- a/pkg/stub/action/integration/build.go
+++ b/pkg/stub/action/integration/build_context.go
@@ -20,6 +20,8 @@ package integration
 import (
  "fmt"
 
+ "github.com/apache/camel-k/pkg/trait"
+
  "github.com/sirupsen/logrus"
 
  "github.com/apache/camel-k/pkg/util"
@@ -31,26 +33,26 @@ import (
  "github.com/operator-framework/operator-sdk/pkg/sdk"
 )
 
-// NewBuildAction create an action that handles integration build
-func NewBuildAction(namespace string) Action {
- return &buildAction{
+// NewBuildContextAction create an action that handles integration context build
+func NewBuildContextAction(namespace string) Action {
+ return &buildContextAction{
  namespace: namespace,
  }
 }
 
-type buildAction struct {
+type buildContextAction struct {
  namespace string
 }
 
-func (action *buildAction) Name() string {
- return "build"
+func (action *buildContextAction) Name() string {
+ return "build-context"
 }
 
-func (action *buildAction) CanHandle(integration *v1alpha1.Integration) bool {
- return integration.Status.Phase == v1alpha1.IntegrationPhaseBuilding
+func (action *buildContextAction) CanHandle(integration *v1alpha1.Integration) bool {
+ return integration.Status.Phase == v1alpha1.IntegrationPhaseBuildingContext
 }
 
-func (action *buildAction) Handle(integration *v1alpha1.Integration) error {
+func (action *buildContextAction) Handle(integration *v1alpha1.Integration) error {
  ctx, err := LookupContextForIntegration(integration)
  if err != nil {
  //TODO: we may need to add a wait strategy, i.e give up after some time
@@ -74,30 +76,40 @@ func (action *buildAction) Handle(integration *v1alpha1.Integration) error {
  }
  }
 
- if ctx.Status.Phase == v1alpha1.IntegrationContextPhaseReady {
+ if ctx.Status.Phase == v1alpha1.IntegrationContextPhaseError {
  target := integration.DeepCopy()
  target.Status.Image = ctx.Status.Image
  target.Spec.Context = ctx.Name
- logrus.Info("Integration ", target.Name, " transitioning to state ", v1alpha1.IntegrationPhaseDeploying)
- target.Status.Phase = v1alpha1.IntegrationPhaseDeploying
- dgst, err := digest.ComputeForIntegration(target)
+ target.Status.Phase = v1alpha1.IntegrationPhaseError
+
+ target.Status.Digest, err = digest.ComputeForIntegration(target)
  if err != nil {
  return err
  }
- target.Status.Digest = dgst
+
+ logrus.Info("Integration ", target.Name, " transitioning to state ", target.Status.Phase)
+
  return sdk.Update(target)
  }
 
- if ctx.Status.Phase == v1alpha1.IntegrationContextPhaseError {
+ if ctx.Status.Phase == v1alpha1.IntegrationContextPhaseReady {
  target := integration.DeepCopy()
  target.Status.Image = ctx.Status.Image
  target.Spec.Context = ctx.Name
- target.Status.Phase = v1alpha1.IntegrationPhaseError
+
  dgst, err := digest.ComputeForIntegration(target)
  if err != nil {
  return err
  }
+
  target.Status.Digest = dgst
+
+ if _, err := trait.Apply(target, ctx); err != nil {
+ return err
+ }
+
+ logrus.Info("Integration ", target.Name, " transitioning to state ", target.Status.Phase)
+
  return sdk.Update(target)
  }
 
@@ -138,5 +150,6 @@ func (action *buildAction) Handle(integration *v1alpha1.Integration) error {
  // same path as integration with a user defined context
  target := integration.DeepCopy()
  target.Spec.Context = platformCtxName
+
  return sdk.Update(target)
 }
diff --git a/pkg/stub/action/integration/build_image.go b/pkg/stub/action/integration/build_image.go
new file mode 100644
index 0000000..a30e4bf
--- /dev/null
+++ b/pkg/stub/action/integration/build_image.go
@@ -0,0 +1,144 @@
+/*
+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 integration
+
+import (
+ "context"
+ "fmt"
+ "path"
+
+ "github.com/pkg/errors"
+
+ "github.com/apache/camel-k/pkg/util/digest"
+
+ "github.com/apache/camel-k/pkg/trait"
+
+ "github.com/apache/camel-k/pkg/builder"
+ "github.com/operator-framework/operator-sdk/pkg/sdk"
+ "github.com/sirupsen/logrus"
+
+ "github.com/apache/camel-k/pkg/platform"
+
+ "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+)
+
+// NewBuildImageAction create an action that handles integration image build
+func NewBuildImageAction(ctx context.Context, namespace string) Action {
+ return &buildImageAction{
+ Context:   ctx,
+ namespace: namespace,
+ }
+}
+
+type buildImageAction struct {
+ context.Context
+ namespace string
+}
+
+func (action *buildImageAction) Name() string {
+ return "build-image"
+}
+
+func (action *buildImageAction) CanHandle(integration *v1alpha1.Integration) bool {
+ return integration.Status.Phase == v1alpha1.IntegrationPhaseBuildingImage
+}
+
+func (action *buildImageAction) Handle(integration *v1alpha1.Integration) error {
+
+ // in this phase the integration need to be associated to a context whose image
+ // will be used as base image for the integration images
+ if integration.Spec.Context == "" {
+ return fmt.Errorf("context is not set for integration: %s", integration.Name)
+ }
+
+ // look-up the integration context associated to this integration, this is needed
+ // to determine the base image
+ ctx := v1alpha1.NewIntegrationContext(integration.Namespace, integration.Spec.Context)
+ if err := sdk.Get(&ctx); err != nil {
+ return errors.Wrapf(err, "unable to find integration context %s, %s", ctx.Name, err)
+ }
+
+ b, err := platform.GetPlatformBuilder(action.Context, action.namespace)
+ if err != nil {
+ return err
+ }
+ env, err := trait.Apply(integration, &ctx)
+ if err != nil {
+ return err
+ }
+
+ // This build do not require to determine dependencies nor a project, the builder
+ // step do remove them
+ r := builder.Request{
+ Meta:     integration.ObjectMeta,
+ Steps:    env.Steps,
+ Platform: env.Platform.Spec,
+ Image:    ctx.Status.Image,
+ }
+
+ // Sources are added as part of the standard deployment bits
+ r.Resources = make([]builder.Resource, 0, len(integration.Spec.Sources))
+
+ for _, source := range integration.Spec.Sources {
+ r.Resources = append(r.Resources, builder.Resource{
+ Content: []byte(source.Content),
+ Target:  path.Join("sources", source.Name),
+ })
+ }
+
+ res := b.Submit(r)
+
+ switch res.Status {
+ case builder.StatusSubmitted:
+ logrus.Info("Build submitted")
+ case builder.StatusStarted:
+ logrus.Info("Build started")
+ case builder.StatusError:
+ target := integration.DeepCopy()
+ target.Status.Phase = v1alpha1.IntegrationPhaseError
+
+ logrus.Info("Integration ", target.Name, " transitioning to state ", target.Status.Phase)
+
+ // remove the build from cache
+ defer b.Purge(r)
+
+ return sdk.Update(target)
+ case builder.StatusCompleted:
+ target := integration.DeepCopy()
+ target.Status.Phase = v1alpha1.IntegrationPhaseDeploying
+ target.Status.Image = res.Image
+
+ dgst, err := digest.ComputeForIntegration(integration)
+ if err != nil {
+ return err
+ }
+
+ target.Status.Digest = dgst
+
+ logrus.Info("Integration ", target.Name, " transitioning to state ", target.Status.Phase)
+
+ // remove the build from cache
+ defer b.Purge(r)
+
+ if err := sdk.Update(target); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/stub/action/integration/initialize.go b/pkg/stub/action/integration/initialize.go
index 8a21301..eea1957 100644
--- a/pkg/stub/action/integration/initialize.go
+++ b/pkg/stub/action/integration/initialize.go
@@ -73,12 +73,15 @@ func (action *initializeAction) Handle(integration *v1alpha1.Integration) error
  }
 
  // update the status
- logrus.Info("Integration ", target.Name, " transitioning to state ", v1alpha1.IntegrationPhaseBuilding)
- target.Status.Phase = v1alpha1.IntegrationPhaseBuilding
  dgst, err := digest.ComputeForIntegration(integration)
  if err != nil {
  return err
  }
+
+ target.Status.Phase = v1alpha1.IntegrationPhaseBuildingContext
  target.Status.Digest = dgst
+
+ logrus.Info("Integration ", target.Name, " transitioning to state ", target.Status.Phase)
+
  return sdk.Update(target)
 }
diff --git a/pkg/stub/action/integration/monitor.go b/pkg/stub/action/integration/monitor.go
index 756695b..d0e8107 100644
--- a/pkg/stub/action/integration/monitor.go
+++ b/pkg/stub/action/integration/monitor.go
@@ -53,8 +53,10 @@ func (action *monitorAction) Handle(integration *v1alpha1.Integration) error {
 
  target := integration.DeepCopy()
  target.Status.Digest = hash
- logrus.Info("Integration ", target.Name, " transitioning to state ", v1alpha1.IntegrationPhaseBuilding)
- target.Status.Phase = v1alpha1.IntegrationPhaseBuilding
+ target.Status.Phase = v1alpha1.IntegrationPhaseBuildingContext
+
+ logrus.Info("Integration ", target.Name, " transitioning to state ", target.Status.Phase)
+
  return sdk.Update(target)
  }
 
diff --git a/pkg/stub/handler.go b/pkg/stub/handler.go
index a2f98ca..f4d3eaa 100644
--- a/pkg/stub/handler.go
+++ b/pkg/stub/handler.go
@@ -34,7 +34,8 @@ func NewHandler(ctx ctx.Context, namespace string) sdk.Handler {
  return &handler{
  integrationActionPool: []integration.Action{
  integration.NewInitializeAction(),
- integration.NewBuildAction(namespace),
+ integration.NewBuildContextAction(namespace),
+ integration.NewBuildImageAction(ctx, namespace),
  integration.NewDeployAction(),
  integration.NewMonitorAction(),
  },
diff --git a/pkg/trait/builder.go b/pkg/trait/builder.go
index c8c4a50..84702cb 100644
--- a/pkg/trait/builder.go
+++ b/pkg/trait/builder.go
@@ -19,6 +19,7 @@ package trait
 
 import (
  "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+ "github.com/apache/camel-k/pkg/builder"
  "github.com/apache/camel-k/pkg/builder/kaniko"
  "github.com/apache/camel-k/pkg/builder/s2i"
  "github.com/apache/camel-k/pkg/platform"
@@ -36,14 +37,41 @@ func newBuilderTrait() *builderTrait {
 }
 
 func (*builderTrait) appliesTo(e *Environment) bool {
- return e.Context != nil && e.Context.Status.Phase == v1alpha1.IntegrationContextPhaseBuilding
+ if e.Context != nil && e.Context.Status.Phase == v1alpha1.IntegrationContextPhaseBuilding {
+ return true
+ }
+
+ if e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseBuildingImage &&
+ e.Context != nil && e.Context.Status.Phase == v1alpha1.IntegrationContextPhaseReady {
+ return true
+ }
+
+ return false
 }
 
 func (*builderTrait) apply(e *Environment) error {
- if platform.SupportsS2iPublishStrategy(e.Platform) {
- e.Steps = s2i.DefaultSteps
- } else if platform.SupportsKanikoPublishStrategy(e.Platform) {
- e.Steps = kaniko.DefaultSteps
+ if e.Context != nil && e.Context.Status.Phase == v1alpha1.IntegrationContextPhaseBuilding {
+ if platform.SupportsS2iPublishStrategy(e.Platform) {
+ e.Steps = s2i.DefaultSteps
+ } else if platform.SupportsKanikoPublishStrategy(e.Platform) {
+ e.Steps = kaniko.DefaultSteps
+ }
+ }
+
+ if e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseBuildingImage &&
+ e.Context != nil && e.Context.Status.Phase == v1alpha1.IntegrationContextPhaseReady {
+
+ if platform.SupportsS2iPublishStrategy(e.Platform) {
+ e.Steps = []builder.Step{
+ builder.NewStep("packager", builder.ApplicationPackagePhase, builder.StandardPackager),
+ builder.NewStep("publisher/s2i", builder.ApplicationPublishPhase, s2i.Publisher),
+ }
+ } else if platform.SupportsKanikoPublishStrategy(e.Platform) {
+ e.Steps = []builder.Step{
+ builder.NewStep("packager", builder.ApplicationPackagePhase, builder.StandardPackager),
+ builder.NewStep("publisher/kaniko", builder.ApplicationPublishPhase, kaniko.Publisher),
+ }
+ }
  }
 
  return nil
diff --git a/pkg/trait/catalog.go b/pkg/trait/catalog.go
index 62c2f9f..d0180be 100644
--- a/pkg/trait/catalog.go
+++ b/pkg/trait/catalog.go
@@ -73,9 +73,7 @@ func (c *Catalog) allTraits() []Trait {
 }
 
 func (c *Catalog) traitsFor(environment *Environment) []Trait {
- profile := environment.DetermineProfile()
-
- switch profile {
+ switch environment.DetermineProfile() {
  case v1alpha1.TraitProfileOpenShift:
  return []Trait{
  c.tDebug,
@@ -105,6 +103,7 @@ func (c *Catalog) traitsFor(environment *Environment) []Trait {
  c.tKnative,
  c.tBuilder,
  c.tSpringBoot,
+ c.tDeployment,
  c.tOwner,
  }
  }
@@ -128,14 +127,17 @@ func (c *Catalog) apply(environment *Environment) error {
  return err
  }
  }
+
  if trait.IsEnabled() {
  logrus.Infof("apply trait: %s", trait.ID())
  if err := trait.apply(environment); err != nil {
  return err
  }
+
  environment.ExecutedTraits = append(environment.ExecutedTraits, trait.ID())
  }
  }
+
  return nil
 }
 
diff --git a/pkg/trait/deployment.go b/pkg/trait/deployment.go
index 7304aa2..b3a3be6 100644
--- a/pkg/trait/deployment.go
+++ b/pkg/trait/deployment.go
@@ -19,6 +19,7 @@ package trait
 
 import (
  "fmt"
+ "path"
  "strings"
 
  "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
@@ -29,7 +30,8 @@ import (
 )
 
 type deploymentTrait struct {
- BaseTrait `property:",squash"`
+ BaseTrait      `property:",squash"`
+ ContainerImage bool `property:"container-image"`
 }
 
 func newDeploymentTrait() *deploymentTrait {
@@ -39,12 +41,40 @@ func newDeploymentTrait() *deploymentTrait {
 }
 
 func (d *deploymentTrait) appliesTo(e *Environment) bool {
- return e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying
+ if e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying {
+ //
+ // Don't deploy on knative
+ //
+ return e.DetermineProfile() != v1alpha1.TraitProfileKnative
+ }
+
+ if d.ContainerImage && e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingContext) {
+ return true
+ }
+
+ if !d.ContainerImage && e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingContext) {
+ return true
+ }
+
+ return false
 }
 
 func (d *deploymentTrait) apply(e *Environment) error {
- e.Resources.AddAll(getConfigMapsFor(e))
- e.Resources.Add(getDeploymentFor(e))
+ if d.ContainerImage && e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingContext) {
+ // trigger container image build
+ e.Integration.Status.Phase = v1alpha1.IntegrationPhaseBuildingImage
+ }
+
+ if !d.ContainerImage && e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingContext) {
+ // trigger integration deploy
+ e.Integration.Status.Phase = v1alpha1.IntegrationPhaseDeploying
+ }
+
+ if e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying {
+ e.Resources.AddAll(d.getConfigMapsFor(e))
+ e.Resources.Add(d.getDeploymentFor(e))
+ }
+
  return nil
 }
 
@@ -54,7 +84,7 @@ func (d *deploymentTrait) apply(e *Environment) error {
 //
 // **********************************
 
-func getConfigMapsFor(e *Environment) []runtime.Object {
+func (d *deploymentTrait) getConfigMapsFor(e *Environment) []runtime.Object {
  maps := make([]runtime.Object, 0, len(e.Integration.Spec.Sources)+1)
 
  // combine properties of integration with context, integration
@@ -81,30 +111,35 @@ func getConfigMapsFor(e *Environment) []runtime.Object {
  },
  )
 
- for i, s := range e.Integration.Spec.Sources {
- maps = append(
- maps,
- &corev1.ConfigMap{
- TypeMeta: metav1.TypeMeta{
- Kind:       "ConfigMap",
- APIVersion: "v1",
- },
- ObjectMeta: metav1.ObjectMeta{
- Name:      fmt.Sprintf("%s-source-%03d", e.Integration.Name, i),
- Namespace: e.Integration.Namespace,
- Labels: map[string]string{
- "camel.apache.org/integration": e.Integration.Name,
+ if !d.ContainerImage {
+
+ // do not create 'source' ConfigMap if a docker images for deployment
+ // is required
+ for i, s := range e.Integration.Spec.Sources {
+ maps = append(
+ maps,
+ &corev1.ConfigMap{
+ TypeMeta: metav1.TypeMeta{
+ Kind:       "ConfigMap",
+ APIVersion: "v1",
  },
- Annotations: map[string]string{
- "camel.apache.org/source.language": string(s.Language),
- "camel.apache.org/source.name":     s.Name,
+ ObjectMeta: metav1.ObjectMeta{
+ Name:      fmt.Sprintf("%s-source-%03d", e.Integration.Name, i),
+ Namespace: e.Integration.Namespace,
+ Labels: map[string]string{
+ "camel.apache.org/integration": e.Integration.Name,
+ },
+ Annotations: map[string]string{
+ "camel.apache.org/source.language": string(s.Language),
+ "camel.apache.org/source.name":     s.Name,
+ },
+ },
+ Data: map[string]string{
+ "integration": s.Content,
  },
  },
- Data: map[string]string{
- "integration": s.Content,
- },
- },
- )
+ )
+ }
  }
 
  return maps
@@ -116,17 +151,34 @@ func getConfigMapsFor(e *Environment) []runtime.Object {
 //
 // **********************************
 
-func getDeploymentFor(e *Environment) *appsv1.Deployment {
+func (d *deploymentTrait) getSources(e *Environment) []string {
  sources := make([]string, 0, len(e.Integration.Spec.Sources))
+
  for i, s := range e.Integration.Spec.Sources {
- src := fmt.Sprintf("file:/etc/camel/integrations/%03d/%s", i, strings.TrimPrefix(s.Name, "/"))
+ root := fmt.Sprintf("/etc/camel/integrations/%03d", i)
+
+ if d.ContainerImage {
+
+ // assume sources are copied over the standard deployments folder
+ root = "/deployments/sources"
+ }
+
+ src := path.Join(root, s.Name)
+ src = "file:" + src
+
  if s.Language != "" {
- src = src + "?language=" + string(s.Language)
+ src = fmt.Sprintf("%s?language=%s", src, string(s.Language))
  }
 
  sources = append(sources, src)
  }
 
+ return sources
+}
+
+func (d *deploymentTrait) getDeploymentFor(e *Environment) *appsv1.Deployment {
+ sources := d.getSources(e)
+
  // combine Environment of integration with context, integration
  // Environment has the priority
  environment := CombineConfigurationAsMap("env", e.Context, e.Integration)
@@ -227,28 +279,35 @@ func getDeploymentFor(e *Environment) *appsv1.Deployment {
  // Volumes :: Sources
  //
 
- for i, s := range e.Integration.Spec.Sources {
- vols = append(vols, corev1.Volume{
- Name: fmt.Sprintf("integration-source-%03d", i),
- VolumeSource: corev1.VolumeSource{
- ConfigMap: &corev1.ConfigMapVolumeSource{
- LocalObjectReference: corev1.LocalObjectReference{
- Name: fmt.Sprintf("%s-source-%03d", e.Integration.Name, i),
- },
- Items: []corev1.KeyToPath{
- {
- Key:  "integration",
- Path: strings.TrimPrefix(s.Name, "/"),
+ if !d.ContainerImage {
+
+ // We can configure the operator to generate a container images that include
+ // integration sources instead of mounting it at runtime and in such case we
+ // do not need to mount any 'source' ConfigMap to the pod
+
+ for i, s := range e.Integration.Spec.Sources {
+ vols = append(vols, corev1.Volume{
+ Name: fmt.Sprintf("integration-source-%03d", i),
+ VolumeSource: corev1.VolumeSource{
+ ConfigMap: &corev1.ConfigMapVolumeSource{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: fmt.Sprintf("%s-source-%03d", e.Integration.Name, i),
+ },
+ Items: []corev1.KeyToPath{
+ {
+ Key:  "integration",
+ Path: strings.TrimPrefix(s.Name, "/"),
+ },
  },
  },
  },
- },
- })
+ })
 
- mnts = append(mnts, corev1.VolumeMount{
- Name:      fmt.Sprintf("integration-source-%03d", i),
- MountPath: fmt.Sprintf("/etc/camel/integrations/%03d", i),
- })
+ mnts = append(mnts, corev1.VolumeMount{
+ Name:      fmt.Sprintf("integration-source-%03d", i),
+ MountPath: fmt.Sprintf("/etc/camel/integrations/%03d", i),
+ })
+ }
  }
 
  //
diff --git a/pkg/trait/types.go b/pkg/trait/types.go
index eaaba02..53fb69c 100644
--- a/pkg/trait/types.go
+++ b/pkg/trait/types.go
@@ -110,7 +110,12 @@ func (e *Environment) IntegrationContextInPhase(phase v1alpha1.IntegrationContex
  return e.Context != nil && e.Context.Status.Phase == phase
 }
 
-// DeterimeProfile determines the TraitProfile of the environment.
+// InPhase --
+func (e *Environment) InPhase(c v1alpha1.IntegrationContextPhase, i v1alpha1.IntegrationPhase) bool {
+ return e.IntegrationContextInPhase(c) && e.IntegrationInPhase(i)
+}
+
+// DetermineProfile determines the TraitProfile of the environment.
 // First looking at the Integration.Spec for a Profile,
 // next looking at the Context.Spec
 // and lastly the Platform Profile
diff --git a/pkg/util/tar/appender.go b/pkg/util/tar/appender.go
index 78df669..47bb8a4 100644
--- a/pkg/util/tar/appender.go
+++ b/pkg/util/tar/appender.go
@@ -128,19 +128,21 @@ func (t *Appender) AddFileWithName(fileName string, filePath string, tarDir stri
  return fileName, nil
 }
 
-// AppendData appends the given content to a file inside the tar, creating it if it does not exist
-func (t *Appender) AppendData(data []byte, tarPath string) error {
- if err := t.writer.WriteHeader(&atar.Header{
+// AddData appends the given content to a file inside the tar, creating it if it does not exist
+func (t *Appender) AddData(data []byte, tarPath string) error {
+ err := t.writer.WriteHeader(&atar.Header{
  Name: tarPath,
  Size: int64(len(data)),
  Mode: 0644,
- }); err != nil {
+ })
+
+ if err != nil {
  return err
  }
 
- _, err := t.writer.Write(data)
- if err != nil {
+ if _, err := t.writer.Write(data); err != nil {
  return errors.Wrap(err, "cannot add data to the tar archive")
  }
+
  return nil
 }
diff --git a/test/build_manager_integration_test.go b/test/build_manager_integration_test.go
index 1e8a10c..4bbcd19 100644
--- a/test/build_manager_integration_test.go
+++ b/test/build_manager_integration_test.go
@@ -28,7 +28,6 @@ import (
 
  "k8s.io/apimachinery/pkg/apis/meta/v1"
 
- "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
  "github.com/apache/camel-k/pkg/builder"
  "github.com/apache/camel-k/pkg/builder/s2i"
  "github.com/stretchr/testify/assert"
@@ -44,9 +43,6 @@ func TestBuildManagerBuild(t *testing.T) {
  Name:            "man-test",
  ResourceVersion: "1",
  },
- Code: v1alpha1.SourceSpec{
- Content: createTimerToLogIntegrationCode(),
- },
  Dependencies: []string{
  "mvn:org.apache.camel/camel-core",
  "camel:telegram",
@@ -83,9 +79,6 @@ func TestBuildManagerFailedBuild(t *testing.T) {
  Name:            "man-test",
  ResourceVersion: "1",
  },
- Code: v1alpha1.SourceSpec{
- Content: createTimerToLogIntegrationCode(),
- },
  Dependencies: []string{
  "mvn:org.apache.camel/camel-cippalippa",
  },
diff --git a/test/testing_env.go b/test/testing_env.go
index 66ff683..d8265c4 100644
--- a/test/testing_env.go
+++ b/test/testing_env.go
@@ -56,22 +56,6 @@ func getTargetNamespace() string {
  return ns
 }
 
-func createTimerToLogIntegrationCode() string {
- return `
-import org.apache.camel.builder.RouteBuilder;
-
-public class Routes extends RouteBuilder {
-
- @Override
-    public void configure() throws Exception {
-        from("timer:tick")
-  .to("log:info");
-    }
-
-}
-`
-}
-
 func createDummyDeployment(name string, replicas *int32, labelKey string, labelValue string, command ...string) (*appsv1.Deployment, error) {
  deployment := getDummyDeployment(name, replicas, labelKey, labelValue, command...)
  gracePeriod := int64(0)
diff --git a/vendor/github.com/scylladb/go-set/LICENSE b/vendor/github.com/scylladb/go-set/LICENSE
new file mode 100644
index 0000000..4947287
--- /dev/null
+++ b/vendor/github.com/scylladb/go-set/LICENSE
@@ -0,0 +1,177 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
\ No newline at end of file
diff --git a/vendor/github.com/scylladb/go-set/strset/strset.go b/vendor/github.com/scylladb/go-set/strset/strset.go
new file mode 100644
index 0000000..ed828e5
--- /dev/null
+++ b/vendor/github.com/scylladb/go-set/strset/strset.go
@@ -0,0 +1,279 @@
+// Copyright (C) 2017 ScyllaDB
+// Use of this source code is governed by a ALv2-style
+// license that can be found at https://github.com/scylladb/go-set/LICENSE.
+
+package strset
+
+import (
+ "fmt"
+ "math"
+ "strings"
+)
+
+var (
+ // helpful to not write everywhere struct{}{}
+ keyExists   = struct{}{}
+ nonExistent string
+)
+
+// Set is the main set structure that holds all the data
+// and methods used to working with the set.
+type Set struct {
+ m map[string]struct{}
+}
+
+// New creates and initializes a new Set.
+func New(ts ...string) *Set {
+ s := NewWithSize(len(ts))
+ s.Add(ts...)
+ return s
+}
+
+// NewWithSize creates a new Set and gives make map a size hint.
+func NewWithSize(size int) *Set {
+ return &Set{make(map[string]struct{}, size)}
+}
+
+// Add includes the specified items (one or more) to the Set. The underlying
+// Set s is modified. If passed nothing it silently returns.
+func (s *Set) Add(items ...string) {
+ for _, item := range items {
+ s.m[item] = keyExists
+ }
+}
+
+// Remove deletes the specified items from the Set. The underlying Set s is
+// modified. If passed nothing it silently returns.
+func (s *Set) Remove(items ...string) {
+ for _, item := range items {
+ delete(s.m, item)
+ }
+}
+
+// Pop deletes and returns an item from the Set. The underlying Set s is
+// modified. If Set is empty, the zero value is returned.
+func (s *Set) Pop() string {
+ for item := range s.m {
+ delete(s.m, item)
+ return item
+ }
+ return nonExistent
+}
+
+// Pop2 tries to delete and return an item from the Set. The underlying Set s
+// is modified. The second value is a bool that is true if the item existed in
+// the set, and false if not. If Set is empty, the zero value and false are
+// returned.
+func (s *Set) Pop2() (string, bool) {
+ for item := range s.m {
+ delete(s.m, item)
+ return item, true
+ }
+ return nonExistent, false
+}
+
+// Has looks for the existence of items passed. It returns false if nothing is
+// passed. For multiple items it returns true only if all of  the items exist.
+func (s *Set) Has(items ...string) bool {
+ has := false
+ for _, item := range items {
+ if _, has = s.m[item]; !has {
+ break
+ }
+ }
+ return has
+}
+
+// HasAny looks for the existence of any of the items passed.
+// It returns false if nothing is passed.
+// For multiple items it returns true if any of the items exist.
+func (s *Set) HasAny(items ...string) bool {
+ has := false
+ for _, item := range items {
+ if _, has = s.m[item]; has {
+ break
+ }
+ }
+ return has
+}
+
+// Size returns the number of items in a Set.
+func (s *Set) Size() int {
+ return len(s.m)
+}
+
+// Clear removes all items from the Set.
+func (s *Set) Clear() {
+ s.m = make(map[string]struct{})
+}
+
+// IsEmpty reports whether the Set is empty.
+func (s *Set) IsEmpty() bool {
+ return s.Size() == 0
+}
+
+// IsEqual test whether s and t are the same in size and have the same items.
+func (s *Set) IsEqual(t *Set) bool {
+ // return false if they are no the same size
+ if s.Size() != t.Size() {
+ return false
+ }
+
+ equal := true
+ t.Each(func(item string) bool {
+ _, equal = s.m[item]
+ return equal // if false, Each() will end
+ })
+
+ return equal
+}
+
+// IsSubset tests whether t is a subset of s.
+func (s *Set) IsSubset(t *Set) bool {
+ if s.Size() < t.Size() {
+ return false
+ }
+
+ subset := true
+
+ t.Each(func(item string) bool {
+ _, subset = s.m[item]
+ return subset
+ })
+
+ return subset
+}
+
+// IsSuperset tests whether t is a superset of s.
+func (s *Set) IsSuperset(t *Set) bool {
+ return t.IsSubset(s)
+}
+
+// Each traverses the items in the Set, calling the provided function for each
+// Set member. Traversal will continue until all items in the Set have been
+// visited, or if the closure returns false.
+func (s *Set) Each(f func(item string) bool) {
+ for item := range s.m {
+ if !f(item) {
+ break
+ }
+ }
+}
+
+// Copy returns a new Set with a copy of s.
+func (s *Set) Copy() *Set {
+ u := NewWithSize(s.Size())
+ for item := range s.m {
+ u.m[item] = keyExists
+ }
+ return u
+}
+
+// String returns a string representation of s
+func (s *Set) String() string {
+ v := make([]string, 0, s.Size())
+ for item := range s.m {
+ v = append(v, fmt.Sprintf("%v", item))
+ }
+ return fmt.Sprintf("[%s]", strings.Join(v, ", "))
+}
+
+// List returns a slice of all items. There is also StringSlice() and
+// IntSlice() methods for returning slices of type string or int.
+func (s *Set) List() []string {
+ v := make([]string, 0, s.Size())
+ for item := range s.m {
+ v = append(v, item)
+ }
+ return v
+}
+
+// Merge is like Union, however it modifies the current Set it's applied on
+// with the given t Set.
+func (s *Set) Merge(t *Set) {
+ for item := range t.m {
+ s.m[item] = keyExists
+ }
+}
+
+// Separate removes the Set items containing in t from Set s. Please aware that
+// it's not the opposite of Merge.
+func (s *Set) Separate(t *Set) {
+ for item := range t.m {
+ delete(s.m, item)
+ }
+}
+
+// Union is the merger of multiple sets. It returns a new set with all the
+// elements present in all the sets that are passed.
+func Union(sets ...*Set) *Set {
+ maxPos := -1
+ maxSize := 0
+ for i, set := range sets {
+ if l := set.Size(); l > maxSize {
+ maxSize = l
+ maxPos = i
+ }
+ }
+ if maxSize == 0 {
+ return New()
+ }
+
+ u := sets[maxPos].Copy()
+ for i, set := range sets {
+ if i == maxPos {
+ continue
+ }
+ for item := range set.m {
+ u.m[item] = keyExists
+ }
+ }
+ return u
+}
+
+// Difference returns a new set which contains items which are in in the first
+// set but not in the others.
+func Difference(set1 *Set, sets ...*Set) *Set {
+ s := set1.Copy()
+ for _, set := range sets {
+ s.Separate(set)
+ }
+ return s
+}
+
+// Intersection returns a new set which contains items that only exist in all
+// given sets.
+func Intersection(sets ...*Set) *Set {
+ minPos := -1
+ minSize := math.MaxInt64
+ for i, set := range sets {
+ if l := set.Size(); l < minSize {
+ minSize = l
+ minPos = i
+ }
+ }
+ if minSize == math.MaxInt64 || minSize == 0 {
+ return New()
+ }
+
+ t := sets[minPos].Copy()
+ for i, set := range sets {
+ if i == minPos {
+ continue
+ }
+ for item := range t.m {
+ if _, has := set.m[item]; !has {
+ delete(t.m, item)
+ }
+ }
+ }
+ return t
+}
+
+// SymmetricDifference returns a new set which s is the difference of items
+// which are in one of either, but not in both.
+func SymmetricDifference(s *Set, t *Set) *Set {
+ u := Difference(s, t)
+ v := Difference(t, s)
+ return Union(u, v)
+}