From 4728c5a085404f0eb761e38f26f5d2a3227fa5d1 Mon Sep 17 00:00:00 2001 From: Stephanie Date: Thu, 27 May 2021 12:36:09 -0400 Subject: [PATCH 1/5] add GenerateInitContainers function to generator Signed-off-by: Stephanie --- pkg/devfile/generator/generators.go | 71 +++++++++++++++++++++++++++ pkg/devfile/generator/utils.go | 28 +++++++++++ pkg/devfile/generator/utils_test.go | 76 +++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+) diff --git a/pkg/devfile/generator/generators.go b/pkg/devfile/generator/generators.go index c3d02d04..562ed524 100644 --- a/pkg/devfile/generator/generators.go +++ b/pkg/devfile/generator/generators.go @@ -1,6 +1,8 @@ package generator import ( + "fmt" + "github.com/devfile/library/pkg/util" buildv1 "github.com/openshift/api/build/v1" imagev1 "github.com/openshift/api/image/v1" routev1 "github.com/openshift/api/route/v1" @@ -9,6 +11,7 @@ import ( extensionsv1 "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "strings" v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser" @@ -27,6 +30,10 @@ const ( deploymentKind = "Deployment" deploymentAPIVersion = "apps/v1" + + containerNameMaxLen = 55 + + shellExecutable = "/bin/sh" ) // GetTypeMeta gets a type meta of the specified kind and version @@ -91,6 +98,70 @@ func GetContainers(devfileObj parser.DevfileObj, options common.DevfileOptions) return containers, nil } +// GetInitContainers gets the init container for every preStart devfile event +func GetInitContainers(devfileObj parser.DevfileObj) ([]corev1.Container, error) { + containers, err := GetContainers(devfileObj, common.DevfileOptions{}) + if err != nil { + return nil, err + } + preStartEvents := devfileObj.Data.GetEvents().PreStart + var initContainers []corev1.Container + if len(preStartEvents) > 0 { + var eventCommands []string + commands, err := devfileObj.Data.GetCommands(common.DevfileOptions{}) + if err != nil { + return nil, err + } + + commandsMap := getCommandsMap(commands) + + for _, event := range preStartEvents { + eventSubCommands := getCommandsFromEvent(commandsMap, strings.ToLower(event)) + eventCommands = append(eventCommands, eventSubCommands...) + } + + for i, commandName := range eventCommands { + if command, ok := commandsMap[commandName]; ok { + component := common.GetExecComponent(command) + commandLine := common.GetExecCommandLine(command) + workingDir := common.GetExecWorkingDir(command) + + var cmdArr []string + if workingDir != "" { + // since we are using /bin/sh -c, the command needs to be within a single double quote instance, for example "cd /tmp && pwd" + cmdArr = []string{shellExecutable, "-c", "cd " + workingDir + " && " + commandLine} + } else { + cmdArr = []string{shellExecutable, "-c", commandLine} + } + + // Get the container info for the given component + for _, container := range containers { + if container.Name == component { + // override any container command and args with our event command cmdArr + container.Command = cmdArr + container.Args = []string{} + + // Override the init container name since there cannot be two containers with the same + // name in a pod. This applies to pod containers and pod init containers. The convention + // for init container name here is, containername-eventname- + // If there are two events referencing the same devfile component, then we will have + // tools-event1-1 & tools-event2-3, for example. And if in the edge case, the same command is + // executed twice by preStart events, then we will have tools-event1-1 & tools-event1-2 + initContainerName := fmt.Sprintf("%s-%s", container.Name, commandName) + initContainerName = util.TruncateString(initContainerName, containerNameMaxLen) + initContainerName = fmt.Sprintf("%s-%d", initContainerName, i+1) + container.Name = initContainerName + + initContainers = append(initContainers, container) + } + } + } + } + } + + return initContainers, nil +} + // DeploymentParams is a struct that contains the required data to create a deployment object type DeploymentParams struct { TypeMeta metav1.TypeMeta diff --git a/pkg/devfile/generator/utils.go b/pkg/devfile/generator/utils.go index 4a7cb7f0..7bfce7aa 100644 --- a/pkg/devfile/generator/utils.go +++ b/pkg/devfile/generator/utils.go @@ -451,3 +451,31 @@ func addVolumeMountToContainers(containers []corev1.Container, volumeName string } } } + +// getCommandsFromEvent returns the list of commands from the event name. +// If the event is a composite command, it returns the sub-commands from the tree +func getCommandsFromEvent(commandsMap map[string]v1.Command, eventName string) []string { + var commands []string + + if command, ok := commandsMap[eventName]; ok { + if command.Composite != nil { + for _, compositeSubCmd := range command.Composite.Commands { + subCommands := getCommandsFromEvent(commandsMap, compositeSubCmd) + commands = append(commands, subCommands...) + } + } else { + commands = append(commands, command.Id) + } + } + + return commands +} + +// getCommandsMap returns a map of the command Id to the command +func getCommandsMap(commands []v1.Command) map[string]v1.Command { + commandMap := make(map[string]v1.Command, len(commands)) + for _, command := range commands { + commandMap[command.Id] = command + } + return commandMap +} diff --git a/pkg/devfile/generator/utils_test.go b/pkg/devfile/generator/utils_test.go index 71f9e065..1b4c5cd7 100644 --- a/pkg/devfile/generator/utils_test.go +++ b/pkg/devfile/generator/utils_test.go @@ -1393,3 +1393,79 @@ func TestAddVolumeMountToContainers(t *testing.T) { }) } } + +func TestGetCommandsFromEvent(t *testing.T) { + + execCommands := []v1.Command{ + { + Id: "exec1", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{}, + }, + }, + { + Id: "exec2", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{}, + }, + }, + { + Id: "exec3", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{}, + }, + }, + } + + compCommands := []v1.Command{ + { + Id: "comp1", + CommandUnion: v1.CommandUnion{ + Composite: &v1.CompositeCommand{ + Commands: []string{ + "exec1", + "exec3", + }, + }, + }, + }, + } + + commandsMap := map[string]v1.Command{ + compCommands[0].Id: compCommands[0], + execCommands[0].Id: execCommands[0], + execCommands[1].Id: execCommands[1], + execCommands[2].Id: execCommands[2], + } + + tests := []struct { + name string + eventName string + wantCommands []string + }{ + { + name: "composite event", + eventName: "comp1", + wantCommands: []string{ + "exec1", + "exec3", + }, + }, + { + name: "exec event", + eventName: "exec2", + wantCommands: []string{ + "exec2", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + commands := getCommandsFromEvent(commandsMap, tt.eventName) + if !reflect.DeepEqual(tt.wantCommands, commands) { + t.Errorf("TestGetCommandsFromEvent error - got %v expected %v", commands, tt.wantCommands) + } + }) + } + +} From bbe2129de3c7eaf761869fd0bf5cf9f90684e2fb Mon Sep 17 00:00:00 2001 From: Stephanie Date: Thu, 27 May 2021 13:18:45 -0400 Subject: [PATCH 2/5] add unit test to GenetrateInitContainers Signed-off-by: Stephanie --- pkg/devfile/generator/generators_test.go | 194 +++++++++++++++++++++++ 1 file changed, 194 insertions(+) diff --git a/pkg/devfile/generator/generators_test.go b/pkg/devfile/generator/generators_test.go index bbf0ef85..03b77b8c 100644 --- a/pkg/devfile/generator/generators_test.go +++ b/pkg/devfile/generator/generators_test.go @@ -1,7 +1,10 @@ package generator import ( + "github.com/devfile/library/pkg/devfile/parser/data" + "github.com/devfile/library/pkg/util" "reflect" + "strings" "testing" v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -487,3 +490,194 @@ func TestGetVolumeMountPath(t *testing.T) { } } + +func TestGetInitContainers(t *testing.T) { + + containers := []v1.Component{ + { + Name: "container1", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "container1", + }, + }, + }, + }, + { + Name: "container2", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "container2", + }, + }, + }, + }, + } + + execCommands := []v1.Command{ + { + Id: "exec1", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + CommandLine: "execcommand1", + WorkingDir: "execworkdir1", + Component: "container1", + }, + }, + }, + { + Id: "exec2", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + CommandLine: "execcommand2", + WorkingDir: "", + Component: "container1", + }, + }, + }, + { + Id: "exec3", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + CommandLine: "execcommand3", + WorkingDir: "execworkdir3", + Component: "container2", + }, + }, + }, + } + + compCommands := []v1.Command{ + { + Id: "comp1", + CommandUnion: v1.CommandUnion{ + Composite: &v1.CompositeCommand{ + Commands: []string{ + "exec1", + "exec3", + }, + }, + }, + }, + } + + longContainerName := "thisisaverylongcontainerandkuberneteshasalimitforanamesize-exec2" + trimmedLongContainerName := util.TruncateString(longContainerName, containerNameMaxLen) + + tests := []struct { + name string + eventCommands []string + wantInitContainer map[string]corev1.Container + longName bool + wantErr bool + }{ + { + name: "Case 1: Composite and Exec events", + eventCommands: []string{ + "exec1", + "exec3", + "exec2", + }, + wantInitContainer: map[string]corev1.Container{ + "container1-exec1": { + Command: []string{shellExecutable, "-c", "cd execworkdir1 && execcommand1"}, + }, + "container1-exec2": { + Command: []string{shellExecutable, "-c", "execcommand2"}, + }, + "container2-exec3": { + Command: []string{shellExecutable, "-c", "cd execworkdir3 && execcommand3"}, + }, + }, + }, + { + name: "Case 2: Long Container Name", + eventCommands: []string{ + "exec2", + }, + wantInitContainer: map[string]corev1.Container{ + trimmedLongContainerName: { + Command: []string{shellExecutable, "-c", "execcommand2"}, + }, + }, + longName: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + if tt.longName { + containers[0].Name = longContainerName + execCommands[1].Exec.Component = longContainerName + } + + devObj := parser.DevfileObj{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APISchemaVersion210)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents(containers) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(execCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(compCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddEvents(v1.Events{ + DevWorkspaceEvents: v1.DevWorkspaceEvents{ + PreStart: tt.eventCommands, + }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), + } + + initContainers, err := GetInitContainers(devObj) + if (err != nil) != tt.wantErr { + t.Errorf("TestGetInitContainers() error = %v, wantErr %v", err, tt.wantErr) + } + + if len(tt.wantInitContainer) != len(initContainers) { + t.Errorf("TestGetInitContainers() error: init container length mismatch, wanted %v got %v", len(tt.wantInitContainer), len(initContainers)) + } + + for _, initContainer := range initContainers { + nameMatched := false + commandMatched := false + for containerName, container := range tt.wantInitContainer { + if strings.Contains(initContainer.Name, containerName) { + nameMatched = true + } + + if reflect.DeepEqual(initContainer.Command, container.Command) { + commandMatched = true + } + + if !reflect.DeepEqual(initContainer.Args, []string{}) { + t.Errorf("TestGetInitContainers() error: init container args not empty, got %v", initContainer.Args) + } + } + + if !nameMatched { + t.Errorf("TestGetInitContainers() error: init container name mismatch, container name not present in %v", initContainer.Name) + } + + if !commandMatched { + t.Errorf("TestGetInitContainers() error: init container command mismatch, command not found in %v", initContainer.Command) + } + } + }) + } + +} From b0182670b2be05351f6d14eb0b01db827500b56f Mon Sep 17 00:00:00 2001 From: Stephanie Date: Wed, 2 Jun 2021 14:53:06 -0400 Subject: [PATCH 3/5] address review comments Signed-off-by: Stephanie --- pkg/devfile/generator/generators.go | 30 ++------ pkg/devfile/generator/generators_test.go | 64 +++++++--------- pkg/devfile/generator/utils.go | 28 ------- pkg/devfile/generator/utils_test.go | 76 ------------------- .../parser/data/v2/common/command_helper.go | 55 ++++++++++++++ .../data/v2/common/command_helper_test.go | 76 +++++++++++++++++++ 6 files changed, 165 insertions(+), 164 deletions(-) diff --git a/pkg/devfile/generator/generators.go b/pkg/devfile/generator/generators.go index 562ed524..b0a16e36 100644 --- a/pkg/devfile/generator/generators.go +++ b/pkg/devfile/generator/generators.go @@ -2,6 +2,9 @@ package generator import ( "fmt" + v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/library/pkg/devfile/parser" + "github.com/devfile/library/pkg/devfile/parser/data/v2/common" "github.com/devfile/library/pkg/util" buildv1 "github.com/openshift/api/build/v1" imagev1 "github.com/openshift/api/image/v1" @@ -11,11 +14,6 @@ import ( extensionsv1 "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "strings" - - v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - "github.com/devfile/library/pkg/devfile/parser" - "github.com/devfile/library/pkg/devfile/parser/data/v2/common" ) const ( @@ -32,8 +30,6 @@ const ( deploymentAPIVersion = "apps/v1" containerNameMaxLen = 55 - - shellExecutable = "/bin/sh" ) // GetTypeMeta gets a type meta of the specified kind and version @@ -113,34 +109,20 @@ func GetInitContainers(devfileObj parser.DevfileObj) ([]corev1.Container, error) return nil, err } - commandsMap := getCommandsMap(commands) + commandsMap := common.GetCommandsMap(commands) for _, event := range preStartEvents { - eventSubCommands := getCommandsFromEvent(commandsMap, strings.ToLower(event)) + eventSubCommands := common.GetCommandsFromEvent(commandsMap, event) eventCommands = append(eventCommands, eventSubCommands...) } for i, commandName := range eventCommands { if command, ok := commandsMap[commandName]; ok { - component := common.GetExecComponent(command) - commandLine := common.GetExecCommandLine(command) - workingDir := common.GetExecWorkingDir(command) - - var cmdArr []string - if workingDir != "" { - // since we are using /bin/sh -c, the command needs to be within a single double quote instance, for example "cd /tmp && pwd" - cmdArr = []string{shellExecutable, "-c", "cd " + workingDir + " && " + commandLine} - } else { - cmdArr = []string{shellExecutable, "-c", commandLine} - } + component := common.GetApplyComponent(command) // Get the container info for the given component for _, container := range containers { if container.Name == component { - // override any container command and args with our event command cmdArr - container.Command = cmdArr - container.Args = []string{} - // Override the init container name since there cannot be two containers with the same // name in a pod. This applies to pod containers and pod init containers. The convention // for init container name here is, containername-eventname- diff --git a/pkg/devfile/generator/generators_test.go b/pkg/devfile/generator/generators_test.go index 03b77b8c..399aed38 100644 --- a/pkg/devfile/generator/generators_test.go +++ b/pkg/devfile/generator/generators_test.go @@ -492,14 +492,15 @@ func TestGetVolumeMountPath(t *testing.T) { } func TestGetInitContainers(t *testing.T) { - + shellExecutable := "/bin/sh" containers := []v1.Component{ { Name: "container1", ComponentUnion: v1.ComponentUnion{ Container: &v1.ContainerComponent{ Container: v1.Container{ - Image: "container1", + Image: "container1", + Command: []string{shellExecutable, "-c", "cd execworkdir1 && execcommand1"}, }, }, }, @@ -509,7 +510,8 @@ func TestGetInitContainers(t *testing.T) { ComponentUnion: v1.ComponentUnion{ Container: &v1.ContainerComponent{ Container: v1.Container{ - Image: "container2", + Image: "container2", + Command: []string{shellExecutable, "-c", "cd execworkdir3 && execcommand3"}, }, }, }, @@ -518,32 +520,26 @@ func TestGetInitContainers(t *testing.T) { execCommands := []v1.Command{ { - Id: "exec1", + Id: "apply1", CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: "execcommand1", - WorkingDir: "execworkdir1", - Component: "container1", + Apply: &v1.ApplyCommand{ + Component: "container1", }, }, }, { - Id: "exec2", + Id: "apply2", CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: "execcommand2", - WorkingDir: "", - Component: "container1", + Apply: &v1.ApplyCommand{ + Component: "container1", }, }, }, { - Id: "exec3", + Id: "apply3", CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: "execcommand3", - WorkingDir: "execworkdir3", - Component: "container2", + Apply: &v1.ApplyCommand{ + Component: "container2", }, }, }, @@ -555,8 +551,8 @@ func TestGetInitContainers(t *testing.T) { CommandUnion: v1.CommandUnion{ Composite: &v1.CompositeCommand{ Commands: []string{ - "exec1", - "exec3", + "apply1", + "apply3", }, }, }, @@ -574,32 +570,32 @@ func TestGetInitContainers(t *testing.T) { wantErr bool }{ { - name: "Case 1: Composite and Exec events", + name: "Composite and Exec events", eventCommands: []string{ - "exec1", - "exec3", - "exec2", + "apply1", + "apply3", + "apply2", }, wantInitContainer: map[string]corev1.Container{ - "container1-exec1": { + "container1-apply1": { Command: []string{shellExecutable, "-c", "cd execworkdir1 && execcommand1"}, }, - "container1-exec2": { - Command: []string{shellExecutable, "-c", "execcommand2"}, + "container1-apply2": { + Command: []string{shellExecutable, "-c", "cd execworkdir1 && execcommand1"}, }, - "container2-exec3": { + "container2-apply3": { Command: []string{shellExecutable, "-c", "cd execworkdir3 && execcommand3"}, }, }, }, { - name: "Case 2: Long Container Name", + name: "Long Container Name", eventCommands: []string{ - "exec2", + "apply2", }, wantInitContainer: map[string]corev1.Container{ trimmedLongContainerName: { - Command: []string{shellExecutable, "-c", "execcommand2"}, + Command: []string{shellExecutable, "-c", "cd execworkdir1 && execcommand1"}, }, }, longName: true, @@ -610,7 +606,7 @@ func TestGetInitContainers(t *testing.T) { if tt.longName { containers[0].Name = longContainerName - execCommands[1].Exec.Component = longContainerName + execCommands[1].Apply.Component = longContainerName } devObj := parser.DevfileObj{ @@ -663,10 +659,6 @@ func TestGetInitContainers(t *testing.T) { if reflect.DeepEqual(initContainer.Command, container.Command) { commandMatched = true } - - if !reflect.DeepEqual(initContainer.Args, []string{}) { - t.Errorf("TestGetInitContainers() error: init container args not empty, got %v", initContainer.Args) - } } if !nameMatched { diff --git a/pkg/devfile/generator/utils.go b/pkg/devfile/generator/utils.go index 7bfce7aa..4a7cb7f0 100644 --- a/pkg/devfile/generator/utils.go +++ b/pkg/devfile/generator/utils.go @@ -451,31 +451,3 @@ func addVolumeMountToContainers(containers []corev1.Container, volumeName string } } } - -// getCommandsFromEvent returns the list of commands from the event name. -// If the event is a composite command, it returns the sub-commands from the tree -func getCommandsFromEvent(commandsMap map[string]v1.Command, eventName string) []string { - var commands []string - - if command, ok := commandsMap[eventName]; ok { - if command.Composite != nil { - for _, compositeSubCmd := range command.Composite.Commands { - subCommands := getCommandsFromEvent(commandsMap, compositeSubCmd) - commands = append(commands, subCommands...) - } - } else { - commands = append(commands, command.Id) - } - } - - return commands -} - -// getCommandsMap returns a map of the command Id to the command -func getCommandsMap(commands []v1.Command) map[string]v1.Command { - commandMap := make(map[string]v1.Command, len(commands)) - for _, command := range commands { - commandMap[command.Id] = command - } - return commandMap -} diff --git a/pkg/devfile/generator/utils_test.go b/pkg/devfile/generator/utils_test.go index 1b4c5cd7..71f9e065 100644 --- a/pkg/devfile/generator/utils_test.go +++ b/pkg/devfile/generator/utils_test.go @@ -1393,79 +1393,3 @@ func TestAddVolumeMountToContainers(t *testing.T) { }) } } - -func TestGetCommandsFromEvent(t *testing.T) { - - execCommands := []v1.Command{ - { - Id: "exec1", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{}, - }, - }, - { - Id: "exec2", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{}, - }, - }, - { - Id: "exec3", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{}, - }, - }, - } - - compCommands := []v1.Command{ - { - Id: "comp1", - CommandUnion: v1.CommandUnion{ - Composite: &v1.CompositeCommand{ - Commands: []string{ - "exec1", - "exec3", - }, - }, - }, - }, - } - - commandsMap := map[string]v1.Command{ - compCommands[0].Id: compCommands[0], - execCommands[0].Id: execCommands[0], - execCommands[1].Id: execCommands[1], - execCommands[2].Id: execCommands[2], - } - - tests := []struct { - name string - eventName string - wantCommands []string - }{ - { - name: "composite event", - eventName: "comp1", - wantCommands: []string{ - "exec1", - "exec3", - }, - }, - { - name: "exec event", - eventName: "exec2", - wantCommands: []string{ - "exec2", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - commands := getCommandsFromEvent(commandsMap, tt.eventName) - if !reflect.DeepEqual(tt.wantCommands, commands) { - t.Errorf("TestGetCommandsFromEvent error - got %v expected %v", commands, tt.wantCommands) - } - }) - } - -} diff --git a/pkg/devfile/parser/data/v2/common/command_helper.go b/pkg/devfile/parser/data/v2/common/command_helper.go index 6a93ac93..6d5c91c4 100644 --- a/pkg/devfile/parser/data/v2/common/command_helper.go +++ b/pkg/devfile/parser/data/v2/common/command_helper.go @@ -50,6 +50,33 @@ func GetExecWorkingDir(dc v1.Command) string { return "" } +// GetApplyComponent returns the component of the exec command +func GetApplyComponent(dc v1.Command) string { + if dc.Apply != nil { + return dc.Apply.Component + } + + return "" +} + +//// GetApplyCommandLine returns the command line of the exec command +//func GetApplyCommandLine(dc v1.Command) string { +// if dc.Apply != nil { +// return dc.Apply +// } +// +// return "" +//} +// +//// GetExecWorkingDir returns the working dir of the exec command +//func GetExecWorkingDir(dc v1.Command) string { +// if dc.Exec != nil { +// return dc.Exec.WorkingDir +// } +// +// return "" +//} + // GetCommandType returns the command type of a given command func GetCommandType(command v1.Command) (v1.CommandType, error) { switch { @@ -66,3 +93,31 @@ func GetCommandType(command v1.Command) (v1.CommandType, error) { return "", fmt.Errorf("unknown command type") } } + +// GetCommandsMap returns a map of the command Id to the command +func GetCommandsMap(commands []v1.Command) map[string]v1.Command { + commandMap := make(map[string]v1.Command, len(commands)) + for _, command := range commands { + commandMap[command.Id] = command + } + return commandMap +} + +// GetCommandsFromEvent returns the list of commands from the event name. +// If the event is a composite command, it returns the sub-commands from the tree +func GetCommandsFromEvent(commandsMap map[string]v1.Command, eventName string) []string { + var commands []string + + if command, ok := commandsMap[eventName]; ok { + if command.Composite != nil { + for _, compositeSubCmd := range command.Composite.Commands { + subCommands := GetCommandsFromEvent(commandsMap, compositeSubCmd) + commands = append(commands, subCommands...) + } + } else { + commands = append(commands, command.Id) + } + } + + return commands +} diff --git a/pkg/devfile/parser/data/v2/common/command_helper_test.go b/pkg/devfile/parser/data/v2/common/command_helper_test.go index a4398f9d..62688929 100644 --- a/pkg/devfile/parser/data/v2/common/command_helper_test.go +++ b/pkg/devfile/parser/data/v2/common/command_helper_test.go @@ -331,3 +331,79 @@ func TestGetCommandType(t *testing.T) { } } + +func TestGetCommandsFromEvent(t *testing.T) { + + execCommands := []v1.Command{ + { + Id: "exec1", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{}, + }, + }, + { + Id: "exec2", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{}, + }, + }, + { + Id: "exec3", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{}, + }, + }, + } + + compCommands := []v1.Command{ + { + Id: "comp1", + CommandUnion: v1.CommandUnion{ + Composite: &v1.CompositeCommand{ + Commands: []string{ + "exec1", + "exec3", + }, + }, + }, + }, + } + + commandsMap := map[string]v1.Command{ + compCommands[0].Id: compCommands[0], + execCommands[0].Id: execCommands[0], + execCommands[1].Id: execCommands[1], + execCommands[2].Id: execCommands[2], + } + + tests := []struct { + name string + eventName string + wantCommands []string + }{ + { + name: "composite event", + eventName: "comp1", + wantCommands: []string{ + "exec1", + "exec3", + }, + }, + { + name: "exec event", + eventName: "exec2", + wantCommands: []string{ + "exec2", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + commands := GetCommandsFromEvent(commandsMap, tt.eventName) + if !reflect.DeepEqual(tt.wantCommands, commands) { + t.Errorf("TestGetCommandsFromEvent error - got %v expected %v", commands, tt.wantCommands) + } + }) + } + +} From da359af7df90f62f45e4f3231ac2532525160737 Mon Sep 17 00:00:00 2001 From: Stephanie Date: Thu, 3 Jun 2021 15:23:23 -0400 Subject: [PATCH 4/5] improve readme, add license & tests section to prepare for first release Signed-off-by: Stephanie --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 702eced9..646de177 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Devfile Library + + ## About The Devfile Parser library is a Golang module that: @@ -11,7 +16,7 @@ The Devfile Parser library is a Golang module that: ## Usage The function documentation can be accessed via [pkg.go.dev](https://pkg.go.dev/github.com/devfile/library). -1. To parse a devfile, visit pkg/devfile/parse.go +1. To parse a devfile, visit [parse.go source file](pkg/devfile/parse.go) ```go // ParserArgs is the struct to pass into parser functions which contains required info for parsing devfile. parserArgs := parser.ParserArgs{ @@ -60,7 +65,7 @@ The function documentation can be accessed via [pkg.go.dev](https://pkg.go.dev/g }) ``` -3. To get the Kubernetes objects from the devfile, visit pkg/devfile/generator/generators.go +3. To get the Kubernetes objects from the devfile, visit [generators.go source file](pkg/devfile/generator/generators.go) ```go // To get a slice of Kubernetes containers of type corev1.Container from the devfile component containers containers, err := generator.GetContainers(devfile) @@ -109,7 +114,7 @@ The function documentation can be accessed via [pkg.go.dev](https://pkg.go.dev/g err := devfile.Data.DeleteComponent(componentName) ``` -5. To write to a devfile, visit pkg/devfile/parser/writer.go +5. To write to a devfile, visit [writer.go source file](pkg/devfile/parser/writer.go) ```go // If the devfile object has been created with devfile path already set, can simply call WriteYamlDevfile to writes the devfile err := devfile.WriteYamlDevfile() @@ -159,7 +164,12 @@ The following projects are consuming this library as a Golang dependency * [odo](https://github.com/openshift/odo) * [OpenShift Console](https://github.com/openshift/console) -In the future, [Workspace Operator](https://github.com/devfile/devworkspace-operator) will be the next consumer of devfile/library. +## Tests + +To run unit tests and api tests. Visit [library tests](tests/README.md) to find out more information on tests +``` +make test +``` ## Issues From 8ac33687a4b3a6d7fea682e182a1a00303b6e072 Mon Sep 17 00:00:00 2001 From: Stephanie Date: Thu, 3 Jun 2021 15:42:12 -0400 Subject: [PATCH 5/5] remove commented codes Signed-off-by: Stephanie --- .../parser/data/v2/common/command_helper.go | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/pkg/devfile/parser/data/v2/common/command_helper.go b/pkg/devfile/parser/data/v2/common/command_helper.go index 6d5c91c4..b3bf8a0a 100644 --- a/pkg/devfile/parser/data/v2/common/command_helper.go +++ b/pkg/devfile/parser/data/v2/common/command_helper.go @@ -50,7 +50,7 @@ func GetExecWorkingDir(dc v1.Command) string { return "" } -// GetApplyComponent returns the component of the exec command +// GetApplyComponent returns the component of the apply command func GetApplyComponent(dc v1.Command) string { if dc.Apply != nil { return dc.Apply.Component @@ -59,24 +59,6 @@ func GetApplyComponent(dc v1.Command) string { return "" } -//// GetApplyCommandLine returns the command line of the exec command -//func GetApplyCommandLine(dc v1.Command) string { -// if dc.Apply != nil { -// return dc.Apply -// } -// -// return "" -//} -// -//// GetExecWorkingDir returns the working dir of the exec command -//func GetExecWorkingDir(dc v1.Command) string { -// if dc.Exec != nil { -// return dc.Exec.WorkingDir -// } -// -// return "" -//} - // GetCommandType returns the command type of a given command func GetCommandType(command v1.Command) (v1.CommandType, error) { switch {