diff --git a/go.mod b/go.mod index fedff089..cbbf43ae 100644 --- a/go.mod +++ b/go.mod @@ -289,7 +289,7 @@ require ( google.golang.org/protobuf v1.31.0 gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - k8s.io/klog/v2 v2.100.1 + k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect lukechampine.com/frand v1.4.2 // indirect oras.land/oras-go v1.2.3 // indirect diff --git a/pkg/modules/generators/workload/workload_generator.go b/pkg/modules/generators/workload/workload_generator.go index e31a29c8..30eddc00 100644 --- a/pkg/modules/generators/workload/workload_generator.go +++ b/pkg/modules/generators/workload/workload_generator.go @@ -3,6 +3,7 @@ package workload import ( "fmt" "net/url" + "path" "path/filepath" "strconv" "strings" @@ -350,7 +351,6 @@ func handleFileCreation(c container.Container, uniqueAppName, containerName stri ) { var idx int err = modules.ForeachOrdered(c.Files, func(k string, v container.FileSpec) error { - // for k, v := range c.Files { // The declared file path needs to include the file name. if filepath.Base(k) == "." || filepath.Base(k) == "/" { return fmt.Errorf("the declared file path needs to include the file name") @@ -369,8 +369,26 @@ func handleFileCreation(c container.Container, uniqueAppName, containerName stri } if v.ContentFrom != "" { - // TODO: support the creation of the file content from a reference source. - panic("not supported the creation the file content from a reference source") + sec, ok, err := parseSecretReference(v.ContentFrom) + if err != nil || !ok { + return fmt.Errorf("invalid content from str") + } + + volumes = append(volumes, corev1.Volume{ + Name: sec.Name, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: sec.Name, + DefaultMode: &modeInt32, + }, + }, + }) + + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: sec.Name, + MountPath: path.Join("/", k), + SubPath: sec.Key, + }) } else if v.Content != "" { // Create the file content with configMap. data := make(map[string]string) @@ -440,3 +458,31 @@ func completeBaseWorkload(base *workload.Base, config apiv1.GenericConfig) error } return nil } + +type secretReference struct { + Name string + Key string +} + +// parseSecretReference takes secret reference string as parameter and returns secretReference obj. +// Parameter `ref` is expected in following format: secret://sec-name/key, if the provided ref str +// is not in valid format, this function will return false or err. +func parseSecretReference(ref string) (result secretReference, _ bool, _ error) { + if strings.HasPrefix(ref, "${secret://") && strings.HasSuffix(ref, "}") { + ref = ref[2 : len(ref)-1] + } + + if !strings.HasPrefix(ref, "secret://") { + return result, false, nil + } + + u, err := url.Parse(ref) + if err != nil { + return result, false, err + } + + result.Name = u.Host + result.Key, _, _ = strings.Cut(strings.TrimPrefix(u.Path, "/"), "/") + + return result, true, nil +} diff --git a/pkg/modules/generators/workload/workload_generator_test.go b/pkg/modules/generators/workload/workload_generator_test.go index d9953a97..7995cfc6 100644 --- a/pkg/modules/generators/workload/workload_generator_test.go +++ b/pkg/modules/generators/workload/workload_generator_test.go @@ -134,6 +134,58 @@ func TestWorkloadGenerator_Generate(t *testing.T) { } } +func TestGenerate(t *testing.T) { + testCases := []struct { + name string + project string + stack string + application string + workload *workload.Workload + }{ + { + name: "simple service workload", + project: "helloworld", + stack: "dev", + application: "nginx", + workload: &workload.Workload{ + Header: workload.Header{ + Type: workload.TypeService, + }, + Service: &workload.Service{ + Base: workload.Base{ + Containers: map[string]container.Container{ + "main": { + Image: "nginx:latest", + Files: map[string]container.FileSpec{ + "/run/secret/password": { + ContentFrom: "secret://sec-name/key?mode=0400", + Mode: "0644", + }, + }, + }, + }, + }, + Type: workload.Deployment, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := &Generator{ + Project: tc.project, + Stack: tc.stack, + App: tc.application, + Workload: tc.workload, + } + spec := &apiv1.Intent{} + err := g.Generate(spec) + assert.NoError(t, err, "Error should be nil") + }) + } +} + func TestToOrderedContainers(t *testing.T) { t.Run("toOrderedContainers should convert app containers to ordered containers", func(t *testing.T) { appContainers := make(map[string]container.Container)