You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by nf...@apache.org on 2018/09/06 11:06:33 UTC

[camel-k] 05/05: Detect changes with digest and redeploy

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

commit f42dedff26826e3e68a565fbc965bc9593b3a053
Author: nferraro <ni...@gmail.com>
AuthorDate: Thu Sep 6 13:04:19 2018 +0200

    Detect changes with digest and redeploy
---
 pkg/apis/camel/v1alpha1/types.go                  |  6 +--
 pkg/build/api/types.go                            |  7 ++-
 pkg/build/build_manager.go                        | 11 ++---
 pkg/build/build_manager_integration_test.go       | 18 +++++---
 pkg/build/local/local_builder.go                  | 10 ++---
 pkg/stub/action/build.go                          |  9 ++--
 pkg/stub/action/initialize.go                     |  5 +--
 pkg/stub/{handler.go => action/monitor.go}        | 52 +++++++++++------------
 pkg/stub/handler.go                               |  1 +
 pkg/{build/api/types.go => util/digest/digest.go} | 44 +++++++++----------
 10 files changed, 86 insertions(+), 77 deletions(-)

diff --git a/pkg/apis/camel/v1alpha1/types.go b/pkg/apis/camel/v1alpha1/types.go
index 0f58ff9..ec30eb9 100644
--- a/pkg/apis/camel/v1alpha1/types.go
+++ b/pkg/apis/camel/v1alpha1/types.go
@@ -48,9 +48,9 @@ type SourceSpec struct {
 }
 
 type IntegrationStatus struct {
-	Phase IntegrationPhase `json:"phase,omitempty"`
-	Hash  string           `json:"hash,omitempty"`
-	Image string           `json:"image,omitempty"`
+	Phase  IntegrationPhase `json:"phase,omitempty"`
+	Digest string           `json:"digest,omitempty"`
+	Image  string           `json:"image,omitempty"`
 }
 
 type IntegrationPhase string
diff --git a/pkg/build/api/types.go b/pkg/build/api/types.go
index 4500937..3358a6c 100644
--- a/pkg/build/api/types.go
+++ b/pkg/build/api/types.go
@@ -19,10 +19,15 @@ package api
 
 // a request to build a specific code
 type BuildSource struct {
-	Identifier	string
+	Identifier	BuildIdentifier
 	Code		string
 }
 
