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 0047930e30b2429af3b2540d70da17d99f4b09c9 Author: nferraro <ni.ferr...@gmail.com> AuthorDate: Fri Sep 21 11:25:41 2018 +0200 Refactored build module into assembler and publisher --- Gopkg.lock | 9 + pkg/build/assemble/doc.go | 19 +++ pkg/build/assemble/maven_assembler.go | 171 +++++++++++++++++++ .../maven_assembler_test.go} | 2 +- pkg/build/build_manager.go | 87 ++++++++-- pkg/build/build_types.go | 25 ++- pkg/build/publish/doc.go | 19 +++ .../local_builder.go => publish/s2i_publisher.go} | 183 ++++++++------------- pkg/stub/action/context/build.go | 8 +- pkg/util/maven/maven.go | 112 ++----------- pkg/util/tar/appender.go | 106 ++++++++++++ test/build_manager_integration_test.go | 15 +- test/local_builder_integration_test.go | 107 ------------ 13 files changed, 513 insertions(+), 350 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 4c8e50f..0975b14 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -493,6 +493,14 @@ version = "v0.9.1" [[projects]] + branch = "v1" + digest = "1:52ffb9db0286de37253a5098607cfbcfcdc94e51e8c226da120513df82adab0c" + name = "gopkg.in/yaml.v1" + packages = ["."] + pruneopts = "NUT" + revision = "9f9df34309c04878acc86042b16630b0f696e1de" + +[[projects]] digest = "1:7c95b35057a0ff2e19f707173cc1a947fa43a6eb5c4d300d196ece0334046082" name = "gopkg.in/yaml.v2" packages = ["."] @@ -733,6 +741,7 @@ "github.com/spf13/cobra", "github.com/stoewer/go-strcase", "github.com/stretchr/testify/assert", + "gopkg.in/yaml.v1", "gopkg.in/yaml.v2", "k8s.io/api/apps/v1", "k8s.io/api/core/v1", diff --git a/pkg/build/assemble/doc.go b/pkg/build/assemble/doc.go new file mode 100644 index 0000000..20b1d42 --- /dev/null +++ b/pkg/build/assemble/doc.go @@ -0,0 +1,19 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package assemble contains tools that convert source files and dependencies into the integration classpath +package assemble diff --git a/pkg/build/assemble/maven_assembler.go b/pkg/build/assemble/maven_assembler.go new file mode 100644 index 0000000..9094181 --- /dev/null +++ b/pkg/build/assemble/maven_assembler.go @@ -0,0 +1,171 @@ +/* +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 assemble + +import ( + "context" + "encoding/xml" + "fmt" + "strings" + "time" + + "github.com/apache/camel-k/pkg/build" + "github.com/apache/camel-k/pkg/util/maven" + "github.com/sirupsen/logrus" + + // import openshift utilities + _ "github.com/apache/camel-k/pkg/util/openshift" + "github.com/apache/camel-k/version" +) + +type mavenAssembler struct { + buffer chan assembleOperation +} + +type assembleOperation struct { + request build.Request + output chan build.AssembledOutput +} + +// NewMavenAssembler create a new builder +func NewMavenAssembler(ctx context.Context) build.Assembler { + assembler := mavenAssembler{ + buffer: make(chan assembleOperation, 100), + } + go assembler.assembleCycle(ctx) + return &assembler +} + +func (b *mavenAssembler) Assemble(request build.Request) <-chan build.AssembledOutput { + res := make(chan build.AssembledOutput, 1) + op := assembleOperation{ + request: request, + output: res, + } + b.buffer <- op + return res +} + +func (b *mavenAssembler) assembleCycle(ctx context.Context) { + for { + select { + case <-ctx.Done(): + b.buffer = nil + return + case op := <-b.buffer: + now := time.Now() + logrus.Info("Starting new Maven build") + res := b.execute(&op.request) + elapsed := time.Now().Sub(now) + + if res.Error != nil { + logrus.Error("Error during Maven build (total time ", elapsed.Seconds(), " seconds): ", res.Error) + } else { + logrus.Info("Maven build completed in ", elapsed.Seconds(), " seconds") + } + + op.output <- res + } + } +} + +func (b *mavenAssembler) execute(request *build.Request) build.AssembledOutput { + project, err := generateProject(request) + if err != nil { + return build.AssembledOutput{ + Error: err, + } + } + + res, err := maven.Process(project) + if err != nil { + return build.AssembledOutput{ + Error: err, + } + } + + output := build.AssembledOutput{ + Classpath: make([]build.ClasspathEntry, 0, len(res.Classpath)), + } + for _, e := range res.Classpath { + output.Classpath = append(output.Classpath, build.ClasspathEntry{ + ID: e.ID, + Location: e.Location, + }) + } + + return output +} + +func generateProject(source *build.Request) (maven.Project, error) { + project := maven.Project{ + XMLName: xml.Name{Local: "project"}, + XMLNs: "http://maven.apache.org/POM/4.0.0", + XMLNsXsi: "http://www.w3.org/2001/XMLSchema-instance", + XsiSchemaLocation: "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd", + ModelVersion: "4.0.0", + GroupID: "org.apache.camel.k.integration", + ArtifactID: "camel-k-integration", + Version: version.Version, + DependencyManagement: maven.DependencyManagement{ + Dependencies: maven.Dependencies{ + Dependencies: []maven.Dependency{ + { + //TODO: camel version should be retrieved from an external request or provided as static version + GroupID: "org.apache.camel", + ArtifactID: "camel-bom", + Version: "2.22.1", + Type: "pom", + Scope: "import", + }, + }, + }, + }, + Dependencies: maven.Dependencies{ + Dependencies: make([]maven.Dependency, 0), + }, + } + + // + // set-up dependencies + // + + deps := &project.Dependencies + deps.AddGAV("org.apache.camel.k", "camel-k-runtime-jvm", version.Version) + + for _, d := range source.Dependencies { + if strings.HasPrefix(d, "camel:") { + artifactID := strings.TrimPrefix(d, "camel:") + + if !strings.HasPrefix(artifactID, "camel-") { + artifactID = "camel-" + artifactID + } + + deps.AddGAV("org.apache.camel", artifactID, "") + } else if strings.HasPrefix(d, "mvn:") { + mid := strings.TrimPrefix(d, "mvn:") + gav := strings.Replace(mid, "/", ":", -1) + + deps.AddEncodedGAV(gav) + } else { + return maven.Project{}, fmt.Errorf("unknown dependency type: %s", d) + } + } + + return project, nil +} diff --git a/pkg/build/local/local_builder_test.go b/pkg/build/assemble/maven_assembler_test.go similarity index 99% rename from pkg/build/local/local_builder_test.go rename to pkg/build/assemble/maven_assembler_test.go index ddb928e..20de3d8 100644 --- a/pkg/build/local/local_builder_test.go +++ b/pkg/build/assemble/maven_assembler_test.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package local +package assemble import ( "testing" diff --git a/pkg/build/build_manager.go b/pkg/build/build_manager.go index 50d1187..895575b 100644 --- a/pkg/build/build_manager.go +++ b/pkg/build/build_manager.go @@ -19,23 +19,24 @@ package build import ( "context" + "errors" "sync" ) // Manager represent the main facade to the image build system type Manager struct { - builds sync.Map ctx context.Context - namespace string - builder Builder + builds sync.Map + assembler Assembler + publisher Publisher } // NewManager creates an instance of the build manager using the given builder -func NewManager(ctx context.Context, namespace string, builder Builder) *Manager { +func NewManager(ctx context.Context, assembler Assembler, publisher Publisher) *Manager { return &Manager{ ctx: ctx, - namespace: namespace, - builder: builder, + assembler: assembler, + publisher: publisher, } } @@ -46,18 +47,41 @@ func (m *Manager) Get(identifier Identifier) Result { return noBuildInfo() } - return *info.(*Result) + return info.(Result) } // Start starts a new build -func (m *Manager) Start(source Request) { - initialBuildInfo := initialBuildInfo(&source) - m.builds.Store(source.Identifier, &initialBuildInfo) +func (m *Manager) Start(request Request) { + m.builds.Store(request.Identifier, initialBuildInfo(request)) - resChannel := m.builder.Build(source) + assembleChannel := m.assembler.Assemble(request) go func() { - res := <-resChannel - m.builds.Store(res.Request.Identifier, &res) + var assembled AssembledOutput + select { + case <-m.ctx.Done(): + m.builds.Store(request.Identifier, canceledBuildInfo(request)) + return + case assembled = <-assembleChannel: + if assembled.Error != nil { + m.builds.Store(request.Identifier, failedAssembleBuildInfo(request, assembled)) + return + } + } + + publishChannel := m.publisher.Publish(request, assembled) + var published PublishedOutput + select { + case <-m.ctx.Done(): + m.builds.Store(request.Identifier, canceledBuildInfo(request)) + return + case published = <-publishChannel: + if published.Error != nil { + m.builds.Store(request.Identifier, failedPublishBuildInfo(request, published)) + return + } + } + + m.builds.Store(request.Identifier, completeResult(request, assembled, published)) }() } @@ -67,9 +91,42 @@ func noBuildInfo() Result { } } -func initialBuildInfo(source *Request) Result { +func initialBuildInfo(request Request) Result { return Result{ - Request: source, + Request: request, Status: StatusStarted, } } + +func canceledBuildInfo(request Request) Result { + return Result{ + Request: request, + Error: errors.New("build canceled"), + Status: StatusError, + } +} + +func failedAssembleBuildInfo(request Request, output AssembledOutput) Result { + return Result{ + Request: request, + Error: output.Error, + Status: StatusError, + } +} + +func failedPublishBuildInfo(request Request, output PublishedOutput) Result { + return Result{ + Request: request, + Error: output.Error, + Status: StatusError, + } +} + +func completeResult(request Request, a AssembledOutput, p PublishedOutput) Result { + return Result{ + Request: request, + Status: StatusCompleted, + Classpath: a.Classpath, + Image: p.Image, + } +} diff --git a/pkg/build/build_types.go b/pkg/build/build_types.go index dc7c787..22ff178 100644 --- a/pkg/build/build_types.go +++ b/pkg/build/build_types.go @@ -39,7 +39,7 @@ type Source struct { // Result represents the result of a build type Result struct { - Request *Request + Request Request Status Status Image string Error error @@ -52,9 +52,26 @@ type ClasspathEntry struct { Location string `json:"location,omitempty" yaml:"location,omitempty"` } -// Builder is supertype of all builders -type Builder interface { - Build(Request) <-chan Result +// AssembledOutput represents the output of the assemble phase +type AssembledOutput struct { + Error error + Classpath []ClasspathEntry +} + +// A Assembler can be used to compute the classpath of a integration context +type Assembler interface { + Assemble(Request) <-chan AssembledOutput +} + +// PublishedOutput is the output of the publish phase +type PublishedOutput struct { + Error error + Image string +} + +// A Publisher publishes a docker image of a build request +type Publisher interface { + Publish(Request, AssembledOutput) <-chan PublishedOutput } // Status -- diff --git a/pkg/build/publish/doc.go b/pkg/build/publish/doc.go new file mode 100644 index 0000000..19343a8 --- /dev/null +++ b/pkg/build/publish/doc.go @@ -0,0 +1,19 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package publish contains strategies for publishing integrations into a Docker registries +package publish diff --git a/pkg/build/local/local_builder.go b/pkg/build/publish/s2i_publisher.go similarity index 58% rename from pkg/build/local/local_builder.go rename to pkg/build/publish/s2i_publisher.go index 232360b..5548194 100644 --- a/pkg/build/local/local_builder.go +++ b/pkg/build/publish/s2i_publisher.go @@ -15,67 +15,66 @@ See the License for the specific language governing permissions and limitations under the License. */ -package local +package publish import ( "context" - "encoding/xml" - "fmt" - "io/ioutil" - "strings" - "time" - + "github.com/apache/camel-k/pkg/build" + "github.com/apache/camel-k/pkg/util/kubernetes" + "github.com/apache/camel-k/pkg/util/kubernetes/customclient" + "github.com/apache/camel-k/pkg/util/maven" + "github.com/apache/camel-k/pkg/util/tar" buildv1 "github.com/openshift/api/build/v1" imagev1 "github.com/openshift/api/image/v1" "github.com/operator-framework/operator-sdk/pkg/sdk" "github.com/operator-framework/operator-sdk/pkg/util/k8sutil" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "io/ioutil" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "path" + "time" +) - "github.com/apache/camel-k/pkg/build" - "github.com/apache/camel-k/pkg/util/kubernetes" - "github.com/apache/camel-k/pkg/util/kubernetes/customclient" - "github.com/apache/camel-k/pkg/util/maven" - - // import openshift utilities - _ "github.com/apache/camel-k/pkg/util/openshift" - "github.com/apache/camel-k/version" +const ( + artifactDirPrefix = "s2i-" ) -type localBuilder struct { - buffer chan buildOperation +type s2iPublisher struct { + buffer chan publishOperation namespace string } -type buildOperation struct { - request build.Request - output chan build.Result +type publishOperation struct { + request build.Request + assembled build.AssembledOutput + output chan build.PublishedOutput } -// NewLocalBuilder create a new builder -func NewLocalBuilder(ctx context.Context, namespace string) build.Builder { - builder := localBuilder{ - buffer: make(chan buildOperation, 100), +// NewS2IPublisher creates a new publisher doing a Openshift S2I binary build +func NewS2IPublisher(ctx context.Context, namespace string) build.Publisher { + publisher := s2iPublisher{ + buffer: make(chan publishOperation, 100), namespace: namespace, } - go builder.buildCycle(ctx) - return &builder + go publisher.publishCycle(ctx) + return &publisher } -func (b *localBuilder) Build(request build.Request) <-chan build.Result { - res := make(chan build.Result, 1) - op := buildOperation{ - request: request, - output: res, +func (b *s2iPublisher) Publish(request build.Request, assembled build.AssembledOutput) <-chan build.PublishedOutput { + res := make(chan build.PublishedOutput, 1) + op := publishOperation{ + request: request, + assembled: assembled, + output: res, } b.buffer <- op return res } -func (b *localBuilder) buildCycle(ctx context.Context) { +func (b *s2iPublisher) publishCycle(ctx context.Context) { for { select { case <-ctx.Done(): @@ -83,14 +82,14 @@ func (b *localBuilder) buildCycle(ctx context.Context) { return case op := <-b.buffer: now := time.Now() - logrus.Info("Starting new build") - res := b.execute(&op.request) + logrus.Info("Starting a new image publication") + res := b.execute(op.request, op.assembled) elapsed := time.Now().Sub(now) if res.Error != nil { - logrus.Error("Error during build (total time ", elapsed.Seconds(), " seconds): ", res.Error) + logrus.Error("Error during publication (total time ", elapsed.Seconds(), " seconds): ", res.Error) } else { - logrus.Info("Process completed in ", elapsed.Seconds(), " seconds") + logrus.Info("Publication completed in ", elapsed.Seconds(), " seconds") } op.output <- res @@ -98,43 +97,22 @@ func (b *localBuilder) buildCycle(ctx context.Context) { } } -func (b *localBuilder) execute(request *build.Request) build.Result { - project, err := generateProject(request) +func (b *s2iPublisher) execute(request build.Request, assembled build.AssembledOutput) build.PublishedOutput { + tarFile, err := b.createTar(assembled) if err != nil { - return build.Result{ - Error: err, - Status: build.StatusError, - } + return build.PublishedOutput{Error: err} } - res, err := maven.Process(project) + image, err := b.publish(tarFile, request) if err != nil { - return build.Result{ - Error: err, - Status: build.StatusError, - } + return build.PublishedOutput{Error: err} } - logrus.Info("Created tar file ", res.TarFilePath) - - image, err := b.publish(res.TarFilePath, request) - if err != nil { - return build.Result{ - Error: errors.Wrap(err, "could not publish docker image"), - Status: build.StatusError, - } - } - - return build.Result{ - Request: request, - Image: image, - Error: nil, - Status: build.StatusCompleted, - Classpath: res.Classpath, - } + return build.PublishedOutput{Image: image} } -func (b *localBuilder) publish(tarFile string, source *build.Request) (string, error) { +func (b *s2iPublisher) publish(tarFile string, source build.Request) (string, error) { + bc := buildv1.BuildConfig{ TypeMeta: metav1.TypeMeta{ APIVersion: buildv1.SchemeGroupVersion.String(), @@ -257,60 +235,39 @@ func (b *localBuilder) publish(tarFile string, source *build.Request) (string, e return is.Status.DockerImageRepository + ":" + source.Identifier.Qualifier, nil } -func generateProject(source *build.Request) (maven.Project, error) { - project := maven.Project{ - XMLName: xml.Name{Local: "project"}, - XMLNs: "http://maven.apache.org/POM/4.0.0", - XMLNsXsi: "http://www.w3.org/2001/XMLSchema-instance", - XsiSchemaLocation: "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd", - ModelVersion: "4.0.0", - GroupID: "org.apache.camel.k.integration", - ArtifactID: "camel-k-integration", - Version: version.Version, - DependencyManagement: maven.DependencyManagement{ - Dependencies: maven.Dependencies{ - Dependencies: []maven.Dependency{ - { - //TODO: camel version should be retrieved from an external request or provided as static version - GroupID: "org.apache.camel", - ArtifactID: "camel-bom", - Version: "2.22.1", - Type: "pom", - Scope: "import", - }, - }, - }, - }, - Dependencies: maven.Dependencies{ - Dependencies: make([]maven.Dependency, 0), - }, +func (b *s2iPublisher) createTar(assembled build.AssembledOutput) (string, error) { + artifactDir, err := ioutil.TempDir("", artifactDirPrefix) + if err != nil { + return "", errors.Wrap(err, "could not create temporary dir for s2i artifacts") } - // - // set-up dependencies - // - - deps := &project.Dependencies - deps.AddGAV("org.apache.camel.k", "camel-k-runtime-jvm", version.Version) + tarFileName := path.Join(artifactDir, "occi.tar") + tarAppender, err := tar.NewAppender(tarFileName) + if err != nil { + return "", err + } + defer tarAppender.Close() - for _, d := range source.Dependencies { - if strings.HasPrefix(d, "camel:") { - artifactID := strings.TrimPrefix(d, "camel:") + cp := "" + for _, entry := range assembled.Classpath { + gav, err := maven.ParseGAV(entry.ID) + if err != nil { + return "", nil + } - if !strings.HasPrefix(artifactID, "camel-") { - artifactID = "camel-" + artifactID - } + tarPath := path.Join("dependencies/", gav.GroupID) + fileName, err := tarAppender.AddFile(entry.Location, tarPath) + if err != nil { + return "", err + } - deps.AddGAV("org.apache.camel", artifactID, "") - } else if strings.HasPrefix(d, "mvn:") { - mid := strings.TrimPrefix(d, "mvn:") - gav := strings.Replace(mid, "/", ":", -1) + cp += fileName + "\n" + } - deps.AddEncodedGAV(gav) - } else { - return maven.Project{}, fmt.Errorf("unknown dependency type: %s", d) - } + err = tarAppender.AppendData([]byte(cp), "classpath") + if err != nil { + return "", err } - return project, nil + return tarFileName, nil } diff --git a/pkg/stub/action/context/build.go b/pkg/stub/action/context/build.go index 9d5d3b1..a815c7c 100644 --- a/pkg/stub/action/context/build.go +++ b/pkg/stub/action/context/build.go @@ -19,6 +19,8 @@ package action import ( "context" + "github.com/apache/camel-k/pkg/build/assemble" + "github.com/apache/camel-k/pkg/build/publish" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -27,13 +29,13 @@ import ( "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" "github.com/apache/camel-k/pkg/build" - "github.com/apache/camel-k/pkg/build/local" ) // NewIntegrationContextBuildAction creates a new build handling action for the context func NewIntegrationContextBuildAction(ctx context.Context, namespace string) IntegrationContextAction { - builder := local.NewLocalBuilder(ctx, namespace) - manager := build.NewManager(ctx, namespace, builder) + assembler := assemble.NewMavenAssembler(ctx) + publisher := publish.NewS2IPublisher(ctx, namespace) + manager := build.NewManager(ctx, assembler, publisher) return &integrationContextBuildAction{ buildManager: manager, diff --git a/pkg/util/maven/maven.go b/pkg/util/maven/maven.go index 1920be8..e2a9b35 100644 --- a/pkg/util/maven/maven.go +++ b/pkg/util/maven/maven.go @@ -18,11 +18,9 @@ limitations under the License. package maven import ( - "archive/tar" "bytes" "encoding/xml" "fmt" - "io" "io/ioutil" "os" "os/exec" @@ -32,7 +30,6 @@ import ( "github.com/apache/camel-k/version" - "github.com/apache/camel-k/pkg/build" "gopkg.in/yaml.v2" "github.com/pkg/errors" @@ -41,13 +38,18 @@ import ( const ( buildDirPrefix = "maven-" - artifactDirPrefix = "maven-bin-" + ) // BuildResult -- type BuildResult struct { - TarFilePath string - Classpath []build.ClasspathEntry + Classpath []ClasspathLibrary +} + +// ClasspathLibrary -- +type ClasspathLibrary struct { + ID string `json:"id" yaml:"id"` + Location string `json:"location,omitempty" yaml:"location,omitempty"` } // Process takes a project description and returns a binary tar with the built artifacts @@ -65,16 +67,7 @@ func Process(project Project) (BuildResult, error) { return res, errors.Wrap(err, "could not write maven source files") } err = runMavenBuild(buildDir, &res) - if err != nil { - return res, err - } - - res.TarFilePath, err = createTar(project, &res) - if err != nil { - return res, err - } - - return res, nil + return res, err } func runMavenBuild(buildDir string, result *BuildResult) error { @@ -95,7 +88,7 @@ func runMavenBuild(buildDir string, result *BuildResult) error { return err } - cp := make(map[string][]build.ClasspathEntry) + cp := make(map[string][]ClasspathLibrary) if err := yaml.Unmarshal(content, &cp); err != nil { return err } @@ -113,91 +106,6 @@ func mavenExtraOptions() string { return "-Dcamel.noop=true" } -func createTar(project Project, result *BuildResult) (string, error) { - artifactDir, err := ioutil.TempDir("", artifactDirPrefix) - if err != nil { - return "", errors.Wrap(err, "could not create temporary dir for maven artifacts") - } - - tarFileName := path.Join(artifactDir, project.ArtifactID+".tar") - tarFile, err := os.Create(tarFileName) - if err != nil { - return "", errors.Wrap(err, "cannot create tar file "+tarFileName) - } - defer tarFile.Close() - - writer := tar.NewWriter(tarFile) - defer writer.Close() - - cp := "" - for _, entry := range result.Classpath { - gav, err := ParseGAV(entry.ID) - if err != nil { - return "", nil - } - - tarPath := path.Join("dependencies/", gav.GroupID) - fileName, err := appendFileToTar(entry.Location, tarPath, writer) - if err != nil { - return "", err - } - - cp += fileName + "\n" - } - - err = appendDataToTar([]byte(cp), "classpath", writer) - if err != nil { - return "", err - } - - return tarFileName, nil -} - -func appendFileToTar(filePath string, tarPath string, writer *tar.Writer) (string, 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 fileName, nil -} - -func appendDataToTar(data []byte, tarPath string, writer *tar.Writer) error { - writer.WriteHeader(&tar.Header{ - Name: tarPath, - Size: int64(len(data)), - Mode: 0644, - }) - - _, err := writer.Write(data) - if err != nil { - return errors.Wrap(err, "cannot add data to the tar archive") - } - return nil -} - func createMavenStructure(buildDir string, project Project) error { pom, err := GeneratePomFileContent(project) if err != nil { diff --git a/pkg/util/tar/appender.go b/pkg/util/tar/appender.go new file mode 100644 index 0000000..3e4aebf --- /dev/null +++ b/pkg/util/tar/appender.go @@ -0,0 +1,106 @@ +/* +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 tar + +import ( + atar "archive/tar" + "github.com/pkg/errors" + "io" + "os" + "path" +) + +// Appender provides a high level abstraction over writing tar files +type Appender struct { + tarFile *os.File + writer *atar.Writer +} + +// NewAppender creates a new tar appender +func NewAppender(fileName string) (*Appender, error) { + tarFile, err := os.Create(fileName) + if err != nil { + return nil, errors.Wrap(err, "cannot create tar file "+fileName) + } + + writer := atar.NewWriter(tarFile) + appender := Appender{ + tarFile: tarFile, + writer: writer, + } + return &appender, nil +} + +// Close closes all handles managed by the appender +func (t *Appender) Close() error { + if err := t.writer.Close(); err != nil { + return err + } + if err := t.tarFile.Close(); err != nil { + return err + } + return nil +} + +// AddFile adds a file content to the tarDir, using the original file name. +// It returns the full path of the file inside the tar. +func (t *Appender) AddFile(filePath string, tarDir string) (string, error) { + info, err := os.Stat(filePath) + if err != nil { + return "", err + } + _, fileName := path.Split(filePath) + if tarDir != "" { + fileName = path.Join(tarDir, fileName) + } + + t.writer.WriteHeader(&atar.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(t.writer, file) + if err != nil { + return "", errors.Wrap(err, "cannot add file to the tar archive") + } + + 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 { + t.writer.WriteHeader(&atar.Header{ + Name: tarPath, + Size: int64(len(data)), + Mode: 0644, + }) + + _, err := t.writer.Write(data) + if 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 9ba22f0..fa7d8f8 100644 --- a/test/build_manager_integration_test.go +++ b/test/build_manager_integration_test.go @@ -23,7 +23,8 @@ package test import ( "context" - "github.com/apache/camel-k/pkg/build/local" + "github.com/apache/camel-k/pkg/build/assemble" + "github.com/apache/camel-k/pkg/build/publish" "testing" "time" @@ -35,9 +36,11 @@ import ( func TestBuildManagerBuild(t *testing.T) { ctx := context.TODO() namespace := getTargetNamespace() - buildManager := build.NewManager(ctx, namespace, local.NewLocalBuilder(ctx, namespace)) + assembler := assemble.NewMavenAssembler(ctx) + publisher := publish.NewS2IPublisher(ctx, namespace) + buildManager := build.NewManager(ctx, assembler, publisher) identifier := build.Identifier{ - Name: "man-test", + Name: "man-test", Qualifier: digest.Random(), } buildManager.Start(build.Request{ @@ -70,9 +73,11 @@ func TestBuildManagerFailedBuild(t *testing.T) { ctx := context.TODO() namespace := getTargetNamespace() - buildManager := build.NewManager(ctx, namespace, local.NewLocalBuilder(ctx, namespace)) + assembler := assemble.NewMavenAssembler(ctx) + publisher := publish.NewS2IPublisher(ctx, namespace) + buildManager := build.NewManager(ctx, assembler, publisher) identifier := build.Identifier{ - Name: "man-test-2", + Name: "man-test-2", Qualifier: digest.Random(), } buildManager.Start(build.Request{ diff --git a/test/local_builder_integration_test.go b/test/local_builder_integration_test.go deleted file mode 100644 index c3f35a2..0000000 --- a/test/local_builder_integration_test.go +++ /dev/null @@ -1,107 +0,0 @@ -// +build integration - -// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "integration" - -/* -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 test - -import ( - "context" - "testing" - - "github.com/apache/camel-k/pkg/build" - "github.com/apache/camel-k/pkg/util/digest" - "github.com/stretchr/testify/assert" - "github.com/apache/camel-k/pkg/build/local" -) - -func TestLocalBuild(t *testing.T) { - - ctx := context.TODO() - builder := local.NewLocalBuilder(ctx, getTargetNamespace()) - - execution := builder.Build(build.Request{ - Identifier: build.Identifier{ - Name: "test0", - Qualifier: digest.Random(), - }, - Code: build.Source{ - Content: createTimerToLogIntegrationCode(), - }, - }) - - res := <-execution - - assert.Nil(t, res.Error, "Build failed") -} - -func TestLocalDoubleBuild(t *testing.T) { - - ctx := context.TODO() - builder := local.NewLocalBuilder(ctx, getTargetNamespace()) - - execution1 := builder.Build(build.Request{ - Identifier: build.Identifier{ - Name: "test1", - Qualifier: digest.Random(), - }, - Code: build.Source{ - Content: createTimerToLogIntegrationCode(), - }, - }) - - execution2 := builder.Build(build.Request{ - Identifier: build.Identifier{ - Name: "test2", - Qualifier: digest.Random(), - }, - Code: build.Source{ - Content: createTimerToLogIntegrationCode(), - }, - }) - - res1 := <-execution1 - res2 := <-execution2 - - assert.Nil(t, res1.Error, "Build failed") - assert.Nil(t, res2.Error, "Build failed") -} - -func TestLocalFailedBuild(t *testing.T) { - - ctx := context.TODO() - builder := local.NewLocalBuilder(ctx, getTargetNamespace()) - - execution := builder.Build(build.Request{ - Identifier: build.Identifier{ - Name: "test3", - Qualifier: digest.Random(), - }, - Code: build.Source{ - Content: createTimerToLogIntegrationCode(), - }, - Dependencies: []string{ - "camel:cippalippa", - }, - }) - - res := <-execution - - assert.NotNil(t, res.Error, "Build should fail") -}