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) } }
