Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Added basic PostStart functionality #3283

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
59d6a01
Add Devfile Parser V2, Update Common Structs (#3188)
May 15, 2020
9a5b45e
Fixes command.go and kubernetes adapter (#3194)
mik-dass May 18, 2020
ce9c99e
Fixes utils of k8s adapter (#3195)
mik-dass May 18, 2020
f9bcff9
Update Command Logic to use groups (#3196)
May 18, 2020
0825fcc
Updates create logic to v2 (#3200)
mik-dass May 18, 2020
8147f76
Fixes utils of docker docker adapter (#3198)
mik-dass May 19, 2020
d2553f5
Update Docker and Kubernetes adapter to use groups (#3206)
May 19, 2020
82b979d
Updated Logs for V2 (#3208)
May 19, 2020
cd8d466
Avoid String Pointers (#3209)
May 19, 2020
65841d9
Update commands check
May 19, 2020
b33a348
Fixes lower and upper case for commands (#3219)
mik-dass May 20, 2020
9132a12
Fixes type of project and components for devfile v1 (#3228)
mik-dass May 21, 2020
9d8eac5
Update testing utils (#3220)
May 21, 2020
01d80e1
Design proposal: Event notification support for build and application…
jgwest May 26, 2020
74d0ebc
Fix Integration tests for devfile v2 (#3236)
May 26, 2020
53401ea
Fixes unit tests (#3240)
mik-dass May 27, 2020
935b409
Add devfiles V2 examples (#3253)
May 27, 2020
e558170
Fix regression by sparse checkout dir PR (#3258)
May 28, 2020
bf8fed0
Address review comments (#3267)
May 29, 2020
04838f4
Address review comments part 2
Jun 2, 2020
524e70a
Address review comments part 2
Jun 2, 2020
5d1a072
Added PostStart Exec command functionality
upLukeWeston May 27, 2020
d8d7bb3
minor changes to poststart test
upLukeWeston Jun 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions docs/proposals/event-notification.md

Large diffs are not rendered by default.

207 changes: 119 additions & 88 deletions pkg/devfile/adapters/common/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,140 +7,160 @@ import (
"github.com/openshift/odo/pkg/devfile/parser/data"
"github.com/openshift/odo/pkg/devfile/parser/data/common"
"k8s.io/klog"

"github.com/pkg/errors"
)

// GetCommand iterates through the devfile commands and returns the associated devfile command
func getCommand(data data.DevfileData, commandName string, required bool) (supportedCommand common.DevfileCommand, err error) {
for _, command := range data.GetCommands() {
if command.Name == commandName {

// Get the supported actions
supportedCommandActions, err := getSupportedCommandActions(data, command)

// None of the actions are supported so the command cannot be run
if len(supportedCommandActions) == 0 {
return supportedCommand, errors.Wrapf(err, "\nThe command \"%v\" was found but its actions are not supported", commandName)
} else if err != nil {
klog.Warning(errors.Wrapf(err, "The command \"%v\" was found but some of its actions are not supported", commandName))
func getCommand(data data.DevfileData, commandName string, groupType common.DevfileCommandGroupType) (supportedCommand common.DevfileCommand, err error) {

commands := data.GetCommands()

for _, command := range commands {

// validate command
err = validateCommand(data, command)

if err != nil {
return common.DevfileCommand{}, err
}

// if command is specified via flags, it has the highest priority
// search through all commands to find the specified command name
// if not found fallback to error.
if commandName != "" {
// Update Group only custom commands (specified by odo flags)
command = updateGroupforCommand(groupType, command)

if command.Exec.Id == commandName {
// In the case of lifecycle events, we don't have the type, only the name
// This will verify that if a groupType is passed, the command extracted matches that groupType
if groupType != "" {

if command.Exec.Group.Kind == "" {
// Devfile V1 for commands passed from flags
// Group type is not updated during conversion
command.Exec.Group.Kind = groupType
}

// we have found the command with name, its groupType Should match to the flag
// e.g --build-command "mybuild"
// exec:
// id: mybuild
// group:
// kind: build
if command.Exec.Group.Kind != groupType {
return supportedCommand, fmt.Errorf("mismatched type, command %s is of type %v groupType in devfile", commandName, groupType)

}

}
supportedCommand = command
return supportedCommand, nil
}
continue
}

// The command is supported, use it
supportedCommand.Name = command.Name
supportedCommand.Actions = supportedCommandActions
supportedCommand.Attributes = command.Attributes
// if no command specified via flag, default command has the highest priority
// We need to scan all the commands to find default command
// exec.Group is a pointer, to avoid null pointer
if command.Exec.Group != nil && command.Exec.Group.Kind == groupType && command.Exec.Group.IsDefault {
supportedCommand = command
return supportedCommand, nil
}
}

// The command was not found
msg := fmt.Sprintf("The command \"%v\" was not found in the devfile", commandName)
if required {
// Not found and required, return an error
err = fmt.Errorf(msg)
} else {
// Not found and optional, so just log it
klog.V(3).Info(msg)
}
if commandName == "" {
// if default command is not found return the first command found for the matching type.
for _, command := range commands {

return
}

// getSupportedCommandActions returns the supported actions for a given command and any errors
// If some actions are supported and others have errors both the supported actions and an aggregated error will be returned.
func getSupportedCommandActions(data data.DevfileData, command common.DevfileCommand) (supportedCommandActions []common.DevfileCommandAction, err error) {
klog.V(3).Infof("Validating actions for command: %v ", command.Name)

problemMsg := ""
for i, action := range command.Actions {
// Check if the command action is of type exec
err := validateAction(data, action)
if err == nil {
klog.V(3).Infof("Action %d maps to component %v", i+1, *action.Component)
supportedCommandActions = append(supportedCommandActions, action)
} else {
problemMsg += fmt.Sprintf("Problem with command \"%v\" action #%d: %v", command.Name, i+1, err)
if command.Exec.Group != nil && command.Exec.Group.Kind == groupType {
supportedCommand = command
return supportedCommand, nil
}
}
}

if len(problemMsg) > 0 {
err = fmt.Errorf(problemMsg)
// if any command specified via flag is not found in devfile then it is an error.
if commandName != "" {
err = fmt.Errorf("the command \"%v\" is not found in the devfile", commandName)
} else {
msg := fmt.Sprintf("the command type \"%v\" is not found in the devfile", groupType)
// if run command is not found in devfile then it is an error
if groupType == common.RunCommandGroupType {
err = fmt.Errorf(msg)
} else {
klog.V(4).Info(msg)
}
}

return
}

// validateAction validates the given action
// 1. action has to be of type exec
// validateCommand validates the given command
// 1. command has to be of type exec
// 2. component should be present
// 3. command should be present
func validateAction(data data.DevfileData, action common.DevfileCommandAction) (err error) {
// 4. command must have group
func validateCommand(data data.DevfileData, command common.DevfileCommand) (err error) {

// type must be exec
if *action.Type != common.DevfileCommandTypeExec {
return fmt.Errorf("Actions must be of type \"exec\"")
if command.Exec == nil {
return fmt.Errorf("command must be of type \"exec\"")
}

// component must be specified
if action.Component == nil || *action.Component == "" {
return fmt.Errorf("Actions must reference a component")
if command.Exec.Component == "" {
return fmt.Errorf("exec commands must reference a component")
}

// must specify a command
if action.Command == nil || *action.Command == "" {
return fmt.Errorf("Actions must have a command")
if command.Exec.CommandLine == "" {
return fmt.Errorf("exec commands must have a command")
}

// must map to a supported component
components := GetSupportedComponents(data)

isActionValid := false
isComponentValid := false
for _, component := range components {
if *action.Component == *component.Alias && isComponentSupported(component) {
isActionValid = true
if command.Exec.Component == component.Container.Name {
isComponentValid = true
}
}
if !isActionValid {
return fmt.Errorf("The action does not map to a supported component")
if !isComponentValid {
return fmt.Errorf("the command does not map to a supported component")
}

return
}

// GetInitCommand iterates through the components in the devfile and returns the init command
func GetInitCommand(data data.DevfileData, devfileInitCmd string) (initCommand common.DevfileCommand, err error) {
if devfileInitCmd != "" {
// a init command was specified so if it is not found then it is an error
return getCommand(data, devfileInitCmd, true)
}
// a init command was not specified so if it is not found then it is not an error
return getCommand(data, string(DefaultDevfileInitCommand), false)

return getCommand(data, devfileInitCmd, common.InitCommandGroupType)
}

func GetCommandByName(data data.DevfileData, postStartCommand string) (command common.DevfileCommand, err error) {
return getCommand(data, postStartCommand, "")
}

// GetBuildCommand iterates through the components in the devfile and returns the build command
func GetBuildCommand(data data.DevfileData, devfileBuildCmd string) (buildCommand common.DevfileCommand, err error) {
if devfileBuildCmd != "" {
// a build command was specified so if it is not found then it is an error
return getCommand(data, devfileBuildCmd, true)
}
// a build command was not specified so if it is not found then it is not an error
return getCommand(data, string(DefaultDevfileBuildCommand), false)

return getCommand(data, devfileBuildCmd, common.BuildCommandGroupType)
}

// GetRunCommand iterates through the components in the devfile and returns the run command
func GetRunCommand(data data.DevfileData, devfileRunCmd string) (runCommand common.DevfileCommand, err error) {
if devfileRunCmd != "" {
return getCommand(data, devfileRunCmd, true)
}
return getCommand(data, string(DefaultDevfileRunCommand), true)

return getCommand(data, devfileRunCmd, common.RunCommandGroupType)
}

// ValidateAndGetPushDevfileCommands validates the build and the run command,
// if provided through odo push or else checks the devfile for devBuild and devRun.
// It returns the build and run commands if its validated successfully, error otherwise.
func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, devfileBuildCmd, devfileRunCmd string) (pushDevfileCommands []common.DevfileCommand, err error) {
func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, devfileBuildCmd, devfileRunCmd string) (commandMap PushCommandsMap, err error) {
var emptyCommand common.DevfileCommand
commandMap = NewPushCommandMap()

isInitCommandValid, isBuildCommandValid, isRunCommandValid := false, false, false

initCommand, initCmdErr := GetInitCommand(data, devfileInitCmd)
Expand All @@ -149,11 +169,11 @@ func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, de
if isInitCmdEmpty && initCmdErr == nil {
// If there was no init command specified through odo push and no default init command in the devfile, default validate to true since the init command is optional
isInitCommandValid = true
klog.V(3).Infof("No init command was provided")
klog.V(4).Infof("No init command was provided")
} else if !isInitCmdEmpty && initCmdErr == nil {
isInitCommandValid = true
pushDevfileCommands = append(pushDevfileCommands, initCommand)
klog.V(3).Infof("Init command: %v", initCommand.Name)
commandMap[common.InitCommandGroupType] = initCommand
klog.V(4).Infof("Init command: %v", initCommand.Exec.Id)
}

buildCommand, buildCmdErr := GetBuildCommand(data, devfileBuildCmd)
Expand All @@ -162,18 +182,18 @@ func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, de
if isBuildCmdEmpty && buildCmdErr == nil {
// If there was no build command specified through odo push and no default build command in the devfile, default validate to true since the build command is optional
isBuildCommandValid = true
klog.V(3).Infof("No build command was provided")
klog.V(4).Infof("No build command was provided")
} else if !reflect.DeepEqual(emptyCommand, buildCommand) && buildCmdErr == nil {
isBuildCommandValid = true
pushDevfileCommands = append(pushDevfileCommands, buildCommand)
klog.V(3).Infof("Build command: %v", buildCommand.Name)
commandMap[common.BuildCommandGroupType] = buildCommand
klog.V(4).Infof("Build command: %v", buildCommand.Exec.Id)
}

runCommand, runCmdErr := GetRunCommand(data, devfileRunCmd)
if runCmdErr == nil && !reflect.DeepEqual(emptyCommand, runCommand) {
pushDevfileCommands = append(pushDevfileCommands, runCommand)
isRunCommandValid = true
klog.V(3).Infof("Run command: %v", runCommand.Name)
commandMap[common.RunCommandGroupType] = runCommand
klog.V(4).Infof("Run command: %v", runCommand.Exec.Id)
}

// If either command had a problem, return an empty list of commands and an error
Expand All @@ -188,8 +208,19 @@ func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, de
if runCmdErr != nil {
commandErrors += fmt.Sprintf(runCmdErr.Error(), "\n")
}
return []common.DevfileCommand{}, fmt.Errorf(commandErrors)
return commandMap, fmt.Errorf(commandErrors)
}

return pushDevfileCommands, nil
return commandMap, nil
}

// Need to update group on custom commands specified by odo flags
func updateGroupforCommand(groupType common.DevfileCommandGroupType, command common.DevfileCommand) common.DevfileCommand {
// Update Group only for exec commands
// Update Group only when Group is not nil, devfile v2 might contain group for custom commands.
if command.Exec != nil && command.Exec.Group == nil && groupType != "" {
command.Exec.Group = &common.Group{Kind: groupType}
return command
}
return command
}
Loading