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

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

commit d2de352d01dd5491d3bdef37ce99d3a05cb737b7
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Fri Nov 26 12:36:55 2021 +0100

    chore(cmd/run): resource type package reorg
---
 pkg/cmd/run.go                                     |   7 +-
 pkg/cmd/run_help.go                                | 240 +++------------------
 pkg/cmd/run_help_test.go                           | 134 ------------
 pkg/util/resource/config.go                        | 234 ++++++++++++++++++++
 .../resource/config_test.go}                       | 150 +++----------
 5 files changed, 301 insertions(+), 464 deletions(-)

diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 767dada..da6eb5e 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -50,6 +50,7 @@ import (
        "github.com/apache/camel-k/pkg/util/kubernetes"
        k8slog "github.com/apache/camel-k/pkg/util/kubernetes/log"
        "github.com/apache/camel-k/pkg/util/property"
+       "github.com/apache/camel-k/pkg/util/resource"
        "github.com/apache/camel-k/pkg/util/sync"
        "github.com/apache/camel-k/pkg/util/watch"
 )
@@ -570,8 +571,8 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd 
*cobra.Command, c client.C
 
        generatedConfigmaps := make([]*corev1.ConfigMap, 0)
 
-       for _, resource := range o.Resources {
-               if config, parseErr := ParseResourceOption(resource); parseErr 
== nil {
+       for _, res := range o.Resources {
+               if config, parseErr := resource.ParseResource(res); parseErr == 
nil {
                        if genCm, applyResourceOptionErr := 
ApplyResourceOption(o.Context, config, integration, c, namespace, 
o.Compression); applyResourceOptionErr != nil {
                                return nil, applyResourceOptionErr
                        } else if genCm != nil {
@@ -583,7 +584,7 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd 
*cobra.Command, c client.C
        }
 
        for _, item := range o.Configs {
-               if config, parseErr := ParseConfigOption(item); parseErr == nil 
{
+               if config, parseErr := resource.ParseConfig(item); parseErr == 
nil {
                        if genCm, applyConfigOptionErr := 
ApplyConfigOption(o.Context, config, integration, c, namespace, o.Compression); 
applyConfigOptionErr != nil {
                                return nil, applyConfigOptionErr
                        } else if genCm != nil {
diff --git a/pkg/cmd/run_help.go b/pkg/cmd/run_help.go
index 0a27687..186eecb 100644
--- a/pkg/cmd/run_help.go
+++ b/pkg/cmd/run_help.go
@@ -22,176 +22,51 @@ import (
        "crypto/sha1" //nolint
        "fmt"
        "path"
-       "path/filepath"
-       "regexp"
        "strings"
 
        v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
        "github.com/apache/camel-k/pkg/client"
-       "github.com/apache/camel-k/pkg/util/camel"
        "github.com/apache/camel-k/pkg/util/kubernetes"
+       "github.com/apache/camel-k/pkg/util/resource"
        "github.com/magiconair/properties"
        corev1 "k8s.io/api/core/v1"
-       k8serrors "k8s.io/apimachinery/pkg/api/errors"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 var invalidPaths = []string{"/etc/camel", "/deployments/dependencies"}
 
-// RunConfigOption represents a config option.
-type RunConfigOption struct {
-       configType      configOptionType
-       resourceName    string
-       resourceKey     string
-       destinationPath string
-}
-
-// DestinationPath is the location where the resource will be stored on 
destination.
-func (runConfigOption *RunConfigOption) DestinationPath() string {
-       return runConfigOption.destinationPath
-}
-
-// Type is the type, converted as string.
-func (runConfigOption *RunConfigOption) Type() string {
-       return string(runConfigOption.configType)
-}
-
-// Name is the name of the resource.
-func (runConfigOption *RunConfigOption) Name() string {
-       return runConfigOption.resourceName
-}
-
-// Key is the key specified for the resource.
-func (runConfigOption *RunConfigOption) Key() string {
-       return runConfigOption.resourceKey
-}
-
-// Validate checks if the DestinationPath is correctly configured.
-func (runConfigOption *RunConfigOption) Validate() error {
-       if runConfigOption.destinationPath == "" {
-               return nil
-       }
-
-       // Check for invalid path
-       for _, invalidPath := range invalidPaths {
-               if runConfigOption.destinationPath == invalidPath || 
strings.HasPrefix(runConfigOption.destinationPath, invalidPath+"/") {
-                       return fmt.Errorf("you cannot mount a file under %s 
path", invalidPath)
-               }
-       }
-       return nil
-}
-
-type configOptionType string
-
-const (
-       // ConfigOptionTypeConfigmap --.
-       ConfigOptionTypeConfigmap configOptionType = "configmap"
-       // ConfigOptionTypeSecret --.
-       ConfigOptionTypeSecret configOptionType = "secret"
-       // ConfigOptionTypeFile --.
-       ConfigOptionTypeFile configOptionType = "file"
-)
-
-var (
-       validConfigSecretRegexp = 
regexp.MustCompile(`^(configmap|secret)\:([\w\.\-\_\:\/@]+)$`)
-       validFileRegexp         = regexp.MustCompile(`^file\:([\w\.\-\_\:\/@" 
]+)$`)
-       validResourceRegexp     = 
regexp.MustCompile(`^([\w\.\-\_\:]+)(\/([\w\.\-\_\:]+))?(\@([\w\.\-\_\:\/]+))?$`)
-)
-
-func newRunConfigOption(configType configOptionType, value string) 
*RunConfigOption {
-       rn, mk, mp := parseResourceValue(configType, value)
-       return &RunConfigOption{
-               configType:      configType,
-               resourceName:    rn,
-               resourceKey:     mk,
-               destinationPath: mp,
-       }
-}
-
-func parseResourceValue(configType configOptionType, value string) (resource 
string, maybeKey string, maybeDestinationPath string) {
-       if configType == ConfigOptionTypeFile {
-               resource, maybeDestinationPath = parseFileValue(value)
-               return resource, "", maybeDestinationPath
-       }
-
-       return parseCMOrSecretValue(value)
-}
-
-func parseFileValue(value string) (localPath string, maybeDestinationPath 
string) {
-       split := strings.SplitN(value, "@", 2)
-       if len(split) == 2 {
-               return split[0], split[1]
-       }
-
-       return value, ""
-}
-
-func parseCMOrSecretValue(value string) (resource string, maybeKey string, 
maybeDestinationPath string) {
-       if !validResourceRegexp.MatchString(value) {
-               return value, "", ""
+//nolint
+func hashFrom(contents ...[]byte) string {
+       // SHA1 because we need to limit the length to less than 64 chars
+       hash := sha1.New()
+       for _, c := range contents {
+               hash.Write(c)
        }
-       // Must have 3 values
-       groups := validResourceRegexp.FindStringSubmatch(value)
 
-       return groups[1], groups[3], groups[5]
+       return fmt.Sprintf("%x", hash.Sum(nil))
 }
 
-// ParseResourceOption will parse and return a runConfigOption.
-func ParseResourceOption(item string) (*RunConfigOption, error) {
-       // Deprecated: ensure backward compatibility with `--resource filename` 
format until version 1.5.x
-       // then replace with parseOption() func directly
-       option, err := parseOption(item)
-       if err != nil {
-               if strings.HasPrefix(err.Error(), "could not match config, 
secret or file configuration") {
-                       fmt.Printf("Warn: --resource %s has been deprecated. 
You should use --resource file:%s instead.\n", item, item)
-                       return parseOption("file:" + item)
-               }
-               return nil, err
+// ApplyConfigOption will set the proper --config option behavior to the 
IntegrationSpec.
+func ApplyConfigOption(ctx context.Context, config *resource.Config, 
integration *v1.Integration, c client.Client,
+       namespace string, enableCompression bool) (*corev1.ConfigMap, error) {
+       // A config option cannot specify destination path
+       if config.DestinationPath() != "" {
+               return nil, fmt.Errorf("cannot specify a destination path for 
this option type")
        }
-
-       return option, nil
-}
-
-// ParseConfigOption will parse and return a runConfigOption.
-func ParseConfigOption(item string) (*RunConfigOption, error) {
-       return parseOption(item)
+       return applyOption(ctx, config, integration, c, namespace, 
enableCompression, v1.ResourceTypeConfig)
 }
 
-func parseOption(item string) (*RunConfigOption, error) {
-       var cot configOptionType
-       var value string
-       switch {
-       case validConfigSecretRegexp.MatchString(item):
-               // parse as secret/configmap
-               groups := validConfigSecretRegexp.FindStringSubmatch(item)
-               switch groups[1] {
-               case "configmap":
-                       cot = ConfigOptionTypeConfigmap
-               case "secret":
-                       cot = ConfigOptionTypeSecret
-               }
-               value = groups[2]
-       case validFileRegexp.MatchString(item):
-               // parse as file
-               groups := validFileRegexp.FindStringSubmatch(item)
-               cot = ConfigOptionTypeFile
-               value = groups[1]
-       default:
-               return nil, fmt.Errorf("could not match config, secret or file 
configuration as %s", item)
-       }
-
-       configurationOption := newRunConfigOption(cot, value)
-       if err := configurationOption.Validate(); err != nil {
-               return nil, err
-       }
-       return configurationOption, nil
+// ApplyResourceOption will set the proper --resource option behavior to the 
IntegrationSpec.
+func ApplyResourceOption(ctx context.Context, config *resource.Config, 
integration *v1.Integration, c client.Client,
+       namespace string, enableCompression bool) (*corev1.ConfigMap, error) {
+       return applyOption(ctx, config, integration, c, namespace, 
enableCompression, v1.ResourceTypeData)
 }
 
-func applyOption(ctx context.Context, config *RunConfigOption, integration 
*v1.Integration,
+func applyOption(ctx context.Context, config *resource.Config, integration 
*v1.Integration,
        c client.Client, namespace string, enableCompression bool, resourceType 
v1.ResourceType) (*corev1.ConfigMap, error) {
        var maybeGenCm *corev1.ConfigMap
-       switch config.configType {
-       case ConfigOptionTypeConfigmap:
+       switch config.Type() {
+       case resource.StorageTypeConfigmap:
                cm := kubernetes.LookupConfigmap(ctx, c, namespace, 
config.Name())
                if cm == nil {
                        fmt.Printf("Warn: %s Configmap not found in %s 
namespace, make sure to provide it before the Integration can run\n",
@@ -199,13 +74,13 @@ func applyOption(ctx context.Context, config 
*RunConfigOption, integration *v1.I
                } else if resourceType != v1.ResourceTypeData && cm.BinaryData 
!= nil {
                        return maybeGenCm, fmt.Errorf("you cannot provide a 
binary config, use a text file instead")
                }
-       case ConfigOptionTypeSecret:
+       case resource.StorageTypeSecret:
                secret := kubernetes.LookupSecret(ctx, c, namespace, 
config.Name())
                if secret == nil {
                        fmt.Printf("Warn: %s Secret not found in %s namespace, 
make sure to provide it before the Integration can run\n",
                                config.Name(), namespace)
                }
-       case ConfigOptionTypeFile:
+       case resource.StorageTypeFile:
                // Don't allow a binary non compressed resource
                rawData, contentType, err := loadRawContent(ctx, config.Name())
                if err != nil {
@@ -218,79 +93,20 @@ func applyOption(ctx context.Context, config 
*RunConfigOption, integration *v1.I
                if err != nil {
                        return maybeGenCm, err
                }
-               maybeGenCm, err = convertFileToConfigmap(ctx, c, resourceSpec, 
config, integration.Namespace, resourceType)
+               maybeGenCm, err = resource.ConvertFileToConfigmap(ctx, c, 
resourceSpec, config, integration.Namespace, resourceType)
                if err != nil {
                        return maybeGenCm, err
                }
        default:
                // Should never reach this
-               return maybeGenCm, fmt.Errorf("invalid option type %s", 
config.configType)
+               return maybeGenCm, fmt.Errorf("invalid option type %s", 
config.Type())
        }
 
-       integration.Spec.AddConfigurationAsResource(config.Type(), 
config.Name(), string(resourceType), config.DestinationPath(), config.Key())
+       integration.Spec.AddConfigurationAsResource(string(config.Type()), 
config.Name(), string(resourceType), config.DestinationPath(), config.Key())
 
        return maybeGenCm, nil
 }
 
-func convertFileToConfigmap(ctx context.Context, c client.Client, resourceSpec 
v1.ResourceSpec, config *RunConfigOption,
-       namespace string, resourceType v1.ResourceType) (*corev1.ConfigMap, 
error) {
-       if config.DestinationPath() == "" {
-               config.resourceKey = filepath.Base(config.Name())
-               // As we are changing the resource to a configmap type
-               // we need to declare the mount path not to use the
-               // default behavior of a configmap (which include a 
subdirectory with the configmap name)
-               if resourceType == v1.ResourceTypeData {
-                       config.destinationPath = camel.ResourcesDefaultMountPath
-               } else {
-                       config.destinationPath = camel.ConfigResourcesMountPath
-               }
-       } else {
-               config.resourceKey = filepath.Base(config.DestinationPath())
-               config.destinationPath = filepath.Dir(config.DestinationPath())
-       }
-       genCmName := fmt.Sprintf("cm-%s", 
hashFrom([]byte(resourceSpec.Content), resourceSpec.RawContent))
-       cm := kubernetes.NewConfigmap(namespace, genCmName, 
filepath.Base(config.Name()), config.Key(), resourceSpec.Content, 
resourceSpec.RawContent)
-       err := c.Create(ctx, cm)
-       if err != nil {
-               if k8serrors.IsAlreadyExists(err) {
-                       // We'll reuse it, as is
-               } else {
-                       return cm, err
-               }
-       }
-       config.configType = ConfigOptionTypeConfigmap
-       config.resourceName = cm.Name
-
-       return cm, nil
-}
-
-//nolint
-func hashFrom(contents ...[]byte) string {
-       // SHA1 because we need to limit the length to less than 64 chars
-       hash := sha1.New()
-       for _, c := range contents {
-               hash.Write(c)
-       }
-
-       return fmt.Sprintf("%x", hash.Sum(nil))
-}
-
-// ApplyConfigOption will set the proper --config option behavior to the 
IntegrationSpec.
-func ApplyConfigOption(ctx context.Context, config *RunConfigOption, 
integration *v1.Integration, c client.Client,
-       namespace string, enableCompression bool) (*corev1.ConfigMap, error) {
-       // A config option cannot specify destination path
-       if config.DestinationPath() != "" {
-               return nil, fmt.Errorf("cannot specify a destination path for 
this option type")
-       }
-       return applyOption(ctx, config, integration, c, namespace, 
enableCompression, v1.ResourceTypeConfig)
-}
-
-// ApplyResourceOption will set the proper --resource option behavior to the 
IntegrationSpec.
-func ApplyResourceOption(ctx context.Context, config *RunConfigOption, 
integration *v1.Integration, c client.Client,
-       namespace string, enableCompression bool) (*corev1.ConfigMap, error) {
-       return applyOption(ctx, config, integration, c, namespace, 
enableCompression, v1.ResourceTypeData)
-}
-
 func binaryOrTextResource(fileName string, data []byte, contentType string, 
base64Compression bool, resourceType v1.ResourceType, destinationPath string) 
(v1.ResourceSpec, error) {
        resourceSpec := v1.ResourceSpec{
                DataSpec: v1.DataSpec{
@@ -325,7 +141,7 @@ func filterFileLocation(maybeFileLocations []string) 
[]string {
        filteredOptions := make([]string, 0)
        for _, option := range maybeFileLocations {
                if strings.HasPrefix(option, "file:") {
-                       localPath, _ := parseFileValue(strings.Replace(option, 
"file:", "", 1))
+                       localPath, _ := 
resource.ParseFileValue(strings.Replace(option, "file:", "", 1))
                        filteredOptions = append(filteredOptions, localPath)
                }
        }
diff --git a/pkg/cmd/run_help_test.go b/pkg/cmd/run_help_test.go
index 0763c06..0fa7c9a 100644
--- a/pkg/cmd/run_help_test.go
+++ b/pkg/cmd/run_help_test.go
@@ -25,125 +25,6 @@ import (
        "github.com/stretchr/testify/assert"
 )
 
-func TestParseConfigOption(t *testing.T) {
-       validConfigMap := "configmap:my-config_map"
-       validSecret := "secret:my-secret"
-       validFile := "file:/tmp/my-file.txt"
-       notValid := "someprotocol:wrong"
-       validLocation := "file:my-file.txt@/tmp/another-name.xml"
-
-       configmap, err := ParseConfigOption(validConfigMap)
-       assert.Nil(t, err)
-       assert.Equal(t, ConfigOptionTypeConfigmap, configmap.configType)
-       assert.Equal(t, "my-config_map", configmap.Name())
-       secret, err := ParseConfigOption(validSecret)
-       assert.Nil(t, err)
-       assert.Equal(t, ConfigOptionTypeSecret, secret.configType)
-       assert.Equal(t, "my-secret", secret.Name())
-       file, err := ParseConfigOption(validFile)
-       assert.Nil(t, err)
-       assert.Equal(t, ConfigOptionTypeFile, file.configType)
-       assert.Equal(t, "/tmp/my-file.txt", file.Name())
-       _, err = ParseConfigOption(notValid)
-       assert.NotNil(t, err)
-       location, err := ParseConfigOption(validLocation)
-       assert.Nil(t, err)
-       assert.Equal(t, ConfigOptionTypeFile, location.configType)
-       assert.Equal(t, "my-file.txt", location.Name())
-       assert.Equal(t, "/tmp/another-name.xml", location.DestinationPath())
-}
-
-func TestParseConfigOptionAllParams(t *testing.T) {
-       cm1 := "configmap:my-config_map/key@/tmp/my"
-       cm2 := "configmap:my-config_map/key"
-       cm3 := "configmap:my-config_map@/tmp/my"
-       cm4 := "configmap:my-config_map"
-       sec1 := "secret:sec/key@/tmp/sec"
-       sec2 := "secret:sec/key"
-       sec3 := "secret:sec@/tmp/sec"
-       sec4 := "secret:sec"
-       file1 := "file:/path/to/my-file.txt@/tmp/file.txt"
-       file2 := "file:/path/to/my-file.txt"
-       file3 := "file:/path to/my-file.txt"
-
-       parsedCm1, err := ParseConfigOption(cm1)
-       assert.Nil(t, err)
-       assert.Equal(t, "configmap", parsedCm1.Type())
-       assert.Equal(t, "my-config_map", parsedCm1.Name())
-       assert.Equal(t, "key", parsedCm1.Key())
-       assert.Equal(t, "/tmp/my", parsedCm1.DestinationPath())
-
-       parsedCm2, err := ParseConfigOption(cm2)
-       assert.Nil(t, err)
-       assert.Equal(t, "configmap", parsedCm2.Type())
-       assert.Equal(t, "my-config_map", parsedCm2.Name())
-       assert.Equal(t, "key", parsedCm2.Key())
-       assert.Equal(t, "", parsedCm2.DestinationPath())
-
-       parsedCm3, err := ParseConfigOption(cm3)
-       assert.Nil(t, err)
-       assert.Equal(t, "configmap", parsedCm3.Type())
-       assert.Equal(t, "my-config_map", parsedCm3.Name())
-       assert.Equal(t, "", parsedCm3.Key())
-       assert.Equal(t, "/tmp/my", parsedCm3.DestinationPath())
-
-       parsedCm4, err := ParseConfigOption(cm4)
-       assert.Nil(t, err)
-       assert.Equal(t, "configmap", parsedCm4.Type())
-       assert.Equal(t, "my-config_map", parsedCm4.Name())
-       assert.Equal(t, "", parsedCm4.Key())
-       assert.Equal(t, "", parsedCm4.DestinationPath())
-
-       parsedSec1, err := ParseConfigOption(sec1)
-       assert.Nil(t, err)
-       assert.Equal(t, "secret", parsedSec1.Type())
-       assert.Equal(t, "sec", parsedSec1.Name())
-       assert.Equal(t, "key", parsedSec1.Key())
-       assert.Equal(t, "/tmp/sec", parsedSec1.DestinationPath())
-
-       parsedSec2, err := ParseConfigOption(sec2)
-       assert.Nil(t, err)
-       assert.Equal(t, "secret", parsedSec2.Type())
-       assert.Equal(t, "sec", parsedSec2.Name())
-       assert.Equal(t, "key", parsedSec2.Key())
-       assert.Equal(t, "", parsedSec2.DestinationPath())
-
-       parsedSec3, err := ParseConfigOption(sec3)
-       assert.Nil(t, err)
-       assert.Equal(t, "secret", parsedSec3.Type())
-       assert.Equal(t, "sec", parsedSec3.Name())
-       assert.Equal(t, "", parsedSec3.Key())
-       assert.Equal(t, "/tmp/sec", parsedSec3.DestinationPath())
-
-       parsedSec4, err := ParseConfigOption(sec4)
-       assert.Nil(t, err)
-       assert.Equal(t, "secret", parsedSec4.Type())
-       assert.Equal(t, "sec", parsedSec4.Name())
-       assert.Equal(t, "", parsedSec4.Key())
-       assert.Equal(t, "", parsedSec4.DestinationPath())
-
-       parsedFile1, err := ParseConfigOption(file1)
-       assert.Nil(t, err)
-       assert.Equal(t, "file", parsedFile1.Type())
-       assert.Equal(t, "/path/to/my-file.txt", parsedFile1.Name())
-       assert.Equal(t, "", parsedFile1.Key())
-       assert.Equal(t, "/tmp/file.txt", parsedFile1.DestinationPath())
-
-       parsedFile2, err := ParseConfigOption(file2)
-       assert.Nil(t, err)
-       assert.Equal(t, "file", parsedFile2.Type())
-       assert.Equal(t, "/path/to/my-file.txt", parsedFile2.Name())
-       assert.Equal(t, "", parsedFile2.Key())
-       assert.Equal(t, "", parsedFile2.DestinationPath())
-
-       parsedFile3, err := ParseConfigOption(file3)
-       assert.Nil(t, err)
-       assert.Equal(t, "file", parsedFile3.Type())
-       assert.Equal(t, "/path to/my-file.txt", parsedFile3.Name())
-       assert.Equal(t, "", parsedFile3.Key())
-       assert.Equal(t, "", parsedFile3.DestinationPath())
-}
-
 func TestFilterFileLocation(t *testing.T) {
        optionFileLocations := []string{
                "file:/path/to/valid/file",
@@ -161,21 +42,6 @@ func TestFilterFileLocation(t *testing.T) {
        assert.Equal(t, "/validfile", filteredOptions[2])
 }
 
-func TestValidateFileLocation(t *testing.T) {
-       validLocation := "file:my-file.txt@/tmp/another-name.xml"
-       etcCamelLocation := "configmap:my-cm@/etc/camel/configmaps"
-       deploymentsDepsLocation := "secret:my-sec@/deployments/dependencies"
-
-       _, err := ParseConfigOption(validLocation)
-       assert.Nil(t, err)
-       _, err = ParseConfigOption(etcCamelLocation)
-       assert.NotNil(t, err)
-       assert.Equal(t, "you cannot mount a file under /etc/camel path", 
err.Error())
-       _, err = ParseConfigOption(deploymentsDepsLocation)
-       assert.NotNil(t, err)
-       assert.Equal(t, "you cannot mount a file under 
/deployments/dependencies path", err.Error())
-}
-
 func TestExtractProperties_SingleKeyValue(t *testing.T) {
        correctValues := []string{"key=val", "key = val", "key= val", " key   = 
 val"}
        for _, val := range correctValues {
diff --git a/pkg/util/resource/config.go b/pkg/util/resource/config.go
new file mode 100644
index 0000000..f5ff97d
--- /dev/null
+++ b/pkg/util/resource/config.go
@@ -0,0 +1,234 @@
+/*
+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 resource
+
+import (
+       "context"
+       "crypto/sha1" //nolint
+       "fmt"
+       "path/filepath"
+       "regexp"
+       "strings"
+
+       v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+       "github.com/apache/camel-k/pkg/client"
+       "github.com/apache/camel-k/pkg/util/camel"
+       "github.com/apache/camel-k/pkg/util/kubernetes"
+       corev1 "k8s.io/api/core/v1"
+       k8serrors "k8s.io/apimachinery/pkg/api/errors"
+)
+
+var invalidPaths = []string{"/etc/camel", "/deployments/dependencies"}
+
+// Config represents a config option.
+type Config struct {
+       configType      StorageType
+       resourceName    string
+       resourceKey     string
+       destinationPath string
+}
+
+// DestinationPath is the location where the resource will be stored on 
destination.
+func (config *Config) DestinationPath() string {
+       return config.destinationPath
+}
+
+// Type is the type, converted as string.
+func (config *Config) Type() StorageType {
+       return config.configType
+}
+
+// Name is the name of the resource.
+func (config *Config) Name() string {
+       return config.resourceName
+}
+
+// Key is the key specified for the resource.
+func (config *Config) Key() string {
+       return config.resourceKey
+}
+
+// Validate checks if the DestinationPath is correctly configured.
+func (config *Config) Validate() error {
+       if config.destinationPath == "" {
+               return nil
+       }
+
+       // Check for invalid path
+       for _, invalidPath := range invalidPaths {
+               if config.destinationPath == invalidPath || 
strings.HasPrefix(config.destinationPath, invalidPath+"/") {
+                       return fmt.Errorf("you cannot mount a file under %s 
path", invalidPath)
+               }
+       }
+       return nil
+}
+
+// StorageType represent a resource/config type such as configmap, secret or 
local file.
+type StorageType string
+
+const (
+       // StorageTypeConfigmap --.
+       StorageTypeConfigmap StorageType = "configmap"
+       // StorageTypeSecret --.
+       StorageTypeSecret StorageType = "secret"
+       // StorageTypeFile --.
+       StorageTypeFile StorageType = "file"
+)
+
+var (
+       validConfigSecretRegexp = 
regexp.MustCompile(`^(configmap|secret)\:([\w\.\-\_\:\/@]+)$`)
+       validFileRegexp         = regexp.MustCompile(`^file\:([\w\.\-\_\:\/@" 
]+)$`)
+       validResourceRegexp     = 
regexp.MustCompile(`^([\w\.\-\_\:]+)(\/([\w\.\-\_\:]+))?(\@([\w\.\-\_\:\/]+))?$`)
+)
+
+func newConfig(configType StorageType, value string) *Config {
+       rn, mk, mp := parseResourceValue(configType, value)
+       return &Config{
+               configType:      configType,
+               resourceName:    rn,
+               resourceKey:     mk,
+               destinationPath: mp,
+       }
+}
+
+func parseResourceValue(configType StorageType, value string) (resource 
string, maybeKey string, maybeDestinationPath string) {
+       if configType == StorageTypeFile {
+               resource, maybeDestinationPath = ParseFileValue(value)
+               return resource, "", maybeDestinationPath
+       }
+
+       return parseCMOrSecretValue(value)
+}
+
+// ParseFileValue will parse a file resource/config option to return the local 
path and the
+// destination path expected.
+func ParseFileValue(value string) (localPath string, maybeDestinationPath 
string) {
+       split := strings.SplitN(value, "@", 2)
+       if len(split) == 2 {
+               return split[0], split[1]
+       }
+
+       return value, ""
+}
+
+func parseCMOrSecretValue(value string) (resource string, maybeKey string, 
maybeDestinationPath string) {
+       if !validResourceRegexp.MatchString(value) {
+               return value, "", ""
+       }
+       // Must have 3 values
+       groups := validResourceRegexp.FindStringSubmatch(value)
+
+       return groups[1], groups[3], groups[5]
+}
+
+// ParseResource will parse and return a Config.
+func ParseResource(item string) (*Config, error) {
+       // Deprecated: ensure backward compatibility with `--resource filename` 
format until version 1.5.x
+       // then replace with parseOption() func directly
+       option, err := parse(item)
+       if err != nil {
+               if strings.HasPrefix(err.Error(), "could not match config, 
secret or file configuration") {
+                       fmt.Printf("Warn: --resource %s has been deprecated. 
You should use --resource file:%s instead.\n", item, item)
+                       return parse("file:" + item)
+               }
+               return nil, err
+       }
+
+       return option, nil
+}
+
+// ParseConfig will parse and return a Config.
+func ParseConfig(item string) (*Config, error) {
+       return parse(item)
+}
+
+func parse(item string) (*Config, error) {
+       var cot StorageType
+       var value string
+       switch {
+       case validConfigSecretRegexp.MatchString(item):
+               // parse as secret/configmap
+               groups := validConfigSecretRegexp.FindStringSubmatch(item)
+               switch groups[1] {
+               case "configmap":
+                       cot = StorageTypeConfigmap
+               case "secret":
+                       cot = StorageTypeSecret
+               }
+               value = groups[2]
+       case validFileRegexp.MatchString(item):
+               // parse as file
+               groups := validFileRegexp.FindStringSubmatch(item)
+               cot = StorageTypeFile
+               value = groups[1]
+       default:
+               return nil, fmt.Errorf("could not match config, secret or file 
configuration as %s", item)
+       }
+
+       configurationOption := newConfig(cot, value)
+       if err := configurationOption.Validate(); err != nil {
+               return nil, err
+       }
+       return configurationOption, nil
+}
+
+// ConvertFileToConfigmap convert a local file resource type in a configmap 
type
+// taking care to create the Configmap on the cluster. The method will change 
the value of config parameter
+// to reflect the conversion applied transparently.
+func ConvertFileToConfigmap(ctx context.Context, c client.Client, resourceSpec 
v1.ResourceSpec, config *Config,
+       namespace string, resourceType v1.ResourceType) (*corev1.ConfigMap, 
error) {
+       if config.DestinationPath() == "" {
+               config.resourceKey = filepath.Base(config.Name())
+               // As we are changing the resource to a configmap type
+               // we need to declare the mount path not to use the
+               // default behavior of a configmap (which include a 
subdirectory with the configmap name)
+               if resourceType == v1.ResourceTypeData {
+                       config.destinationPath = camel.ResourcesDefaultMountPath
+               } else {
+                       config.destinationPath = camel.ConfigResourcesMountPath
+               }
+       } else {
+               config.resourceKey = filepath.Base(config.DestinationPath())
+               config.destinationPath = filepath.Dir(config.DestinationPath())
+       }
+       genCmName := fmt.Sprintf("cm-%s", 
hashFrom([]byte(resourceSpec.Content), resourceSpec.RawContent))
+       cm := kubernetes.NewConfigmap(namespace, genCmName, 
filepath.Base(config.Name()), config.Key(), resourceSpec.Content, 
resourceSpec.RawContent)
+       err := c.Create(ctx, cm)
+       if err != nil {
+               if k8serrors.IsAlreadyExists(err) {
+                       // We'll reuse it, as is
+               } else {
+                       return cm, err
+               }
+       }
+       config.configType = StorageTypeConfigmap
+       config.resourceName = cm.Name
+
+       return cm, nil
+}
+
+//nolint
+func hashFrom(contents ...[]byte) string {
+       // SHA1 because we need to limit the length to less than 64 chars
+       hash := sha1.New()
+       for _, c := range contents {
+               hash.Write(c)
+       }
+
+       return fmt.Sprintf("%x", hash.Sum(nil))
+}
diff --git a/pkg/cmd/run_help_test.go b/pkg/util/resource/config_test.go
similarity index 53%
copy from pkg/cmd/run_help_test.go
copy to pkg/util/resource/config_test.go
index 0763c06..44173f9 100644
--- a/pkg/cmd/run_help_test.go
+++ b/pkg/util/resource/config_test.go
@@ -15,11 +15,9 @@ See the License for the specific language governing 
permissions and
 limitations under the License.
 */
 
-package cmd
+package resource
 
 import (
-       "io/ioutil"
-       "os"
        "testing"
 
        "github.com/stretchr/testify/assert"
@@ -32,23 +30,23 @@ func TestParseConfigOption(t *testing.T) {
        notValid := "someprotocol:wrong"
        validLocation := "file:my-file.txt@/tmp/another-name.xml"
 
-       configmap, err := ParseConfigOption(validConfigMap)
+       configmap, err := ParseConfig(validConfigMap)
        assert.Nil(t, err)
-       assert.Equal(t, ConfigOptionTypeConfigmap, configmap.configType)
+       assert.Equal(t, StorageTypeConfigmap, configmap.configType)
        assert.Equal(t, "my-config_map", configmap.Name())
-       secret, err := ParseConfigOption(validSecret)
+       secret, err := ParseConfig(validSecret)
        assert.Nil(t, err)
-       assert.Equal(t, ConfigOptionTypeSecret, secret.configType)
+       assert.Equal(t, StorageTypeSecret, secret.configType)
        assert.Equal(t, "my-secret", secret.Name())
-       file, err := ParseConfigOption(validFile)
+       file, err := ParseConfig(validFile)
        assert.Nil(t, err)
-       assert.Equal(t, ConfigOptionTypeFile, file.configType)
+       assert.Equal(t, StorageTypeFile, file.configType)
        assert.Equal(t, "/tmp/my-file.txt", file.Name())
-       _, err = ParseConfigOption(notValid)
+       _, err = ParseConfig(notValid)
        assert.NotNil(t, err)
-       location, err := ParseConfigOption(validLocation)
+       location, err := ParseConfig(validLocation)
        assert.Nil(t, err)
-       assert.Equal(t, ConfigOptionTypeFile, location.configType)
+       assert.Equal(t, StorageTypeFile, location.configType)
        assert.Equal(t, "my-file.txt", location.Name())
        assert.Equal(t, "/tmp/another-name.xml", location.DestinationPath())
 }
@@ -66,173 +64,95 @@ func TestParseConfigOptionAllParams(t *testing.T) {
        file2 := "file:/path/to/my-file.txt"
        file3 := "file:/path to/my-file.txt"
 
-       parsedCm1, err := ParseConfigOption(cm1)
+       parsedCm1, err := ParseConfig(cm1)
        assert.Nil(t, err)
-       assert.Equal(t, "configmap", parsedCm1.Type())
+       assert.Equal(t, StorageTypeConfigmap, parsedCm1.Type())
        assert.Equal(t, "my-config_map", parsedCm1.Name())
        assert.Equal(t, "key", parsedCm1.Key())
        assert.Equal(t, "/tmp/my", parsedCm1.DestinationPath())
 
-       parsedCm2, err := ParseConfigOption(cm2)
+       parsedCm2, err := ParseConfig(cm2)
        assert.Nil(t, err)
-       assert.Equal(t, "configmap", parsedCm2.Type())
+       assert.Equal(t, StorageTypeConfigmap, parsedCm2.Type())
        assert.Equal(t, "my-config_map", parsedCm2.Name())
        assert.Equal(t, "key", parsedCm2.Key())
        assert.Equal(t, "", parsedCm2.DestinationPath())
 
-       parsedCm3, err := ParseConfigOption(cm3)
+       parsedCm3, err := ParseConfig(cm3)
        assert.Nil(t, err)
-       assert.Equal(t, "configmap", parsedCm3.Type())
+       assert.Equal(t, StorageTypeConfigmap, parsedCm3.Type())
        assert.Equal(t, "my-config_map", parsedCm3.Name())
        assert.Equal(t, "", parsedCm3.Key())
        assert.Equal(t, "/tmp/my", parsedCm3.DestinationPath())
 
-       parsedCm4, err := ParseConfigOption(cm4)
+       parsedCm4, err := ParseConfig(cm4)
        assert.Nil(t, err)
-       assert.Equal(t, "configmap", parsedCm4.Type())
+       assert.Equal(t, StorageTypeConfigmap, parsedCm4.Type())
        assert.Equal(t, "my-config_map", parsedCm4.Name())
        assert.Equal(t, "", parsedCm4.Key())
        assert.Equal(t, "", parsedCm4.DestinationPath())
 
-       parsedSec1, err := ParseConfigOption(sec1)
+       parsedSec1, err := ParseConfig(sec1)
        assert.Nil(t, err)
-       assert.Equal(t, "secret", parsedSec1.Type())
+       assert.Equal(t, StorageTypeSecret, parsedSec1.Type())
        assert.Equal(t, "sec", parsedSec1.Name())
        assert.Equal(t, "key", parsedSec1.Key())
        assert.Equal(t, "/tmp/sec", parsedSec1.DestinationPath())
 
-       parsedSec2, err := ParseConfigOption(sec2)
+       parsedSec2, err := ParseConfig(sec2)
        assert.Nil(t, err)
-       assert.Equal(t, "secret", parsedSec2.Type())
+       assert.Equal(t, StorageTypeSecret, parsedSec2.Type())
        assert.Equal(t, "sec", parsedSec2.Name())
        assert.Equal(t, "key", parsedSec2.Key())
        assert.Equal(t, "", parsedSec2.DestinationPath())
 
-       parsedSec3, err := ParseConfigOption(sec3)
+       parsedSec3, err := ParseConfig(sec3)
        assert.Nil(t, err)
-       assert.Equal(t, "secret", parsedSec3.Type())
+       assert.Equal(t, StorageTypeSecret, parsedSec3.Type())
        assert.Equal(t, "sec", parsedSec3.Name())
        assert.Equal(t, "", parsedSec3.Key())
        assert.Equal(t, "/tmp/sec", parsedSec3.DestinationPath())
 
-       parsedSec4, err := ParseConfigOption(sec4)
+       parsedSec4, err := ParseConfig(sec4)
        assert.Nil(t, err)
-       assert.Equal(t, "secret", parsedSec4.Type())
+       assert.Equal(t, StorageTypeSecret, parsedSec4.Type())
        assert.Equal(t, "sec", parsedSec4.Name())
        assert.Equal(t, "", parsedSec4.Key())
        assert.Equal(t, "", parsedSec4.DestinationPath())
 
-       parsedFile1, err := ParseConfigOption(file1)
+       parsedFile1, err := ParseConfig(file1)
        assert.Nil(t, err)
-       assert.Equal(t, "file", parsedFile1.Type())
+       assert.Equal(t, StorageTypeFile, parsedFile1.Type())
        assert.Equal(t, "/path/to/my-file.txt", parsedFile1.Name())
        assert.Equal(t, "", parsedFile1.Key())
        assert.Equal(t, "/tmp/file.txt", parsedFile1.DestinationPath())
 
-       parsedFile2, err := ParseConfigOption(file2)
+       parsedFile2, err := ParseConfig(file2)
        assert.Nil(t, err)
-       assert.Equal(t, "file", parsedFile2.Type())
+       assert.Equal(t, StorageTypeFile, parsedFile2.Type())
        assert.Equal(t, "/path/to/my-file.txt", parsedFile2.Name())
        assert.Equal(t, "", parsedFile2.Key())
        assert.Equal(t, "", parsedFile2.DestinationPath())
 
-       parsedFile3, err := ParseConfigOption(file3)
+       parsedFile3, err := ParseConfig(file3)
        assert.Nil(t, err)
-       assert.Equal(t, "file", parsedFile3.Type())
+       assert.Equal(t, StorageTypeFile, parsedFile3.Type())
        assert.Equal(t, "/path to/my-file.txt", parsedFile3.Name())
        assert.Equal(t, "", parsedFile3.Key())
        assert.Equal(t, "", parsedFile3.DestinationPath())
 }
 
-func TestFilterFileLocation(t *testing.T) {
-       optionFileLocations := []string{
-               "file:/path/to/valid/file",
-               "file:app.properties",
-               "configmap:my-configmap",
-               "secret:my-secret",
-               "file:/validfile@/tmp/destination",
-       }
-
-       filteredOptions := filterFileLocation(optionFileLocations)
-
-       assert.Equal(t, 3, len(filteredOptions))
-       assert.Equal(t, "/path/to/valid/file", filteredOptions[0])
-       assert.Equal(t, "app.properties", filteredOptions[1])
-       assert.Equal(t, "/validfile", filteredOptions[2])
-}
-
 func TestValidateFileLocation(t *testing.T) {
        validLocation := "file:my-file.txt@/tmp/another-name.xml"
        etcCamelLocation := "configmap:my-cm@/etc/camel/configmaps"
        deploymentsDepsLocation := "secret:my-sec@/deployments/dependencies"
 
-       _, err := ParseConfigOption(validLocation)
+       _, err := ParseConfig(validLocation)
        assert.Nil(t, err)
-       _, err = ParseConfigOption(etcCamelLocation)
+       _, err = ParseConfig(etcCamelLocation)
        assert.NotNil(t, err)
        assert.Equal(t, "you cannot mount a file under /etc/camel path", 
err.Error())
-       _, err = ParseConfigOption(deploymentsDepsLocation)
+       _, err = ParseConfig(deploymentsDepsLocation)
        assert.NotNil(t, err)
        assert.Equal(t, "you cannot mount a file under 
/deployments/dependencies path", err.Error())
 }
-
-func TestExtractProperties_SingleKeyValue(t *testing.T) {
-       correctValues := []string{"key=val", "key = val", "key= val", " key   = 
 val"}
-       for _, val := range correctValues {
-               prop, err := extractProperties(val)
-               assert.Nil(t, err)
-               value, ok := prop.Get("key")
-               assert.True(t, ok)
-               assert.Equal(t, "val", value)
-       }
-}
-
-func TestExtractProperties_FromFile(t *testing.T) {
-       var tmpFile1 *os.File
-       var err error
-       if tmpFile1, err = ioutil.TempFile("", "camel-k-*.properties"); err != 
nil {
-               t.Error(err)
-       }
-
-       assert.Nil(t, tmpFile1.Close())
-       assert.Nil(t, ioutil.WriteFile(tmpFile1.Name(), []byte(`
-       key=value
-       #key2=value2
-       my.key=value
-       `), 0o400))
-
-       props, err := extractProperties("file:" + tmpFile1.Name())
-       assert.Nil(t, err)
-       assert.Equal(t, 2, props.Len())
-       for _, prop := range props.Keys() {
-               value, ok := props.Get(prop)
-               assert.True(t, ok)
-               assert.Equal(t, "value", value)
-       }
-}
-
-func TestExtractPropertiesFromFileAndSingleValue(t *testing.T) {
-       var tmpFile1 *os.File
-       var err error
-       if tmpFile1, err = ioutil.TempFile("", "camel-k-*.properties"); err != 
nil {
-               t.Error(err)
-       }
-
-       assert.Nil(t, tmpFile1.Close())
-       assert.Nil(t, ioutil.WriteFile(tmpFile1.Name(), []byte(`
-       key=value
-       #key2=value2
-       my.key=value
-       `), 0o400))
-
-       properties := []string{"key=override", "file:" + tmpFile1.Name(), 
"my.key = override"}
-       props, err := mergePropertiesWithPrecedence(properties)
-       assert.Nil(t, err)
-       assert.Equal(t, 2, props.Len())
-       val, ok := props.Get("key")
-       assert.True(t, ok)
-       assert.Equal(t, "override", val)
-       val, ok = props.Get("my.key")
-       assert.True(t, ok)
-       assert.Equal(t, "override", val)
-}

Reply via email to