+type BuildIdentifier struct {
+	Name	string
+	Digest	string
+}
+
 // represents the result of a build
 type BuildResult struct {
 	Source		*BuildSource
diff --git a/pkg/build/build_manager.go b/pkg/build/build_manager.go
index fee2315..170a5e6 100644
--- a/pkg/build/build_manager.go
+++ b/pkg/build/build_manager.go
@@ -26,19 +26,19 @@ import (
 
 // main facade to the image build system
 type BuildManager struct {
-	builds	map[string]*api.BuildResult
+	builds	map[api.BuildIdentifier]*api.BuildResult
 	mutex	sync.Mutex
 	builder	api.Builder
 }
 
 func NewBuildManager(ctx context.Context, namespace string) *BuildManager {
 	return &BuildManager{
-		builds: make(map[string]*api.BuildResult),
+		builds: make(map[api.BuildIdentifier]*api.BuildResult),
 		builder: local.NewLocalBuilder(ctx, namespace),
 	}
 }
 
-func (m *BuildManager) Get(identifier string) api.BuildResult {
+func (m *BuildManager) Get(identifier api.BuildIdentifier) api.BuildResult {
 	m.mutex.Lock()
 	defer m.mutex.Unlock()
 
@@ -53,7 +53,7 @@ func (m *BuildManager) Start(source api.BuildSource) {
 	m.mutex.Lock()
 	defer m.mutex.Unlock()
 
-	initialBuildInfo := initialBuildInfo()
+	initialBuildInfo := initialBuildInfo(&source)
 	m.builds[source.Identifier] = &initialBuildInfo
 
 	resChannel := m.builder.Build(source)
@@ -72,8 +72,9 @@ func noBuildInfo() api.BuildResult {
 	}
 }
 
-func initialBuildInfo() api.BuildResult {
+func initialBuildInfo(source *api.BuildSource) api.BuildResult {
 	return api.BuildResult{
+		Source: source,
 		Status: api.BuildStatusStarted,
 	}
 }
\ No newline at end of file
diff --git a/pkg/build/build_manager_integration_test.go b/pkg/build/build_manager_integration_test.go
index f74fd4e..dcf7c9e 100644
--- a/pkg/build/build_manager_integration_test.go
+++ b/pkg/build/build_manager_integration_test.go
@@ -31,16 +31,19 @@ import (
 func TestBuild(t *testing.T) {
 	ctx := context.TODO()
 	buildManager := NewBuildManager(ctx, test.GetTargetNamespace())
-
+	identifier := build.BuildIdentifier{
+		Name: "example",
+		Digest: "sadsadasdsadasdafwefwef",
+	}
 	buildManager.Start(build.BuildSource{
-		Identifier: "1",
+		Identifier: identifier,
 		Code: code(),
 	})
 
 	deadline := time.Now().Add(5 * time.Minute)
 	var result build.BuildResult
 	for time.Now().Before(deadline) {
-		result = buildManager.Get("1")
+		result = buildManager.Get(identifier)
 		if result.Status == build.BuildStatusCompleted || result.Status == build.BuildStatusError {
 			break
 		}
@@ -56,16 +59,19 @@ func TestFailedBuild(t *testing.T) {
 
 	ctx := context.TODO()
 	buildManager := NewBuildManager(ctx, test.GetTargetNamespace())
-
+	identifier := build.BuildIdentifier{
+		Name: "example",
+		Digest: "545454",
+	}
 	buildManager.Start(build.BuildSource{
-		Identifier: "1",
+		Identifier: identifier,
 		Code: code() + "XX",
 	})
 
 	deadline := time.Now().Add(5 * time.Minute)
 	var result build.BuildResult
 	for time.Now().Before(deadline) {
-		result = buildManager.Get("1")
+		result = buildManager.Get(identifier)
 		if result.Status == build.BuildStatusCompleted || result.Status == build.BuildStatusError {
 			break
 		}
diff --git a/pkg/build/local/local_builder.go b/pkg/build/local/local_builder.go
index 9874434..02bacf1 100644
--- a/pkg/build/local/local_builder.go
+++ b/pkg/build/local/local_builder.go
@@ -139,7 +139,7 @@ func (b *localBuilder) publish(tarFile string, source build.BuildSource) (string
 			Kind: "BuildConfig",
 		},
 		ObjectMeta: metav1.ObjectMeta{
-			Name: "kamel",
+			Name: "kamel-" + source.Identifier.Name,
 			Namespace: b.namespace,
 		},
 		Spec: buildv1.BuildConfigSpec{
@@ -158,7 +158,7 @@ func (b *localBuilder) publish(tarFile string, source build.BuildSource) (string
 				Output: buildv1.BuildOutput{
 					To: &v1.ObjectReference{
 						Kind: "ImageStreamTag",
-						Name: "kamel:latest",
+						Name: "kamel-" + source.Identifier.Name + ":" + source.Identifier.Digest,
 					},
 				},
 			},
@@ -177,7 +177,7 @@ func (b *localBuilder) publish(tarFile string, source build.BuildSource) (string
 			Kind: "ImageStream",
 		},
 		ObjectMeta: metav1.ObjectMeta{
-			Name: "kamel",
+			Name: "kamel-" + source.Identifier.Name,
 			Namespace: b.namespace,
 		},
 		Spec: imagev1.ImageStreamSpec{
@@ -224,7 +224,7 @@ func (b *localBuilder) publish(tarFile string, source build.BuildSource) (string
 		Namespace(b.namespace).
 		Body(resource).
 		Resource("buildconfigs").
-		Name("kamel").
+		Name("kamel-" + source.Identifier.Name).
 		SubResource("instantiatebinary").
 		Do()
 
@@ -269,7 +269,7 @@ func (b *localBuilder) publish(tarFile string, source build.BuildSource) (string
 	if is.Status.DockerImageRepository == "" {
 		return "", errors.New("dockerImageRepository not available in ImageStream")
 	}
-	return is.Status.DockerImageRepository + ":latest", nil
+	return is.Status.DockerImageRepository + ":" + source.Identifier.Digest, nil
 }
 
 func (b *localBuilder) createTar(buildDir string, source build.BuildSource) (string, error) {
diff --git a/pkg/stub/action/build.go b/pkg/stub/action/build.go
index 3b7b942..62517b0 100644
--- a/pkg/stub/action/build.go
+++ b/pkg/stub/action/build.go
@@ -45,11 +45,14 @@ func (b *BuildAction) CanHandle(integration *v1alpha1.Integration) bool {
 }
 
 func (b *BuildAction) Handle(integration *v1alpha1.Integration) error {
-
-	buildResult := b.buildManager.Get(integration.Status.Hash)
+	buildIdentifier := api.BuildIdentifier{
+		Name: integration.Name,
+		Digest: integration.Status.Digest,
+	}
+	buildResult := b.buildManager.Get(buildIdentifier)
 	if buildResult.Status == api.BuildStatusNotRequested {
 		b.buildManager.Start(api.BuildSource{
-			Identifier: integration.Status.Hash,
+			Identifier: buildIdentifier,
 			Code: *integration.Spec.Source.Code, // FIXME possible panic
 		})
 		logrus.Info("Build started")
diff --git a/pkg/stub/action/initialize.go b/pkg/stub/action/initialize.go
index 52d8db9..8b5a445 100644
--- a/pkg/stub/action/initialize.go
+++ b/pkg/stub/action/initialize.go
@@ -20,8 +20,7 @@ package action
 import (
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/operator-framework/operator-sdk/pkg/sdk"
-	"math/rand"
-	"strconv"
+	"github.com/apache/camel-k/pkg/util/digest"
 )
 
 // initializes the integration status to trigger the deployment
@@ -50,6 +49,6 @@ func (b *InitializeAction) Handle(integration *v1alpha1.Integration) error {
 	}
 	// update the status
 	target.Status.Phase = v1alpha1.IntegrationPhaseBuilding
-	target.Status.Hash = strconv.Itoa(rand.Int()) // TODO replace with hash
+	target.Status.Digest = digest.Compute(integration)
 	return sdk.Update(target)
 }
diff --git a/pkg/stub/handler.go b/pkg/stub/action/monitor.go
similarity index 52%
copy from pkg/stub/handler.go
copy to pkg/stub/action/monitor.go
index ca46e54..deb30d6 100644
--- a/pkg/stub/handler.go
+++ b/pkg/stub/action/monitor.go
@@ -15,43 +15,43 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package stub
+package action
 
 import (
-	"context"
-
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
-
 	"github.com/operator-framework/operator-sdk/pkg/sdk"
-	"github.com/apache/camel-k/pkg/stub/action"
+	"github.com/apache/camel-k/pkg/util/digest"
 	"github.com/sirupsen/logrus"
 )
 
-func NewHandler(ctx context.Context, namespace string) sdk.Handler {
-	return &Handler{
-		actionPool: []action.Action{
-			action.NewInitializeAction(),
-			action.NewBuildAction(ctx, namespace),
-			action.NewDeployAction(),
-		},
-	}
+type MonitorAction struct {
+}
+
+func NewMonitorAction() *MonitorAction {
+	return &MonitorAction{}
 }
 
-type Handler struct {
-	actionPool	[]action.Action
+func (b *MonitorAction) Name() string {
+	return "monitor"
 }
 
-func (h *Handler) Handle(ctx context.Context, event sdk.Event) error {
-	switch o := event.Object.(type) {
-	case *v1alpha1.Integration:
-		for _, a := range h.actionPool {
-			if a.CanHandle(o) {
-				logrus.Info("Invoking action ", a.Name(), " on integration ", o.Name)
-				if err := a.Handle(o); err != nil {
-					return err
-				}
-			}
-		}
+func (a *MonitorAction) CanHandle(integration *v1alpha1.Integration) bool {
+	return integration.Status.Phase == v1alpha1.IntegrationPhaseRunning ||
+		integration.Status.Phase == v1alpha1.IntegrationPhaseError
+}
+
+func (a *MonitorAction) Handle(integration *v1alpha1.Integration) error {
+
+	hash := digest.Compute(integration)
+	if hash != integration.Status.Digest {
+		logrus.Info("Integration ", integration.Name, " needs a rebuild")
+
+		target := integration.DeepCopy()
+		target.Status.Digest=hash
+		target.Status.Phase=v1alpha1.IntegrationPhaseBuilding
+		return sdk.Update(target)
 	}
+
+	// TODO check also if deployment matches (e.g. replicas)
 	return nil
 }
diff --git a/pkg/stub/handler.go b/pkg/stub/handler.go
index ca46e54..a46c16f 100644
--- a/pkg/stub/handler.go
+++ b/pkg/stub/handler.go
@@ -33,6 +33,7 @@ func NewHandler(ctx context.Context, namespace string) sdk.Handler {
 			action.NewInitializeAction(),
 			action.NewBuildAction(ctx, namespace),
 			action.NewDeployAction(),
+			action.NewMonitorAction(),
 		},
 	}
 }
diff --git a/pkg/build/api/types.go b/pkg/util/digest/digest.go
similarity index 52%
copy from pkg/build/api/types.go
copy to pkg/util/digest/digest.go
index 4500937..c29c018 100644
--- a/pkg/build/api/types.go
+++ b/pkg/util/digest/digest.go
@@ -15,31 +15,25 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package api
+package digest
 
-// a request to build a specific code
-type BuildSource struct {
-	Identifier	string
-	Code		string
-}
-
-// represents the result of a build
-type BuildResult struct {
-	Source		*BuildSource
-	Status		BuildStatus
-	Image		string
-	Error		error
-}
+import (
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"crypto/sha256"
+	"github.com/apache/camel-k/version"
+	"encoding/base64"
+)
 
-// supertype of all builders
-type Builder interface {
-	Build(BuildSource) <- chan BuildResult
+// Compute a digest of the fields that are relevant for the deployment
+// Produces a digest that can be used as docker image tag
+func Compute(integration *v1alpha1.Integration) string {
+	hash := sha256.New()
+	// Operator version is relevant
+	hash.Write([]byte(version.Version))
+	// Integration relevant fields
+	if integration.Spec.Source.Code != nil {
+		hash.Write([]byte(*integration.Spec.Source.Code))
+	}
+	// Add a letter at the beginning and use URL safe encoding
+	return "v" + base64.RawURLEncoding.EncodeToString(hash.Sum(nil))
 }
-
-type BuildStatus int
-const (
-	BuildStatusNotRequested		BuildStatus = iota
-	BuildStatusStarted
-	BuildStatusCompleted
-	BuildStatusError
-)
\ No newline at end of file