Add base image creation support.
diff --git a/pkg/cmd/local.go b/pkg/cmd/local.go
index 89b7740..af04dad 100644
--- a/pkg/cmd/local.go
+++ b/pkg/cmd/local.go
@@ -50,6 +50,7 @@
}
localCmd.AddCommand(cmdOnly(newCmdLocalRun(options)))
+ localCmd.AddCommand(cmdOnly(newCmdLocalCreate(options)))
return nil
}
diff --git a/pkg/cmd/local_create.go b/pkg/cmd/local_create.go
new file mode 100644
index 0000000..53e6451
--- /dev/null
+++ b/pkg/cmd/local_create.go
@@ -0,0 +1,97 @@
+/*
+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 cmd
+
+import (
+ "fmt"
+
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+func newCmdLocalCreate(rootCmdOptions *RootCmdOptions) (*cobra.Command, *localCreateCmdOptions) {
+ options := localCreateCmdOptions{
+ RootCmdOptions: rootCmdOptions,
+ }
+
+ cmd := cobra.Command{
+ Use: "create [options]",
+ Short: "Create integration images locally.",
+ Long: `Create integration images locally for containerized integrations.`,
+ PreRunE: decode(&options),
+ RunE: func(_ *cobra.Command, args []string) error {
+ if err := options.validate(args); err != nil {
+ return err
+ }
+ if err := options.init(); err != nil {
+ return err
+ }
+ if err := options.run(args); err != nil {
+ fmt.Println(err.Error())
+ }
+ if err := options.deinit(); err != nil {
+ return err
+ }
+
+ return nil
+ },
+ Annotations: map[string]string{
+ offlineCommandLabel: "true",
+ },
+ }
+
+ cmd.Flags().Bool("base-image", false, "Create base image used as a starting point for any integration.")
+ cmd.Flags().String("docker-registry", "", "Docker registry to store intermediate images.")
+
+ return &cmd, &options
+}
+
+type localCreateCmdOptions struct {
+ *RootCmdOptions
+ BaseImage bool `mapstructure:"base-image"`
+ DockerRegistry string `mapstructure:"docker-registry"`
+}
+
+func (command *localCreateCmdOptions) validate(args []string) error {
+ // If containerize is set then docker registry must be set.
+ if command.BaseImage && command.DockerRegistry == "" {
+ return errors.New("base image cannot be created as no registry has been provided")
+ }
+
+ return nil
+}
+
+func (command *localCreateCmdOptions) init() error {
+ return createDockerBaseWorkingDirectory()
+}
+
+func (command *localCreateCmdOptions) run(args []string) error {
+ // Create the Dockerfile and build the base image.
+ if command.BaseImage {
+ err := createAndBuildBaseImage(command.DockerRegistry)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (command *localCreateCmdOptions) deinit() error {
+ return deleteDockerBaseWorkingDirectory()
+}
diff --git a/pkg/cmd/local_run.go b/pkg/cmd/local_run.go
index a05968d..ca34eed 100644
--- a/pkg/cmd/local_run.go
+++ b/pkg/cmd/local_run.go
@@ -111,8 +111,28 @@
return nil
}
- // Run the integration locally.
- err = RunLocalIntegration(command.PropertyFiles, dependencies, args)
+ // Run integration inside a local container.
+ if command.Containerize {
+ // Get base image name.
+
+ // Assemble Dockerfile for containerized run.
+ // - copy properties
+ // - copy dependencies
+ // - copy sources
+ // = assemble run command: GetIntegrationRunCommand
+
+ // Build container image.
+
+ // Run container image.
+
+ return nil
+ }
+
+ // Get integration run command.
+ cmd := GetIntegrationRunCommand(command.PropertyFiles, dependencies, args)
+
+ // Run integration locally.
+ err = cmd.Run()
if err != nil {
return nil
}
diff --git a/pkg/cmd/util_commands.go b/pkg/cmd/util_commands.go
index 4819935..bc377ab 100644
--- a/pkg/cmd/util_commands.go
+++ b/pkg/cmd/util_commands.go
@@ -65,8 +65,8 @@
return strings.Join(classpathContents, ":")
}
-// RunLocalIntegration --
-func RunLocalIntegration(properties []string, dependencies []string, routes []string) error {
+// GetIntegrationRunCommand --
+func GetIntegrationRunCommand(properties []string, dependencies []string, routes []string) *exec.Cmd {
// Create classpath value.
classpathValue := assembleClasspatchArgValue(properties, dependencies, routes)
@@ -83,13 +83,13 @@
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
- fmt.Printf("executing: %s", strings.Join(cmd.Args, " "))
-
// Add directory where the properties file resides.
cmd.Env = append(cmd.Env, "CAMEL_K_CONF_D="+getPropertiesDir())
// Add files to the command line under the CAMEL_K_ROUTES flag.
cmd.Env = append(cmd.Env, "CAMEL_K_ROUTES="+strings.Join(formatRoutes(routes), ","))
- return cmd.Run()
+ fmt.Printf("executing: %s", strings.Join(cmd.Args, " "))
+
+ return cmd
}
diff --git a/pkg/cmd/util_containerization.go b/pkg/cmd/util_containerization.go
new file mode 100644
index 0000000..cc8780c
--- /dev/null
+++ b/pkg/cmd/util_containerization.go
@@ -0,0 +1,74 @@
+/*
+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 cmd
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "strings"
+
+ "github.com/apache/camel-k/pkg/util/docker"
+ "github.com/pkg/errors"
+)
+
+func createDockerBaseWorkingDirectory() error {
+ // Create local docker base directory.
+ temporaryDirectory, err := ioutil.TempDir(os.TempDir(), "docker-base-")
+ if err != nil {
+ return err
+ }
+
+ // Set the Docker base directory to the default value.
+ docker.BaseWorkingDirectory = temporaryDirectory
+
+ return nil
+}
+
+func deleteDockerBaseWorkingDirectory() error {
+ // Remove directory used for computing the dependencies.
+ defer os.RemoveAll(docker.BaseWorkingDirectory)
+
+ return nil
+}
+
+func createAndBuildBaseImage(dockerRegistry string) error {
+ // Set docker registry.
+ docker.RegistryName = dockerRegistry
+
+ // Create the base image Docker file.
+ err := docker.CreateBaseImageDockerFile()
+ if err != nil {
+ return err
+ }
+
+ // Get the Docker command arguments for building the base image and create the command.
+ args := docker.BuildBaseImageArgs()
+ cmd := exec.CommandContext(ctx, "docker", args...)
+
+ // Output executed command.
+ fmt.Printf("Executing: " + strings.Join(cmd.Args, " "))
+
+ // Run the command.
+ if err := cmd.Run(); err != nil {
+ errors.Errorf("base image containerization did not run successfully: %v", err)
+ }
+
+ return nil
+}
diff --git a/pkg/util/docker/docker.go b/pkg/util/docker/docker.go
new file mode 100644
index 0000000..ec38e47
--- /dev/null
+++ b/pkg/util/docker/docker.go
@@ -0,0 +1,54 @@
+/*
+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 docker
+
+import (
+ "path"
+ "strings"
+
+ "github.com/apache/camel-k/pkg/util"
+)
+
+// CreateBaseImageDockerFile --
+func CreateBaseImageDockerFile() error {
+ dockerFile := []string{}
+
+ // Base image is a java-only image since the integration command is just a java command.
+ dockerFile = append(dockerFile, FROM("adoptopenjdk/openjdk11:alpine"))
+
+ // Ensure Maven is already installed.
+ dockerFile = append(dockerFile, RUNMavenInstall())
+
+ // Write <base-work-dir>/Dockerfile
+ baseDockerFilePath := path.Join(BaseWorkingDirectory, "Dockerfile")
+ err := util.WriteToFile(baseDockerFilePath, strings.Join(dockerFile, "\n"))
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// BuildBaseImageArgs --
+func BuildBaseImageArgs() []string {
+ // Construct the docker command:
+ //
+ // docker build -f <BaseWorkingDirectory>/Dockerfile -t <dockerRegistry>/<BaseImageName>
+ //
+ return BuildImageArgs(BaseWorkingDirectory, BaseImageName, BaseWorkingDirectory)
+}
diff --git a/pkg/util/docker/docker_base.go b/pkg/util/docker/docker_base.go
new file mode 100644
index 0000000..de7c3f7
--- /dev/null
+++ b/pkg/util/docker/docker_base.go
@@ -0,0 +1,228 @@
+/*
+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 docker
+
+import (
+ "path"
+ "strings"
+)
+
+// RegistryName -- the docker registry name.
+var RegistryName = ""
+
+// BaseImageName -- base image name.
+var BaseImageName string = "integration-base-image"
+
+// BaseWorkingDirectory -- directory used by Docker to construct the base image.
+var BaseWorkingDirectory string = ""
+
+// Internal variables.
+var (
+ dockerEndpointSeparator = "/"
+ containerFileSeparator = "/"
+ latestTag = "latest"
+)
+
+// BuildImageArgs - standard docker build arguments.
+func BuildImageArgs(dockerFileDir string, imageName string, sourceDir string) []string {
+ // Construct the docker command:
+ //
+ // docker build -f <docker-file> -t <image-name> <source-directory>
+ //
+ args := make([]string, 0)
+ args = append(args, "build")
+
+ // Add path to Dockerfile:
+ dockerFile := path.Join(dockerFileDir, "Dockerfile")
+ args = append(args, DockerfilePathArg(dockerFile)...)
+
+ // Image name:
+ args = append(args, ImageArg(imageName, "")...)
+
+ // Root of source directory.
+ if sourceDir != "" {
+ args = append(args, sourceDir)
+ }
+
+ return args
+}
+
+// RunImageArgs -- standard docker run arguments.
+func RunImageArgs(imageName string, imageTag string) []string {
+ // Construct the docker command:
+ //
+ // docker run --network="host" <image-name>:<tag>
+ //
+ // TODO: support other types of network connections.
+ args := make([]string, 0)
+ args = append(args, "run")
+
+ // TODO: support other networks.
+ args = append(args, "--network=host")
+
+ // Path to Docker image:
+ args = append(args, ImageArg(imageName, imageTag)...)
+
+ return args
+}
+
+//
+// Arguments to docker command line.
+//
+
+// DockerfilePathArg --
+func DockerfilePathArg(dockerfilePath string) []string {
+ args := make([]string, 0)
+ args = append(args, "-f")
+ args = append(args, dockerfilePath)
+ return args
+}
+
+// ImageArg --
+func ImageArg(dockerImageName string, tag string) []string {
+ args := make([]string, 0)
+ args = append(args, "-t")
+ args = append(args, GetFullDockerImage(dockerImageName, tag))
+ return args
+}
+
+// LatestImageArg --
+func LatestImageArg(dockerImageName string) []string {
+ args := make([]string, 0)
+ args = append(args, "-t")
+ args = append(args, GetFullDockerImage(dockerImageName, latestTag))
+ return args
+}
+
+//
+// Docker-spcific helper functions.
+//
+
+// GetImage - <image-name>:<tag>
+func GetImage(dockerImageName string, tag string) string {
+ image := make([]string, 0)
+ image = append(image, dockerImageName)
+ image = append(image, tag)
+ return strings.Join(image, ":")
+}
+
+// GetLatestImage - <image-name>:latest
+func GetLatestImage(dockerImageName string) string {
+ return GetImage(dockerImageName, latestTag)
+}
+
+// GetFullDockerImage - <docker-registry>/<image-name>:<tag>
+func GetFullDockerImage(dockerImageName string, tag string) string {
+ fullImagePath := make([]string, 0)
+ fullImagePath = append(fullImagePath, RegistryName)
+ if tag == "" {
+ fullImagePath = append(fullImagePath, dockerImageName)
+ } else {
+ fullImagePath = append(fullImagePath, GetImage(dockerImageName, tag))
+ }
+
+ return strings.Join(fullImagePath, dockerEndpointSeparator)
+}
+
+//
+// Container file management.
+//
+
+// JoinPath -- for container paths.
+func JoinPath(lhsPath string, rhsPath string) string {
+ p := []string{lhsPath, rhsPath}
+ return strings.Join(p, containerFileSeparator)
+}
+
+//
+// Docker syntax functions.
+//
+
+// Generic commands.
+
+// COPY --
+func COPY(from string, to string) string {
+ c := []string{"COPY", from, to}
+ return strings.Join(c, " ")
+}
+
+// RUN --
+func RUN(command string) string {
+ c := []string{"RUN", command}
+ return strings.Join(c, " ")
+}
+
+// FROM --
+func FROM(imageName string) string {
+ c := []string{"FROM", imageName}
+ return strings.Join(c, " ")
+}
+
+// WORKDIR --
+func WORKDIR(workDir string) string {
+ c := []string{"WORKDIR", workDir}
+ return strings.Join(c, " ")
+}
+
+// ENV --
+func ENV(envVar string, value string) string {
+ p := []string{envVar, value}
+ c := []string{"ENV", strings.Join(p, "=")}
+ return strings.Join(c, " ")
+}
+
+// AS --
+func AS(image string, alias string) string {
+ c := []string{image, "as", alias}
+ return strings.Join(c, " ")
+}
+
+// CMD --
+func CMD(command string) string {
+ c := []string{"CMD", command}
+ return strings.Join(c, " ")
+}
+
+// // COPYFromBuilder --
+// func COPYFromBuilder(from string, to string) string {
+// flag := []string{"--from", internalBuilderImageName}
+// newFrom := []string{strings.Join(flag, "="), from}
+// return COPY(strings.Join(newFrom, " "), to)
+// }
+
+// RUNMavenInstall --
+func RUNMavenInstall() string {
+ return RUN("apk add --update maven && apk update && apk upgrade")
+}
+
+// RUNMakeDir --
+func RUNMakeDir(dirName string) string {
+ c := []string{"mkdir", "-p", dirName}
+ return RUN(strings.Join(c, " "))
+}
+
+// ENVAppend --
+func ENVAppend(envVar string, value string) string {
+ tail := []string{value, "$" + envVar}
+ return ENV(envVar, strings.Join(tail, ":"))
+}
+
+// CMDShellWrap --
+func CMDShellWrap(command string) string {
+ return CMD("/bin/sh -c \"" + command + "\"")
+}
diff --git a/pkg/util/util.go b/pkg/util/util.go
index 30ff77b..5e47e2d 100644
--- a/pkg/util/util.go
+++ b/pkg/util/util.go
@@ -23,6 +23,7 @@
"encoding/xml"
"fmt"
"io"
+ "io/ioutil"
"os"
"path"
"regexp"
@@ -74,6 +75,7 @@
return false
}
+// StringSliceContainsAnyOf --
func StringSliceContainsAnyOf(slice []string, items ...string) bool {
for i := 0; i < len(slice); i++ {
for j := 0; j < len(items); j++ {
@@ -325,3 +327,14 @@
return yamldata, nil
}
+
+// WriteToFile --
+func WriteToFile(filePath string, fileContents string) error {
+ err := ioutil.WriteFile(filePath, []byte(fileContents), 0777)
+ if err != nil {
+ return errors.Errorf("error writing file: %v", filePath)
+ }
+
+ // All went well, return true.
+ return nil
+}