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:31 UTC
[camel-k] 03/05: Initial draft of local build system
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 8661409fac1c95481ac5b25cb1db648f687a2d10
Author: nferraro <ni...@gmail.com>
AuthorDate: Mon Sep 3 09:49:57 2018 +0200
Initial draft of local build system
---
Gopkg.lock | 36 ++-
cmd/camel-k/main.go | 5 +-
pkg/apis/camel/v1alpha1/types.go | 4 +-
pkg/build/{build.go => api/types.go} | 28 +-
pkg/build/build_manager.go | 67 ++++
pkg/build/local/local_builder.go | 433 ++++++++++++++++++++++++++
pkg/build/local/local_builder_test.go | 90 ++++++
pkg/build/local/scheme.go | 98 ++++++
pkg/stub/action/action.go | 6 +-
pkg/stub/action/build.go | 63 ++++
pkg/stub/{handler.go => action/initialize.go} | 40 +--
pkg/stub/handler.go | 11 +-
pkg/util/kubernetes/wait.go | 39 +++
pkg/util/openshift/register.go | 77 +++++
14 files changed, 963 insertions(+), 34 deletions(-)
diff --git a/Gopkg.lock b/Gopkg.lock
index c662f8a..08cdcda 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -169,6 +169,22 @@
version = "1.0.1"
[[projects]]
+ name = "github.com/openshift/api"
+ packages = [
+ "apps/v1",
+ "authorization/v1",
+ "build/v1",
+ "image/docker10",
+ "image/dockerpre012",
+ "image/v1",
+ "pkg/serialization",
+ "route/v1",
+ "template/v1"
+ ]
+ revision = "0d921e363e951d89f583292c60d013c318df64dc"
+ version = "v3.9.0"
+
+[[projects]]
branch = "master"
name = "github.com/operator-framework/operator-sdk"
packages = [
@@ -181,6 +197,18 @@
revision = "0f60da86a138f7e79f275d539c18615bc2727014"
[[projects]]
+ name = "github.com/pkg/errors"
+ packages = ["."]
+ revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
+ version = "v0.8.0"
+
+[[projects]]
+ name = "github.com/pmezard/go-difflib"
+ packages = ["difflib"]
+ revision = "792786c7400a136282c1664665ae0a8db921c6c2"
+ version = "v1.0.0"
+
+[[projects]]
name = "github.com/prometheus/client_golang"
packages = [
"prometheus",
@@ -229,6 +257,12 @@
version = "v1.0.2"
[[projects]]
+ name = "github.com/stretchr/testify"
+ packages = ["assert"]
+ revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
+ version = "v1.2.2"
+
+[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
@@ -538,6 +572,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "22a3131aff0c3b6f0a97bc22324996cfa5dabe1f626a90facb0daf29bbe0d52a"
+ inputs-digest = "fe3fc8a50615445124a1a85023b09a54b2aa9f0dfc077f6c4823b46ee4ba7ef1"
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/cmd/camel-k/main.go b/cmd/camel-k/main.go
index 63c4fc0..8fdaa53 100644
--- a/cmd/camel-k/main.go
+++ b/cmd/camel-k/main.go
@@ -48,9 +48,10 @@ func main() {
if err != nil {
logrus.Fatalf("failed to get watch namespace: %v", err)
}
+ ctx := context.TODO()
resyncPeriod := time.Duration(5) * time.Second
logrus.Infof("Watching %s, %s, %s, %d", resource, kind, namespace, resyncPeriod)
sdk.Watch(resource, kind, namespace, resyncPeriod)
- sdk.Handle(stub.NewHandler())
- sdk.Run(context.TODO())
+ sdk.Handle(stub.NewHandler(ctx, namespace))
+ sdk.Run(ctx)
}
diff --git a/pkg/apis/camel/v1alpha1/types.go b/pkg/apis/camel/v1alpha1/types.go
index d1a4431..b8ed15a 100644
--- a/pkg/apis/camel/v1alpha1/types.go
+++ b/pkg/apis/camel/v1alpha1/types.go
@@ -48,13 +48,15 @@ type SourceSpec struct {
}
type IntegrationStatus struct {
- Phase IntegrationPhase `json:"phase,omitempty"`
+ Phase IntegrationPhase `json:"phase,omitempty"`
+ Identifier string `json:"identifier,omitempty"`
}
type IntegrationPhase string
const (
IntegrationPhaseBuilding IntegrationPhase = "Building"
+ IntegrationPhaseDeploying IntegrationPhase = "Deploying"
IntegrationPhaseRunning IntegrationPhase = "Running"
IntegrationPhaseError IntegrationPhase = "Error"
)
\ No newline at end of file
diff --git a/pkg/build/build.go b/pkg/build/api/types.go
similarity index 61%
rename from pkg/build/build.go
rename to pkg/build/api/types.go
index 88d762c..4500937 100644
--- a/pkg/build/build.go
+++ b/pkg/build/api/types.go
@@ -15,5 +15,31 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package build
+package api
+// 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
+}
+
+// supertype of all builders
+type Builder interface {
+ Build(BuildSource) <- chan BuildResult
+}
+
+type BuildStatus int
+const (
+ BuildStatusNotRequested BuildStatus = iota
+ BuildStatusStarted
+ BuildStatusCompleted
+ BuildStatusError
+)
\ No newline at end of file
diff --git a/pkg/build/build_manager.go b/pkg/build/build_manager.go
new file mode 100644
index 0000000..7ddd03a
--- /dev/null
+++ b/pkg/build/build_manager.go
@@ -0,0 +1,67 @@
+/*
+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 build
+
+import (
+ "sync"
+ "github.com/apache/camel-k/pkg/build/local"
+ "context"
+ "github.com/apache/camel-k/pkg/build/api"
+)
+
+// main facade to the image build system
+type BuildManager struct {
+ builds map[string]*api.BuildResult
+ mutex sync.Mutex
+ builder api.Builder
+}
+
+func NewBuildManager(ctx context.Context, namespace string) *BuildManager {
+ return &BuildManager{
+ builds: make(map[string]*api.BuildResult),
+ builder: local.NewLocalBuilder(ctx, namespace),
+ }
+}
+
+func (m *BuildManager) Get(identifier string) api.BuildResult {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ if info, present := m.builds[identifier]; !present || info == nil {
+ return noBuildInfo()
+ } else {
+ return *info
+ }
+}
+
+func (m *BuildManager) Start(source api.BuildSource) {
+ resChannel := m.builder.Build(source)
+ go func() {
+ res := <-resChannel
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ m.builds[res.Source.Identifier] = &res
+ }()
+}
+
+func noBuildInfo() api.BuildResult {
+ return api.BuildResult{
+ Status: api.BuildStatusNotRequested,
+ }
+}
\ No newline at end of file
diff --git a/pkg/build/local/local_builder.go b/pkg/build/local/local_builder.go
new file mode 100644
index 0000000..bdbb3d6
--- /dev/null
+++ b/pkg/build/local/local_builder.go
@@ -0,0 +1,433 @@
+/*
+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 local
+
+import (
+ "context"
+ "github.com/sirupsen/logrus"
+ "time"
+ "io/ioutil"
+ "path"
+ "os"
+ "os/exec"
+ "github.com/pkg/errors"
+ "archive/tar"
+ "io"
+ buildv1 "github.com/openshift/api/build/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/api/core/v1"
+ "github.com/operator-framework/operator-sdk/pkg/sdk"
+ "k8s.io/client-go/rest"
+ "github.com/operator-framework/operator-sdk/pkg/k8sclient"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ imagev1 "github.com/openshift/api/image/v1"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+ "github.com/operator-framework/operator-sdk/pkg/util/k8sutil"
+
+ _ "github.com/apache/camel-k/pkg/util/openshift"
+ build "github.com/apache/camel-k/pkg/build/api"
+ "github.com/apache/camel-k/pkg/util/kubernetes"
+)
+
+type localBuilder struct {
+ buffer chan buildOperation
+ namespace string
+}
+
+type buildOperation struct {
+ source build.BuildSource
+ output chan build.BuildResult
+}
+
+func NewLocalBuilder(ctx context.Context, namespace string) build.Builder {
+ builder := localBuilder{
+ buffer: make(chan buildOperation, 100),
+ namespace: namespace,
+ }
+ go builder.buildCycle(ctx)
+ return &builder
+}
+
+func (b *localBuilder) Build(source build.BuildSource) <- chan build.BuildResult {
+ res := make(chan build.BuildResult, 1)
+ op := buildOperation{
+ source: source,
+ output: res,
+ }
+ b.buffer <- op
+ return res
+}
+
+func (b *localBuilder) buildCycle(ctx context.Context) {
+ for {
+ select {
+ case <- ctx.Done():
+ b.buffer = nil
+ return
+ case op := <- b.buffer:
+ now := time.Now()
+ logrus.Info("Starting new build")
+ err := b.execute(op.source)
+ elapsed := time.Now().Sub(now)
+ if err != nil {
+ logrus.Error("Error during build (total time ", elapsed.Seconds(), " seconds): ", err)
+ } else {
+ logrus.Info("Build completed in ", elapsed.Seconds(), " seconds")
+ }
+
+ if err != nil {
+ op.output <- build.BuildResult{
+ Source: &op.source,
+ Status: build.BuildStatusError,
+ Error: err,
+ }
+ } else {
+ op.output <- build.BuildResult{
+ Source: &op.source,
+ Status: build.BuildStatusCompleted,
+ Image: "kamel:latest",
+ }
+ }
+
+ }
+ }
+}
+
+func (b *localBuilder) execute(source build.BuildSource) error {
+ buildDir, err := ioutil.TempDir("", "kamel-")
+ if err != nil {
+ return errors.Wrap(err, "could not create temporary dir for maven artifacts")
+ }
+ //defer os.RemoveAll(buildDir)
+
+ logrus.Info("Using build dir : ", buildDir)
+
+ tarFileName, err := b.createTar(buildDir, source)
+ if err != nil {
+ return err
+ }
+ logrus.Info("Created tar file ", tarFileName)
+
+ err = b.publish(tarFileName, source)
+ if err != nil {
+ return errors.Wrap(err, "could not publish docker image")
+ }
+
+ return nil
+}
+
+func (b *localBuilder) publish(tarFile string, source build.BuildSource) error {
+
+ bc := buildv1.BuildConfig{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: buildv1.SchemeGroupVersion.String(),
+ Kind: "BuildConfig",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "kamel",
+ Namespace: b.namespace,
+ },
+ Spec: buildv1.BuildConfigSpec{
+ CommonSpec: buildv1.CommonSpec{
+ Source: buildv1.BuildSource{
+ Type: buildv1.BuildSourceBinary,
+ },
+ Strategy: buildv1.BuildStrategy{
+ SourceStrategy: &buildv1.SourceBuildStrategy{
+ From: v1.ObjectReference{
+ Kind: "DockerImage",
+ Name: "fabric8/s2i-java:2.1",
+ },
+ },
+ },
+ Output: buildv1.BuildOutput{
+ To: &v1.ObjectReference{
+ Kind: "ImageStreamTag",
+ Name: "kamel:latest",
+ },
+ },
+ },
+ },
+ }
+
+ sdk.Delete(&bc)
+ err := sdk.Create(&bc)
+ if err != nil {
+ return errors.Wrap(err, "cannot create build config")
+ }
+
+ is := imagev1.ImageStream{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: imagev1.SchemeGroupVersion.String(),
+ Kind: "ImageStream",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "kamel",
+ Namespace: b.namespace,
+ },
+ Spec: imagev1.ImageStreamSpec{
+ LookupPolicy: imagev1.ImageLookupPolicy{
+ Local: true,
+ },
+ },
+ }
+
+ sdk.Delete(&is)
+ err = sdk.Create(&is)
+ if err != nil {
+ return errors.Wrap(err, "cannot create image stream")
+ }
+
+ inConfig := k8sclient.GetKubeConfig()
+ config := rest.CopyConfig(inConfig)
+ config.GroupVersion = &schema.GroupVersion{
+ Group: "build.openshift.io",
+ Version: "v1",
+ }
+ config.APIPath = "/apis"
+ config.AcceptContentTypes = "application/json"
+ config.ContentType = "application/json"
+
+ // this gets used for discovery and error handling types
+ config.NegotiatedSerializer = basicNegotiatedSerializer{}
+ if config.UserAgent == "" {
+ config.UserAgent = rest.DefaultKubernetesUserAgent()
+ }
+
+ restClient, err := rest.RESTClientFor(config)
+ if err != nil {
+ return err
+ }
+
+ resource, err := ioutil.ReadFile(tarFile)
+ if err != nil {
+ return errors.Wrap(err, "cannot fully read tar file " + tarFile)
+ }
+
+ result := restClient.
+ Post().
+ Namespace(b.namespace).
+ Body(resource).
+ Resource("buildconfigs").
+ Name("kamel").
+ SubResource("instantiatebinary").
+ Do()
+
+ if result.Error() != nil {
+ return errors.Wrap(result.Error(), "cannot instantiate binary")
+ }
+
+ data, err := result.Raw()
+ if err != nil {
+ return errors.Wrap(err, "no raw data retrieved")
+ }
+
+ u := unstructured.Unstructured{}
+ err = u.UnmarshalJSON(data)
+ if err != nil {
+ return errors.Wrap(err, "cannot unmarshal instantiate binary response")
+ }
+
+ ocbuild, err := k8sutil.RuntimeObjectFromUnstructured(&u)
+ if err != nil {
+ return err
+ }
+ logrus.Info(ocbuild)
+
+ err = kubernetes.WaitCondition(ocbuild, func(obj interface{})(bool, error) {
+ if val, ok := obj.(*buildv1.Build); ok {
+ if val.Status.Phase == buildv1.BuildPhaseComplete {
+ return true, nil
+ } else if val.Status.Phase == buildv1.BuildPhaseCancelled ||
+ val.Status.Phase == buildv1.BuildPhaseFailed ||
+ val.Status.Phase == buildv1.BuildPhaseError {
+ return false, errors.New("build failed")
+ }
+ }
+ return false, nil
+ }, 5 * time.Minute)
+
+ return err
+}
+
+func (b *localBuilder) createTar(buildDir string, source build.BuildSource) (string, error) {
+ dir, err := b.createMavenStructure(buildDir, source)
+ if err != nil {
+ return "", err
+ }
+
+ mavenBuild := exec.Command("mvn", "clean", "install", "-DskipTests")
+ mavenBuild.Dir = dir
+ logrus.Info("Starting maven build: mvn clean install -DskipTests")
+ err = mavenBuild.Run()
+ if err != nil {
+ return "", errors.Wrap(err, "failure while executing maven build")
+ }
+
+ mavenDep := exec.Command("mvn", "dependency:copy-dependencies")
+ mavenDep.Dir = dir
+ logrus.Info("Copying maven dependencies: mvn dependency:copy-dependencies")
+ err = mavenDep.Run()
+ if err != nil {
+ return "", errors.Wrap(err, "failure while extracting maven dependencies")
+ }
+ logrus.Info("Maven build completed successfully")
+
+ tarFileName := path.Join(buildDir, "kamel-app.tar")
+ tarFile, err := os.Create(tarFileName)
+ if err != nil {
+ return "", errors.Wrap(err, "cannot create tar file")
+ }
+ defer tarFile.Close()
+
+ writer := tar.NewWriter(tarFile)
+ err = b.appendToTar(path.Join(buildDir, "target", "kamel-app-1.0.0.jar"), "", writer)
+ if err != nil {
+ return "", err
+ }
+
+ err = b.appendToTar(path.Join(buildDir, "environment"), ".s2i", writer)
+ if err != nil {
+ return "", err
+ }
+
+ dependenciesDir := path.Join(buildDir, "target", "dependency")
+ dependencies, err := ioutil.ReadDir(dependenciesDir)
+ if err != nil {
+ return "", err
+ }
+
+ for _, dep := range dependencies {
+ err = b.appendToTar(path.Join(dependenciesDir, dep.Name()), "", writer)
+ if err != nil {
+ return "", err
+ }
+ }
+
+ writer.Close()
+
+ return tarFileName, nil
+}
+
+func (b *localBuilder) appendToTar(filePath string, tarPath string, writer *tar.Writer) error {
+ info, err := os.Stat(filePath)
+ if err != nil {
+ return err
+ }
+ _, fileName := path.Split(filePath)
+ if tarPath != "" {
+ fileName = path.Join(tarPath, fileName)
+ }
+
+ writer.WriteHeader(&tar.Header{
+ Name: fileName,
+ Size: info.Size(),
+ Mode: int64(info.Mode()),
+ ModTime: info.ModTime(),
+ })
+
+ file, err := os.Open(filePath)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ _, err = io.Copy(writer, file)
+ if err != nil {
+ return errors.Wrap(err, "cannot add file to the tar archive")
+ }
+ return nil
+}
+
+func (b *localBuilder) createMavenStructure(buildDir string, source build.BuildSource) (string, error) {
+ pomFileName := path.Join(buildDir, "pom.xml")
+ pom, err := os.Create(pomFileName)
+ if err != nil {
+ return "", err
+ }
+ defer pom.Close()
+
+ _, err = pom.WriteString(b.createPom())
+ if err != nil {
+ return "", err
+ }
+
+ packageDir := path.Join(buildDir, "src", "main", "java", "kamel")
+ err = os.MkdirAll(packageDir, 0777)
+ if err != nil {
+ return "", err
+ }
+
+ sourceFileName := path.Join(packageDir, "Routes.java")
+ sourceFile, err := os.Create(sourceFileName)
+ if err != nil {
+ return "", err
+ }
+ defer sourceFile.Close()
+
+ _, err = sourceFile.WriteString(source.Code)
+ if err != nil {
+ return "", err
+ }
+
+
+ envFileName := path.Join(buildDir, "environment")
+ envFile, err := os.Create(envFileName)
+ if err != nil {
+ return "", err
+ }
+ defer envFile.Close()
+
+ _, err = envFile.WriteString(b.createEnvFile())
+ if err != nil {
+ return "", err
+ }
+
+ return buildDir, nil
+}
+
+func (b *localBuilder) createEnvFile() string {
+ return `
+JAVA_MAIN_CLASS=me.nicolaferraro.kamel.Application
+KAMEL_CLASS=kamel.Routes
+`
+}
+
+
+func (b *localBuilder) createPom() string {
+ return `<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>me.nicolaferraro.kamel.app</groupId>
+ <artifactId>kamel-app</artifactId>
+ <version>1.0.0</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>me.nicolaferraro.kamel</groupId>
+ <artifactId>camel-java-runtime</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
+</project>
+`
+}
diff --git a/pkg/build/local/local_builder_test.go b/pkg/build/local/local_builder_test.go
new file mode 100644
index 0000000..f179682
--- /dev/null
+++ b/pkg/build/local/local_builder_test.go
@@ -0,0 +1,90 @@
+/*
+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 local
+
+import (
+ "testing"
+ "context"
+ "github.com/stretchr/testify/assert"
+ build "github.com/apache/camel-k/pkg/build/api"
+)
+
+func TestBuild(t *testing.T) {
+
+ ctx := context.TODO()
+ builder := NewLocalBuilder(ctx, "test")
+
+ execution := builder.Build(build.BuildSource{
+ Code: code(),
+ })
+
+ res := <- execution
+
+ assert.Nil(t, res.Error, "Build failed")
+}
+
+func TestDoubleBuild(t *testing.T) {
+
+ ctx := context.TODO()
+ builder := NewLocalBuilder(ctx, "test")
+
+ execution1 := builder.Build(build.BuildSource{
+ Code: code(),
+ })
+
+ execution2 := builder.Build(build.BuildSource{
+ Code: code(),
+ })
+
+ res1 := <- execution1
+ res2 := <- execution2
+
+ assert.Nil(t, res1.Error, "Build failed")
+ assert.Nil(t, res2.Error, "Build failed")
+}
+
+func TestFailedBuild(t *testing.T) {
+
+ ctx := context.TODO()
+ builder := NewLocalBuilder(ctx, "test")
+
+ execution := builder.Build(build.BuildSource{
+ Code: code() + "-",
+ })
+
+ res := <- execution
+
+ assert.NotNil(t, res.Error, "Build should fail")
+}
+
+func code() string {
+ return `package kamel;
+
+import org.apache.camel.builder.RouteBuilder;
+
+public class Routes extends RouteBuilder {
+
+ @Override
+ public void configure() throws Exception {
+ from("timer:tick")
+ .to("log:info");
+ }
+
+}
+`
+}
\ No newline at end of file
diff --git a/pkg/build/local/scheme.go b/pkg/build/local/scheme.go
new file mode 100644
index 0000000..7fdc19f
--- /dev/null
+++ b/pkg/build/local/scheme.go
@@ -0,0 +1,98 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package local
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apimachinery/pkg/runtime/serializer"
+ "k8s.io/apimachinery/pkg/runtime/serializer/json"
+ "k8s.io/apimachinery/pkg/runtime/serializer/versioning"
+)
+
+var watchScheme = runtime.NewScheme()
+var basicScheme = runtime.NewScheme()
+var deleteScheme = runtime.NewScheme()
+var parameterScheme = runtime.NewScheme()
+var deleteOptionsCodec = serializer.NewCodecFactory(deleteScheme)
+var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme)
+
+var versionV1 = schema.GroupVersion{Version: "v1"}
+
+func init() {
+ metav1.AddToGroupVersion(watchScheme, versionV1)
+ metav1.AddToGroupVersion(basicScheme, versionV1)
+ metav1.AddToGroupVersion(parameterScheme, versionV1)
+ metav1.AddToGroupVersion(deleteScheme, versionV1)
+}
+
+var watchJsonSerializerInfo = runtime.SerializerInfo{
+ MediaType: "application/json",
+ EncodesAsText: true,
+ Serializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, false),
+ PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, true),
+ StreamSerializer: &runtime.StreamSerializerInfo{
+ EncodesAsText: true,
+ Serializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, false),
+ Framer: json.Framer,
+ },
+}
+
+// watchNegotiatedSerializer is used to read the wrapper of the watch stream
+type watchNegotiatedSerializer struct{}
+
+var watchNegotiatedSerializerInstance = watchNegotiatedSerializer{}
+
+func (s watchNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
+ return []runtime.SerializerInfo{watchJsonSerializerInfo}
+}
+
+func (s watchNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
+ return versioning.NewDefaultingCodecForScheme(watchScheme, encoder, nil, gv, nil)
+}
+
+func (s watchNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
+ return versioning.NewDefaultingCodecForScheme(watchScheme, nil, decoder, nil, gv)
+}
+
+// basicNegotiatedSerializer is used to handle discovery and error handling serialization
+type basicNegotiatedSerializer struct{}
+
+func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
+ return []runtime.SerializerInfo{
+ {
+ MediaType: "application/json",
+ EncodesAsText: true,
+ Serializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, false),
+ PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, true),
+ StreamSerializer: &runtime.StreamSerializerInfo{
+ EncodesAsText: true,
+ Serializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, false),
+ Framer: json.Framer,
+ },
+ },
+ }
+}
+
+func (s basicNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
+ return versioning.NewDefaultingCodecForScheme(watchScheme, encoder, nil, gv, nil)
+}
+
+func (s basicNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
+ return versioning.NewDefaultingCodecForScheme(watchScheme, nil, decoder, nil, gv)
+}
\ No newline at end of file
diff --git a/pkg/stub/action/action.go b/pkg/stub/action/action.go
index 185105f..d7558ce 100644
--- a/pkg/stub/action/action.go
+++ b/pkg/stub/action/action.go
@@ -23,8 +23,10 @@ import (
type Action interface {
- CanExecute(integration *v1alpha1.Integration) bool
+ // returns true if the action can handle the integration
+ CanHandle(integration *v1alpha1.Integration) bool
- Execute(syndesis *v1alpha1.Integration) error
+ // executes the handling function
+ Handle(integration *v1alpha1.Integration) error
}
diff --git a/pkg/stub/action/build.go b/pkg/stub/action/build.go
new file mode 100644
index 0000000..ccae198
--- /dev/null
+++ b/pkg/stub/action/build.go
@@ -0,0 +1,63 @@
+/*
+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 action
+
+import (
+ "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+ "context"
+ "github.com/apache/camel-k/pkg/build"
+ "github.com/sirupsen/logrus"
+ "github.com/operator-framework/operator-sdk/pkg/sdk"
+ "github.com/apache/camel-k/pkg/build/api"
+)
+
+type BuildAction struct {
+ buildManager *build.BuildManager
+}
+
+func NewBuildAction(ctx context.Context, namespace string) *BuildAction {
+ return &BuildAction{
+ buildManager: build.NewBuildManager(ctx, namespace),
+ }
+}
+
+func (b *BuildAction) CanHandle(integration *v1alpha1.Integration) bool {
+ return integration.Status.Phase == v1alpha1.IntegrationPhaseBuilding
+}
+
+func (b *BuildAction) Handle(integration *v1alpha1.Integration) error {
+
+ buildResult := b.buildManager.Get(integration.Status.Identifier)
+ if buildResult.Status == api.BuildStatusNotRequested {
+ b.buildManager.Start(api.BuildSource{
+ Identifier: integration.Status.Identifier,
+ Code: *integration.Spec.Source.Code, // FIXME possible panic
+ })
+ logrus.Info("Build started")
+ } else if buildResult.Status == api.BuildStatusError {
+ target := integration.DeepCopy()
+ target.Status.Phase = v1alpha1.IntegrationPhaseError
+ return sdk.Update(target)
+ } else if buildResult.Status == api.BuildStatusCompleted {
+ target := integration.DeepCopy()
+ target.Status.Phase = v1alpha1.IntegrationPhaseDeploying
+ return sdk.Update(target)
+ }
+
+ return nil
+}
diff --git a/pkg/stub/handler.go b/pkg/stub/action/initialize.go
similarity index 59%
copy from pkg/stub/handler.go
copy to pkg/stub/action/initialize.go
index 8f6fd16..60f8dc6 100644
--- a/pkg/stub/handler.go
+++ b/pkg/stub/action/initialize.go
@@ -15,37 +15,31 @@ 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"
+ "math/rand"
+ "strconv"
)
-func NewHandler() sdk.Handler {
- return &Handler{
- actionPool: []action.Action{},
- }
+// initializes the integration status to trigger the deployment
+type InitializeAction struct {
+
+}
+
+func NewInitializeAction() *InitializeAction {
+ return &InitializeAction{}
}
-type Handler struct {
- actionPool []action.Action
+func (b *InitializeAction) CanHandle(integration *v1alpha1.Integration) bool {
+ return integration.Status.Phase == ""
}
-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.CanExecute(o) {
- if err := a.Execute(o); err != nil {
- return err
- }
- }
- }
- }
- return nil
+func (b *InitializeAction) Handle(integration *v1alpha1.Integration) error {
+ target := integration.DeepCopy()
+ target.Status.Phase = v1alpha1.IntegrationPhaseBuilding
+ target.Status.Identifier = strconv.Itoa(rand.Int()) // TODO replace with hash
+ return sdk.Update(target)
}
diff --git a/pkg/stub/handler.go b/pkg/stub/handler.go
index 8f6fd16..7c52247 100644
--- a/pkg/stub/handler.go
+++ b/pkg/stub/handler.go
@@ -26,9 +26,12 @@ import (
"github.com/apache/camel-k/pkg/stub/action"
)
-func NewHandler() sdk.Handler {
+func NewHandler(ctx context.Context, namespace string) sdk.Handler {
return &Handler{
- actionPool: []action.Action{},
+ actionPool: []action.Action{
+ action.NewInitializeAction(),
+ action.NewBuildAction(ctx, namespace),
+ },
}
}
@@ -40,8 +43,8 @@ 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.CanExecute(o) {
- if err := a.Execute(o); err != nil {
+ if a.CanHandle(o) {
+ if err := a.Handle(o); err != nil {
return err
}
}
diff --git a/pkg/util/kubernetes/wait.go b/pkg/util/kubernetes/wait.go
new file mode 100644
index 0000000..f333b78
--- /dev/null
+++ b/pkg/util/kubernetes/wait.go
@@ -0,0 +1,39 @@
+package kubernetes
+
+import (
+ "time"
+ "github.com/pkg/errors"
+ "k8s.io/apimachinery/pkg/runtime"
+ "github.com/operator-framework/operator-sdk/pkg/sdk"
+)
+
+type ResourceRetrieveFunction func()(interface{}, error)
+
+type ResourceCheckFunction func(interface{})(bool, error)
+
+const (
+ sleepTime = 400 * time.Millisecond
+)
+
+func WaitCondition(obj runtime.Object, condition ResourceCheckFunction, maxDuration time.Duration) error {
+ start := time.Now()
+
+ for start.Add(maxDuration).After(time.Now()) {
+ err := sdk.Get(obj)
+ if err != nil {
+ time.Sleep(sleepTime)
+ continue
+ }
+
+ satisfied, err := condition(obj)
+ if err != nil {
+ return errors.Wrap(err, "error while evaluating condition")
+ } else if !satisfied {
+ time.Sleep(sleepTime)
+ continue
+ }
+
+ return nil
+ }
+ return errors.New("timeout while waiting condition")
+}
\ No newline at end of file
diff --git a/pkg/util/openshift/register.go b/pkg/util/openshift/register.go
new file mode 100644
index 0000000..076db9f
--- /dev/null
+++ b/pkg/util/openshift/register.go
@@ -0,0 +1,77 @@
+/*
+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.
+*/
+
+// Register all Openshift types that we want to manage.
+package openshift
+
+import (
+ apps "github.com/openshift/api/apps/v1"
+ authorization "github.com/openshift/api/authorization/v1"
+ build "github.com/openshift/api/build/v1"
+ image "github.com/openshift/api/image/v1"
+ route "github.com/openshift/api/route/v1"
+ template "github.com/openshift/api/template/v1"
+ "github.com/operator-framework/operator-sdk/pkg/util/k8sutil"
+ "github.com/sirupsen/logrus"
+ "k8s.io/apimachinery/pkg/runtime"
+)
+
+func init() {
+ k8sutil.AddToSDKScheme(AddToScheme)
+}
+
+var (
+ AddToScheme = addKnownTypes
+)
+
+type registerFunction func(*runtime.Scheme) error
+
+func addKnownTypes(scheme *runtime.Scheme) error {
+
+ var err error
+
+ // Standardized groups
+ err = doAdd(apps.AddToScheme, scheme, err)
+ err = doAdd(template.AddToScheme, scheme, err)
+ err = doAdd(image.AddToScheme, scheme, err)
+ err = doAdd(route.AddToScheme, scheme, err)
+ err = doAdd(build.AddToScheme, scheme, err)
+ err = doAdd(authorization.AddToScheme, scheme, err)
+
+
+ // Legacy "oapi" resources
+ err = doAdd(apps.AddToSchemeInCoreGroup, scheme, err)
+ err = doAdd(template.AddToSchemeInCoreGroup, scheme, err)
+ err = doAdd(image.AddToSchemeInCoreGroup, scheme, err)
+ err = doAdd(route.AddToSchemeInCoreGroup, scheme, err)
+ err = doAdd(build.AddToSchemeInCoreGroup, scheme, err)
+ err = doAdd(authorization.AddToSchemeInCoreGroup, scheme, err)
+
+ return err
+}
+
+func doAdd(addToScheme registerFunction, scheme *runtime.Scheme, err error) error {
+ callErr := addToScheme(scheme)
+ if callErr != nil {
+ logrus.Error("Error while registering Openshift types", callErr)
+ }
+
+ if err == nil {
+ return callErr
+ }
+ return err
+}
\ No newline at end of file