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

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

commit e938c50fc43db9ad9b3cb85f8d944dc354aa3511
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Mon Jun 7 12:37:25 2021 +0200

    feat(cmd/run): configmap/secret key filtering
    
    * Added support to specify a single key from a configmap/secret in 
--resource or --config flag
    * Refactoring the RunConfigOption struct to include the new feature and 
hide the complexity to usage
    
    Ref #2003
---
 pkg/apis/camel/v1/common_types.go              |  1 +
 pkg/apis/camel/v1/integration_types_support.go |  4 +-
 pkg/cmd/run.go                                 |  4 +-
 pkg/cmd/run_help.go                            | 76 +++++++++++++++-----
 pkg/cmd/run_help_test.go                       | 99 +++++++++++++++++++++++---
 pkg/trait/trait_types.go                       | 33 +++++++--
 pkg/trait/util.go                              |  1 +
 7 files changed, 184 insertions(+), 34 deletions(-)

diff --git a/pkg/apis/camel/v1/common_types.go 
b/pkg/apis/camel/v1/common_types.go
index 42a23fa..d547159 100644
--- a/pkg/apis/camel/v1/common_types.go
+++ b/pkg/apis/camel/v1/common_types.go
@@ -30,6 +30,7 @@ type ConfigurationSpec struct {
        Value              string `json:"value"`
        ResourceType       string `json:"resourceType,omitempty"`
        ResourceMountPoint string `json:"resourceMountPoint,omitempty"`
+       ResourceKey        string `json:"resourceKey,omitempty"`
 }
 
 // Artifact --
diff --git a/pkg/apis/camel/v1/integration_types_support.go 
b/pkg/apis/camel/v1/integration_types_support.go
index 8c2d3e5..31f09d5 100644
--- a/pkg/apis/camel/v1/integration_types_support.go
+++ b/pkg/apis/camel/v1/integration_types_support.go
@@ -110,12 +110,14 @@ func (in *IntegrationSpec) AddConfiguration(confType 
string, confValue string) {
 }
 
 // AddConfigurationAsResource will set a configuration specified with a 
