Skip to content

Commit

Permalink
Merge pull request #89 from yangcao77/181-generate-initcontainers
Browse files Browse the repository at this point in the history
add GenerateInitContainers function to generator
  • Loading branch information
yangcao77 committed Jun 3, 2021
2 parents bd69960 + 8ac3368 commit 4693e1d
Show file tree
Hide file tree
Showing 5 changed files with 370 additions and 8 deletions.
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Devfile Library

<div id="header">

[![Apache2.0 License](https://img.shields.io/badge/license-Apache2.0-brightgreen.svg)](LICENSE)
</div>

## About

The Devfile Parser library is a Golang module that:
Expand All @@ -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{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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

Expand Down
61 changes: 57 additions & 4 deletions pkg/devfile/generator/generators.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
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"
routev1 "github.com/openshift/api/route/v1"
Expand All @@ -9,10 +14,6 @@ import (
extensionsv1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

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 (
Expand All @@ -27,6 +28,8 @@ const (

deploymentKind = "Deployment"
deploymentAPIVersion = "apps/v1"

containerNameMaxLen = 55
)

// GetTypeMeta gets a type meta of the specified kind and version
Expand Down Expand Up @@ -91,6 +94,56 @@ 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 := common.GetCommandsMap(commands)

for _, event := range preStartEvents {
eventSubCommands := common.GetCommandsFromEvent(commandsMap, event)
eventCommands = append(eventCommands, eventSubCommands...)
}

for i, commandName := range eventCommands {
if command, ok := commandsMap[commandName]; ok {
component := common.GetApplyComponent(command)

// Get the container info for the given component
for _, container := range containers {
if container.Name == component {
// 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-<position of command in prestart events>
// 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
Expand Down
186 changes: 186 additions & 0 deletions pkg/devfile/generator/generators_test.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -487,3 +490,186 @@ 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",
Command: []string{shellExecutable, "-c", "cd execworkdir1 && execcommand1"},
},
},
},
},
{
Name: "container2",
ComponentUnion: v1.ComponentUnion{
Container: &v1.ContainerComponent{
Container: v1.Container{
Image: "container2",
Command: []string{shellExecutable, "-c", "cd execworkdir3 && execcommand3"},
},
},
},
},
}

execCommands := []v1.Command{
{
Id: "apply1",
CommandUnion: v1.CommandUnion{
Apply: &v1.ApplyCommand{
Component: "container1",
},
},
},
{
Id: "apply2",
CommandUnion: v1.CommandUnion{
Apply: &v1.ApplyCommand{
Component: "container1",
},
},
},
{
Id: "apply3",
CommandUnion: v1.CommandUnion{
Apply: &v1.ApplyCommand{
Component: "container2",
},
},
},
}

compCommands := []v1.Command{
{
Id: "comp1",
CommandUnion: v1.CommandUnion{
Composite: &v1.CompositeCommand{
Commands: []string{
"apply1",
"apply3",
},
},
},
},
}

longContainerName := "thisisaverylongcontainerandkuberneteshasalimitforanamesize-exec2"
trimmedLongContainerName := util.TruncateString(longContainerName, containerNameMaxLen)

tests := []struct {
name string
eventCommands []string
wantInitContainer map[string]corev1.Container
longName bool
wantErr bool
}{
{
name: "Composite and Exec events",
eventCommands: []string{
"apply1",
"apply3",
"apply2",
},
wantInitContainer: map[string]corev1.Container{
"container1-apply1": {
Command: []string{shellExecutable, "-c", "cd execworkdir1 && execcommand1"},
},
"container1-apply2": {
Command: []string{shellExecutable, "-c", "cd execworkdir1 && execcommand1"},
},
"container2-apply3": {
Command: []string{shellExecutable, "-c", "cd execworkdir3 && execcommand3"},
},
},
},
{
name: "Long Container Name",
eventCommands: []string{
"apply2",
},
wantInitContainer: map[string]corev1.Container{
trimmedLongContainerName: {
Command: []string{shellExecutable, "-c", "cd execworkdir1 && execcommand1"},
},
},
longName: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

if tt.longName {
containers[0].Name = longContainerName
execCommands[1].Apply.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 !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)
}
}
})
}

}
37 changes: 37 additions & 0 deletions pkg/devfile/parser/data/v2/common/command_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ func GetExecWorkingDir(dc v1.Command) string {
return ""
}

// GetApplyComponent returns the component of the apply command
func GetApplyComponent(dc v1.Command) string {
if dc.Apply != nil {
return dc.Apply.Component
}

return ""
}

// GetCommandType returns the command type of a given command
func GetCommandType(command v1.Command) (v1.CommandType, error) {
switch {
Expand All @@ -66,3 +75,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
}
Loading

0 comments on commit 4693e1d

Please sign in to comment.