resource type
-func (in *IntegrationSpec) AddConfigurationAsResource(confType string, 
confValue string, resourceType string, resourceMountPoint string) {
+func (in *IntegrationSpec) AddConfigurationAsResource(
+       confType string, confValue string, resourceType string, 
resourceMountPoint string, resourceKey string) {
        in.Configuration = append(in.Configuration, ConfigurationSpec{
                Type:               confType,
                Value:              confValue,
                ResourceType:       resourceType,
                ResourceMountPoint: resourceMountPoint,
+               ResourceKey:        resourceKey,
        })
 }
 
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index fe6b209..e6d42b4 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -81,8 +81,8 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) 
(*cobra.Command, *runCmdOptions)
        cmd.Flags().StringP("kit", "k", "", "The kit used to run the 
integration")
        cmd.Flags().StringArrayP("property", "p", nil, "Add a runtime property 
or properties file (syntax: 
[my-key=my-value|file:/path/to/my-conf.properties])")
        cmd.Flags().StringArray("build-property", nil, "Add a build time 
property or properties file (syntax: 
[my-key=my-value|file:/path/to/my-conf.properties])")
-       cmd.Flags().StringArray("config", nil, "Add a runtime configuration 
from a Configmap, a Secret or a file (syntax: [configmap|secret|file]:name)")
-       cmd.Flags().StringArray("resource", nil, "Add a runtime resource from a 
Configmap, a Secret or a file (syntax: [configmap|secret|file]:name[@path])")
+       cmd.Flags().StringArray("config", nil, "Add a runtime configuration 
from a Configmap, a Secret or a file (syntax: 
[configmap|secret|file]:name[/key])")
+       cmd.Flags().StringArray("resource", nil, "Add a runtime resource from a 
Configmap, a Secret or a file (syntax: 
[configmap|secret|file]:name[/key][@path])")
        cmd.Flags().StringArray("configmap", nil, "[Deprecated] Add a 
ConfigMap")
        cmd.Flags().StringArray("secret", nil, "[Deprecated] Add a Secret")
        cmd.Flags().StringArray("maven-repository", nil, "Add a maven 
repository")
diff --git a/pkg/cmd/run_help.go b/pkg/cmd/run_help.go
index 9734dbd..d117bf8 100644
--- a/pkg/cmd/run_help.go
+++ b/pkg/cmd/run_help.go
@@ -33,8 +33,9 @@ var invalidPaths = []string{"/etc/camel", 
"/deployments/dependencies"}
 
 // RunConfigOption represents a config option
 type RunConfigOption struct {
-       ConfigType      configOptionType
-       Value           string
+       configType      configOptionType
+       resourceName    string
+       resourceKey     string
        destinationPath string
 }
 
@@ -43,11 +44,28 @@ func (runConfigOption *RunConfigOption) DestinationPath() 
string {
        return runConfigOption.destinationPath
 }
 
-// Validate checks if the DestinationPath exists and in case if it's a valid 
path
+// 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)
@@ -68,17 +86,28 @@ const (
 )
 
 var validConfigRegexp = 
regexp.MustCompile(`^(configmap|secret|file)\:([\w\.\-\_\:\/@]+)$`)
+var validResourceRegexp = 
regexp.MustCompile(`^([\w\.\-\_\:]+)(\/([\w\.\-\_\:]+))?(\@([\w\.\-\_\:\/]+))?$`)
 
 func newRunConfigOption(configType configOptionType, value string) 
*RunConfigOption {
-       optionValue, maybeDestinationPath := parseFileValue(value)
+       rn, mk, mp := parseResourceValue(configType, value)
        return &RunConfigOption{
-               ConfigType:      configType,
-               Value:           optionValue,
-               destinationPath: maybeDestinationPath,
+               configType:      configType,
+               resourceName:    rn,
+               resourceKey:     mk,
+               destinationPath: mp,
        }
 }
 
-func parseFileValue(value string) (string, string) {
+func parseResourceValue(configType configOptionType, value string) (resource 
string, maybeKey string, maybeDestinationPath string) {
+       if configType == ConfigOptionTypeFile {
+               resource, maybeDestinationPath = parseFileValue(value)
+               return resource, "", maybeDestinationPath
+       } else {
+               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]
@@ -86,6 +115,15 @@ func parseFileValue(value string) (string, string) {
        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]
+}
+
 // 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
@@ -133,26 +171,26 @@ func parseOption(item string) (*RunConfigOption, error) {
 
 func applyOption(config *RunConfigOption, integrationSpec *v1.IntegrationSpec,
        c client.Client, namespace string, enableCompression bool, resourceType 
v1.ResourceType) error {
-       switch config.ConfigType {
+       switch config.configType {
        case ConfigOptionTypeConfigmap:
-               cm := kubernetes.LookupConfigmap(context.Background(), c, 
namespace, config.Value)
+               cm := kubernetes.LookupConfigmap(context.Background(), 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",
-                               config.Value, namespace)
+                               config.Name(), namespace)
                } else if resourceType != v1.ResourceTypeData && cm.BinaryData 
!= nil {
                        return fmt.Errorf("you cannot provide a binary config, 
use a text file instead")
                }
-               
integrationSpec.AddConfigurationAsResource(string(config.ConfigType), 
config.Value, string(resourceType), config.DestinationPath())
+               integrationSpec.AddConfigurationAsResource(config.Type(), 
config.Name(), string(resourceType), config.DestinationPath(), config.Key())
        case ConfigOptionTypeSecret:
-               secret := kubernetes.LookupSecret(context.Background(), c, 
namespace, config.Value)
+               secret := kubernetes.LookupSecret(context.Background(), 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.Value, namespace)
+                               config.Name(), namespace)
                }
-               
integrationSpec.AddConfigurationAsResource(string(config.ConfigType), 
config.Value, string(resourceType), config.DestinationPath())
+               
integrationSpec.AddConfigurationAsResource(string(config.configType), 
config.Name(), string(resourceType), config.DestinationPath(), config.Key())
        case ConfigOptionTypeFile:
                // Don't allow a file size longer than 1 MiB
-               fileSize, err := fileSize(config.Value)
+               fileSize, err := fileSize(config.Name())
                printSize := fmt.Sprintf("%.2f", float64(fileSize)/Megabyte)
                if err != nil {
                        return err
@@ -160,21 +198,21 @@ func applyOption(config *RunConfigOption, integrationSpec 
*v1.IntegrationSpec,
                        return fmt.Errorf("you cannot provide a file larger 
than 1 MB (it was %s MB), check configmap option or --volume instead", 
printSize)
                }
                // Don't allow a binary non compressed resource
-               rawData, contentType, err := loadRawContent(config.Value)
+               rawData, contentType, err := loadRawContent(config.Name())
                if err != nil {
                        return err
                }
                if resourceType != v1.ResourceTypeData && !enableCompression && 
isBinary(contentType) {
                        return fmt.Errorf("you cannot provide a binary config, 
use a text file or check --resource flag instead")
                }
-               resourceSpec, err := 
binaryOrTextResource(path.Base(config.Value), rawData, contentType, 
enableCompression, resourceType, config.DestinationPath())
+               resourceSpec, err := 
binaryOrTextResource(path.Base(config.Name()), rawData, contentType, 
enableCompression, resourceType, config.DestinationPath())
                if err != nil {
                        return err
                }
                integrationSpec.AddResources(resourceSpec)
        default:
                // Should never reach this
-               return fmt.Errorf("invalid option type %s", config.ConfigType)
+               return fmt.Errorf("invalid option type %s", config.configType)
        }
 
        return nil
diff --git a/pkg/cmd/run_help_test.go b/pkg/cmd/run_help_test.go
index ebbff23..75e02f3 100644
--- a/pkg/cmd/run_help_test.go
+++ b/pkg/cmd/run_help_test.go
@@ -32,25 +32,108 @@ func TestParseConfigOption(t *testing.T) {
 
        configmap, err := ParseConfigOption(validConfigMap)
        assert.Nil(t, err)
-       assert.Equal(t, ConfigOptionTypeConfigmap, configmap.ConfigType)
-       assert.Equal(t, "my-config_map", configmap.Value)
+       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.Value)
+       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.Value)
+       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.Value)
+       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"
+
+       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())
+}
+
 func TestFilterFileLocation(t *testing.T) {
        optionFileLocations := []string{
                "file:/path/to/valid/file",
diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go
index 9049fe0..6b3a23d 100644
--- a/pkg/trait/trait_types.go
+++ b/pkg/trait/trait_types.go
@@ -697,7 +697,7 @@ func (e *Environment) configureVolumesAndMounts(vols 
*[]corev1.Volume, mnts *[]c
        for _, configmaps := range e.collectConfigurations("configmap") {
                refName := kubernetes.SanitizeLabel(configmaps["value"])
 
-               *vols = append(*vols, corev1.Volume{
+               configmapVolume := corev1.Volume{
                        Name: refName,
                        VolumeSource: corev1.VolumeSource{
                                ConfigMap: &corev1.ConfigMapVolumeSource{
@@ -706,7 +706,19 @@ func (e *Environment) configureVolumesAndMounts(vols 
*[]corev1.Volume, mnts *[]c
                                        },
                                },
                        },
-               })
+               }
+
+               // Filter the items selected, if specified
+               if configmaps["resourceKey"] != "" {
+                       configmapVolume.VolumeSource.ConfigMap.Items = 
[]corev1.KeyToPath{
+                               {
+                                       Key:  configmaps["resourceKey"],
+                                       Path: configmaps["resourceKey"],
+                               },
+                       }
+               }
+
+               *vols = append(*vols, configmapVolume)
 
                *mnts = append(*mnts, corev1.VolumeMount{
                        Name:      refName,
@@ -736,17 +748,30 @@ func (e *Environment) configureVolumesAndMounts(vols 
*[]corev1.Volume, mnts *[]c
                        MountPath: path.Join(serviceBindingsMountPath, 
strings.ToLower(sb)),
                })
        }
+
        for _, secret := range e.collectConfigurations("secret") {
                refName := kubernetes.SanitizeLabel(secret["value"])
 
-               *vols = append(*vols, corev1.Volume{
+               secretVolume := corev1.Volume{
                        Name: refName,
                        VolumeSource: corev1.VolumeSource{
                                Secret: &corev1.SecretVolumeSource{
                                        SecretName: secret["value"],
                                },
                        },
-               })
+               }
+
+               // Filter the items selected, if specified
+               if secret["resourceKey"] != "" {
+                       secretVolume.VolumeSource.Secret.Items = 
[]corev1.KeyToPath{
+                               {
+                                       Key:  secret["resourceKey"],
+                                       Path: secret["resourceKey"],
+                               },
+                       }
+               }
+
+               *vols = append(*vols, secretVolume)
 
                *mnts = append(*mnts, corev1.VolumeMount{
                        Name:      refName,
diff --git a/pkg/trait/util.go b/pkg/trait/util.go
index 0f5d936..af88cc4 100644
--- a/pkg/trait/util.go
+++ b/pkg/trait/util.go
@@ -98,6 +98,7 @@ func collectConfigurations(configurationType string, 
configurable ...v1.Configur
                                item["value"] = entry.Value
                                item["resourceType"] = entry.ResourceType
                                item["resourceMountPoint"] = 
entry.ResourceMountPoint
+                               item["resourceKey"] = entry.ResourceKey
                                result = append(result, item)
                        }
                }

Reply via email to