From 59d6a018b804df06d9b2fa71ddf64f9e0dbcc749 Mon Sep 17 00:00:00 2001 From: Aditi Sharma Date: Fri, 15 May 2020 13:39:44 +0530 Subject: [PATCH 01/23] Add Devfile Parser V2, Update Common Structs (#3188) * Add Devfile Parser for Version 2.0.0 Added Devfile V2 Go Structures Added converter for v2 ro common types Signed-off-by: adisky * Add example V2 devfile added example nodejs V2 devfile * Add Common Types as V2 Add common types as v2 Add Converter from common to v1 * Updated example devfile with group --- pkg/devfile/parser/context/apiVersion.go | 33 +- pkg/devfile/parser/context/apiVersion_test.go | 2 +- pkg/devfile/parser/data/1.0.0/components.go | 176 +++- pkg/devfile/parser/data/1.0.0/types.go | 225 ++++- pkg/devfile/parser/data/2.0.0/components.go | 41 + .../parser/data/2.0.0/devfileJsonSchema200.go | 864 ++++++++++++++++++ pkg/devfile/parser/data/2.0.0/types.go | 485 ++++++++++ pkg/devfile/parser/data/common/types.go | 514 ++++++++--- pkg/devfile/parser/data/interface.go | 3 + pkg/devfile/parser/data/versions.go | 8 +- pkg/devfile/validate/components.go | 17 +- pkg/devfile/validate/validate.go | 27 +- .../source/devfiles/nodejs/devfileV2.yaml | 39 + 13 files changed, 2254 insertions(+), 180 deletions(-) create mode 100644 pkg/devfile/parser/data/2.0.0/components.go create mode 100644 pkg/devfile/parser/data/2.0.0/devfileJsonSchema200.go create mode 100644 pkg/devfile/parser/data/2.0.0/types.go create mode 100644 tests/examples/source/devfiles/nodejs/devfileV2.yaml diff --git a/pkg/devfile/parser/context/apiVersion.go b/pkg/devfile/parser/context/apiVersion.go index 68f4e1fd12b..8f37641482b 100644 --- a/pkg/devfile/parser/context/apiVersion.go +++ b/pkg/devfile/parser/context/apiVersion.go @@ -19,19 +19,34 @@ func (d *DevfileCtx) SetDevfileAPIVersion() error { return errors.Wrapf(err, "failed to decode devfile json") } - // Get "apiVersion" value from the map - apiVersion, ok := r["apiVersion"] - if !ok { - return fmt.Errorf("apiVersion not present in devfile") - } + var apiVer string + + // Get "apiVersion" value from map for devfile V1 + apiVersion, okApi := r["apiVersion"] + + // Get "schemaVersion" value from map for devfile V2 + schemaVersion, okSchema := r["schemaVersion"] + + if okApi { + apiVer = apiVersion.(string) + // apiVersion cannot be empty + if apiVer == "" { + return fmt.Errorf("apiVersion in devfile cannot be empty") + } + + } else if okSchema { + apiVer = schemaVersion.(string) + // SchemaVersion cannot be empty + if schemaVersion.(string) == "" { + return fmt.Errorf("schemaVersion in devfile cannot be empty") + } + } else { + return fmt.Errorf("apiVersion or schemaVersion not present in devfile") - // apiVersion cannot be empty - if apiVersion.(string) == "" { - return fmt.Errorf("apiVersion in devfile cannot be empty") } // Successful - d.apiVersion = apiVersion.(string) + d.apiVersion = apiVer klog.V(4).Infof("devfile apiVersion: '%s'", d.apiVersion) return nil } diff --git a/pkg/devfile/parser/context/apiVersion_test.go b/pkg/devfile/parser/context/apiVersion_test.go index ad708cdcc4a..904809637e7 100644 --- a/pkg/devfile/parser/context/apiVersion_test.go +++ b/pkg/devfile/parser/context/apiVersion_test.go @@ -32,7 +32,7 @@ func TestSetDevfileAPIVersion(t *testing.T) { name: "apiVersion not present", rawJson: []byte(emptyJson), want: "", - wantErr: fmt.Errorf("apiVersion not present in devfile"), + wantErr: fmt.Errorf("apiVersion or schemaVersion not present in devfile"), }, { name: "apiVersion empty", diff --git a/pkg/devfile/parser/data/1.0.0/components.go b/pkg/devfile/parser/data/1.0.0/components.go index 2fd71de403d..b27b5c1afdd 100644 --- a/pkg/devfile/parser/data/1.0.0/components.go +++ b/pkg/devfile/parser/data/1.0.0/components.go @@ -1,21 +1,37 @@ package version100 import ( - "strings" - "github.com/openshift/odo/pkg/devfile/parser/data/common" ) -// GetComponents returns the slice of DevfileComponent objects parsed from the Devfile +func (d *Devfile100) GetMetadata() common.DevfileMetadata { + // No GenerateName field in V2 + return common.DevfileMetadata{ + Name: *d.Metadata.Name, + //Version: No field in V1 + } +} + +/// GetComponents returns the slice of DevfileComponent objects parsed from the Devfile func (d *Devfile100) GetComponents() []common.DevfileComponent { - return d.Components + var comps []common.DevfileComponent + for _, v := range d.Components { + comps = append(comps, convertV1ComponentToCommon(v)) + } + return comps } // GetAliasedComponents returns the slice of DevfileComponent objects that each have an alias func (d *Devfile100) GetAliasedComponents() []common.DevfileComponent { + // TODO(adi): we might not need this for V2 as name is a required field now. + var comps []common.DevfileComponent + for _, v := range d.Components { + comps = append(comps, convertV1ComponentToCommon(v)) + } + var aliasedComponents = []common.DevfileComponent{} - for _, comp := range d.Components { - if comp.Alias != nil { + for _, comp := range comps { + if comp.Container.Name != "" { aliasedComponents = append(aliasedComponents, comp) } } @@ -24,17 +40,157 @@ func (d *Devfile100) GetAliasedComponents() []common.DevfileComponent { // GetProjects returns the slice of DevfileProject objects parsed from the Devfile func (d *Devfile100) GetProjects() []common.DevfileProject { - return d.Projects + + var projects []common.DevfileProject + for _, v := range d.Projects { + // We are only supporting ProjectType git in V1 + if v.Source.Type == ProjectTypeGit { + projects = append(projects, convertV1ProjectToCommon(v)) + } + } + + return projects } // GetCommands returns the slice of DevfileCommand objects parsed from the Devfile func (d *Devfile100) GetCommands() []common.DevfileCommand { + var commands []common.DevfileCommand + for _, v := range d.Commands { + cmd := convertV1CommandToCommon(v) - for _, command := range d.Commands { - command.Name = strings.ToLower(command.Name) - commands = append(commands, command) + commands = append(commands, cmd) } return commands } + +func (d *Devfile100) GetParent() common.DevfileParent { + return common.DevfileParent{} + +} + +func (d *Devfile100) GetEvents() common.DevfileEvents { + return common.DevfileEvents{} + +} + +func convertV1CommandToCommon(c Command) (d common.DevfileCommand) { + var exec common.Exec + + for _, action := range c.Actions { + + if *action.Type == DevfileCommandTypeExec { + exec = common.Exec{ + Attributes: c.Attributes, + CommandLine: *action.Command, + Component: *action.Component, + Group: getGroup(c.Name), + Id: c.Name, + WorkingDir: action.Workdir, + // Env: + // Label: + } + } + + } + + // TODO: Previewurl + return common.DevfileCommand{ + //TODO(adi): Type + Exec: &exec, + } +} + +func convertV1ComponentToCommon(c Component) (d common.DevfileComponent) { + + var endpoints []*common.Endpoint + for _, v := range c.ComponentDockerimage.Endpoints { + endpoints = append(endpoints, convertV1EndpointsToCommon(v)) + } + + var envs []*common.Env + for _, v := range c.ComponentDockerimage.Env { + envs = append(envs, convertV1EnvToCommon(v)) + } + + var volumes []*common.VolumeMount + for _, v := range c.ComponentDockerimage.Volumes { + volumes = append(volumes, convertV1VolumeToCommon(v)) + } + + container := common.Container{ + Name: *c.Alias, + Endpoints: endpoints, + Env: envs, + Image: *c.ComponentDockerimage.Image, + MemoryLimit: *c.ComponentDockerimage.MemoryLimit, + MountSources: c.MountSources, + VolumeMounts: volumes, + // SourceMapping: Not present in V1 + } + + d = common.DevfileComponent{Container: &container} + + return d +} + +func convertV1EndpointsToCommon(e DockerimageEndpoint) *common.Endpoint { + return &common.Endpoint{ + // Attributes: + // Configuration: + Name: *e.Name, + TargetPort: *e.Port, + } +} + +func convertV1EnvToCommon(e DockerimageEnv) *common.Env { + return &common.Env{ + Name: *e.Name, + Value: *e.Value, + } +} + +func convertV1VolumeToCommon(v DockerimageVolume) *common.VolumeMount { + return &common.VolumeMount{ + Name: *v.Name, + Path: *v.ContainerPath, + } +} + +func convertV1ProjectToCommon(p Project) common.DevfileProject { + + git := common.Git{ + Branch: *p.Source.Branch, + Location: p.Source.Location, + SparseCheckoutDir: *p.Source.SparseCheckoutDir, + StartPoint: *p.Source.StartPoint, + } + + return common.DevfileProject{ + ClonePath: p.ClonePath, + Name: p.Name, + Git: &git, + } + +} + +func getGroup(name string) *common.Group { + var kind common.DevfileCommandGroupType + + switch name { + case "devRun": + kind = common.RunCommandGroupType + case "devBuild": + kind = common.BuildCommandGroupType + case "devInit": + kind = common.InitCommandGroupType + default: + kind = "" + } + + return &common.Group{ + // TODO(adi): IsDefault: + Kind: kind, + } +} diff --git a/pkg/devfile/parser/data/1.0.0/types.go b/pkg/devfile/parser/data/1.0.0/types.go index 75ee4461f4b..af9774f81ca 100644 --- a/pkg/devfile/parser/data/1.0.0/types.go +++ b/pkg/devfile/parser/data/1.0.0/types.go @@ -1,26 +1,231 @@ package version100 -import ( - "github.com/openshift/odo/pkg/devfile/parser/data/common" -) - // Devfile100 struct maps to devfile 1.0.0 version schema type Devfile100 struct { // Devfile section "apiVersion" - ApiVersion common.ApiVersion `yaml:"apiVersion" json:"apiVersion"` + ApiVersion ApiVersion `yaml:"apiVersion" json:"apiVersion"` // Devfile section "metadata" - Metadata common.DevfileMetadata `yaml:"metadata" json:"metadata"` + Metadata Metadata `yaml:"metadata" json:"metadata"` // Devfile section projects - Projects []common.DevfileProject `yaml:"projects,omitempty" json:"projects,omitempty"` + Projects []Project `yaml:"projects,omitempty" json:"projects,omitempty"` - Attributes common.Attributes `yaml:"attributes,omitempty" json:"attributes,omitempty"` + Attributes Attributes `yaml:"attributes,omitempty" json:"attributes,omitempty"` // Description of the workspace components, such as editor and plugins - Components []common.DevfileComponent `yaml:"components,omitempty" json:"components,omitempty"` + Components []Component `yaml:"components,omitempty" json:"components,omitempty"` // Description of the predefined commands to be available in workspace - Commands []common.DevfileCommand `yaml:"commands,omitempty" json:"commands,omitempty"` + Commands []Command `yaml:"commands,omitempty" json:"commands,omitempty"` +} + +// -------------- Supported devfile project types ------------ // +// DevfileProjectType store valid devfile project types +type ProjectType string + +const ( + ProjectTypeGit ProjectType = "git" +) + +var SupportedProjectTypes = []ProjectType{ProjectTypeGit} + +// -------------- Supported devfile component types ------------ // +// DevfileComponentType stores valid devfile component types +type ComponentType string + +const ( + DevfileComponentTypeCheEditor ComponentType = "cheEditor" + DevfileComponentTypeChePlugin ComponentType = "chePlugin" + DevfileComponentTypeDockerimage ComponentType = "dockerimage" + DevfileComponentTypeKubernetes ComponentType = "kubernetes" + DevfileComponentTypeOpenshift ComponentType = "openshift" +) + +// -------------- Supported devfile command types ------------ // +type CommandType string + +const ( + DevfileCommandTypeInit CommandType = "init" + DevfileCommandTypeBuild CommandType = "build" + DevfileCommandTypeRun CommandType = "run" + DevfileCommandTypeDebug CommandType = "debug" + DevfileCommandTypeExec CommandType = "exec" +) + +// ----------- Devfile Schema ---------- // +type Attributes map[string]string + +type ApiVersion string + +type Metadata struct { + + // Workspaces created from devfile, will use it as base and append random suffix. + // It's used when name is not defined. + GenerateName *string `yaml:"generateName,omitempty" json:"generateName,omitempty"` + + // The name of the devfile. Workspaces created from devfile, will inherit this + // name + Name *string `yaml:"name,omitempty" json:"name,omitempty"` +} + +// Description of the projects, containing names and sources locations +type Project struct { + + // The path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name." + ClonePath *string `yaml:"clonePath,omitempty" json:"clonePath,omitempty"` + + // The Project Name + Name string `yaml:"name" json:"name"` + + // Describes the project's source - type and location + Source ProjectSource `yaml:"source" json:"source"` +} + +type ProjectSource struct { + Type ProjectType `yaml:"type" json:"type"` + + // Project's source location address. Should be URL for git and github located projects" + Location string `yaml:"location" json:"location"` + + // The name of the of the branch to check out after obtaining the source from the location. + // The branch has to already exist in the source otherwise the default branch is used. + // In case of git, this is also the name of the remote branch to push to. + Branch *string `yaml:"branch,omitempty" json:"branch,omitempty"` + + // The id of the commit to reset the checked out branch to. + // Note that this is equivalent to 'startPoint' and provided for convenience. + CommitId *string `yaml:"commitId,omitempty" json:"commitId,omitempty"` + + // Part of project to populate in the working directory. + SparseCheckoutDir *string `yaml:"sparseCheckoutDir,omitempty" json:"sparseCheckoutDir,omitempty"` + + // The tag or commit id to reset the checked out branch to. + StartPoint *string `yaml:"startPoint,omitempty" json:"startPoint,omitempty"` + + // The name of the tag to reset the checked out branch to. + // Note that this is equivalent to 'startPoint' and provided for convenience. + Tag *string `yaml:"tag,omitempty" json:"tag,omitempty"` +} + +type Command struct { + + // List of the actions of given command. Now the only one command must be + // specified in list but there are plans to implement supporting multiple actions + // commands. + Actions []CommandAction `yaml:"actions" json:"actions"` + + // Additional command attributes + Attributes Attributes `yaml:"attributes,omitempty" json:"attributes,omitempty"` + + // Describes the name of the command. Should be unique per commands set. + Name string `yaml:"name"` + + // Preview url + PreviewUrl CommandPreviewUrl `yaml:"previewUrl,omitempty" json:"previewUrl,omitempty"` +} + +type CommandPreviewUrl struct { + Port *int32 `yaml:"port,omitempty" json:"port,omitempty"` + Path *string `yaml:"path,omitempty" json:"path,omitempty"` +} + +type CommandAction struct { + + // The actual action command-line string + Command *string `yaml:"command,omitempty" json:"command,omitempty"` + + // Describes component to which given action relates + Component *string `yaml:"component,omitempty" json:"component,omitempty"` + + // the path relative to the location of the devfile to the configuration file + // defining one or more actions in the editor-specific format + Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` + + // The content of the referenced configuration file that defines one or more + // actions in the editor-specific format + ReferenceContent *string `yaml:"referenceContent,omitempty" json:"referenceContent,omitempty"` + + // Describes action type + Type *CommandType `yaml:"type,omitempty" json:"type,omitempty"` + + // Working directory where the command should be executed + Workdir *string `yaml:"workdir,omitempty" json:"workdir,omitempty"` +} + +type Component struct { + + // The name using which other places of this devfile (like commands) can refer to + // this component. This attribute is optional but must be unique in the devfile if + // specified. + Alias *string `yaml:"alias,omitempty" json:"alias,omitempty"` + + // Describes whether projects sources should be mount to the component. + // `CHE_PROJECTS_ROOT` environment variable should contains a path where projects + // sources are mount + MountSources bool `yaml:"mountSources,omitempty" json:"mountSources,omitempty"` + + // Describes type of the component, e.g. whether it is an plugin or editor or + // other type + Type ComponentType `yaml:"type" json:"type"` + + // for type ChePlugin + ComponentChePlugin `yaml:",inline" json:",inline"` + + // for type=dockerfile + ComponentDockerimage `yaml:",inline" json:",inline"` +} + +type ComponentChePlugin struct { + Id *string `yaml:"id,omitempty" json:"id,omitempty"` + Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` + RegistryUrl *string `yaml:"registryUrl,omitempty" json:"registryUrl,omitempty"` +} + +type ComponentCheEditor struct { + Id *string `yaml:"id,omitempty" json:"id,omitempty"` + Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` + RegistryUrl *string `yaml:"registryUrl,omitempty" json:"registryUrl,omitempty"` +} + +type ComponentOpenshift struct { + Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` + ReferenceContent *string `yaml:"referenceContent,omitempty" json:"referenceContent,omitempty"` + Selector *string `yaml:"selector,omitempty" json:"selector,omitempty"` + EntryPoints *string `yaml:"entryPoints,omitempty" json:"entryPoints,omitempty"` + MemoryLimit *string `yaml:"memoryLimit,omitempty" json:"memoryLimit,omitempty"` +} + +type ComponentKubernetes struct { + Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` + ReferenceContent *string `yaml:"referenceContent,omitempty" json:"referenceContent,omitempty"` + Selector *string `yaml:"selector,omitempty" json:"selector,omitempty"` + EntryPoints *string `yaml:"entryPoints,omitempty" json:"entryPoints,omitempty"` + MemoryLimit *string `yaml:"memoryLimit,omitempty" json:"memoryLimit,omitempty"` +} + +type ComponentDockerimage struct { + Image *string `yaml:"image,omitempty" json:"image,omitempty"` + MemoryLimit *string `yaml:"memoryLimit,omitempty" json:"memoryLimit,omitempty"` + Command []string `yaml:"command,omitempty" json:"command,omitempty"` + Args []string `yaml:"args,omitempty" json:"args,omitempty"` + Volumes []DockerimageVolume `yaml:"volumes,omitempty" json:"volumes,omitempty"` + Env []DockerimageEnv `yaml:"env,omitempty" json:"env,omitempty"` + Endpoints []DockerimageEndpoint `yaml:"endpoints,omitempty" json:"endpoints,omitempty"` +} + +type DockerimageVolume struct { + Name *string `yaml:"name,omitempty" json:"name,omitempty"` + ContainerPath *string `yaml:"containerPath,omitempty" json:"containerPath,omitempty"` +} + +type DockerimageEnv struct { + Name *string `yaml:"name,omitempty" json:"name,omitempty"` + Value *string `yaml:"value,omitempty" json:"value,omitempty"` +} + +type DockerimageEndpoint struct { + Name *string `yaml:"name,omitempty" json:"name,omitempty"` + Port *int32 `yaml:"port,omitempty" json:"port,omitempty"` } diff --git a/pkg/devfile/parser/data/2.0.0/components.go b/pkg/devfile/parser/data/2.0.0/components.go new file mode 100644 index 00000000000..986eae361e2 --- /dev/null +++ b/pkg/devfile/parser/data/2.0.0/components.go @@ -0,0 +1,41 @@ +package version200 + +import ( + "github.com/openshift/odo/pkg/devfile/parser/data/common" +) + +// GetComponents returns the slice of DevfileComponent objects parsed from the Devfile +func (d *Devfile200) GetComponents() []common.DevfileComponent { + return d.Components +} + +// GetCommands returns the slice of DevfileCommand objects parsed from the Devfile +func (d *Devfile200) GetCommands() []common.DevfileCommand { + return d.Commands +} + +// GetParent returns the DevfileParent object parsed from devfile +func (d *Devfile200) GetParent() common.DevfileParent { + return d.Parent +} + +// GetProject returns the DevfileProject Object parsed from devfile +func (d *Devfile200) GetProject() []common.DevfileProject { + return d.Projects +} + +// GetMetadata returns the DevfileMetadata Object parsed from devfile +func (d *Devfile200) GetMetadata() common.DevfileMetadata { + return d.Metadata +} + +// GetEvents returns the Events Object parsed from devfile +func (d *Devfile200) GetEvents() common.DevfileEvents { + return d.Events +} + +// GetAliasedComponents returns the slice of DevfileComponent objects that each have an alias +func (d *Devfile200) GetAliasedComponents() []common.DevfileComponent { + // V2 has name required in jsonSchema + return d.Components +} diff --git a/pkg/devfile/parser/data/2.0.0/devfileJsonSchema200.go b/pkg/devfile/parser/data/2.0.0/devfileJsonSchema200.go new file mode 100644 index 00000000000..4becd5a291c --- /dev/null +++ b/pkg/devfile/parser/data/2.0.0/devfileJsonSchema200.go @@ -0,0 +1,864 @@ +package version200 + +const JsonSchema200 = `{ + "description": "Devfile schema.", + "properties": { + "commands": { + "description": "Predefined, ready-to-use, workspace-related commands", + "items": { + "properties": { + "composite": { + "description": "Composite command", + "properties": { + "attributes": { + "additionalProperties": { + "type": "string" + }, + "description": "Optional map of free-form additional command attributes", + "type": "object" + }, + "commands": { + "description": "The commands that comprise this composite command", + "items": { + "type": "string" + }, + "type": "array" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": [ + "build", + "run", + "test", + "debug" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, or from a parent, or in events.", + "type": "string" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "type": "boolean" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "custom": { + "description": "Custom command", + "properties": { + "attributes": { + "additionalProperties": { + "type": "string" + }, + "description": "Optional map of free-form additional command attributes", + "type": "object" + }, + "commandClass": { + "type": "string" + }, + "embeddedResource": { + "type": "object" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": [ + "build", + "run", + "test", + "debug" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, or from a parent, or in events.", + "type": "string" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "required": [ + "commandClass", + "embeddedResource", + "id" + ], + "type": "object" + }, + "exec": { + "description": "Exec command", + "properties": { + "attributes": { + "additionalProperties": { + "type": "string" + }, + "description": "Optional map of free-form additional command attributes", + "type": "object" + }, + "commandLine": { + "description": "The actual command-line string", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": [ + "build", + "run", + "test", + "debug" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, or from a parent, or in events.", + "type": "string" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed", + "type": "string" + } + }, + "required": [ + "commandLine", + "id" + ], + "type": "object" + }, + "type": { + "description": "Type of workspace command", + "enum": [ + "Exec", + "VscodeTask", + "VscodeLaunch", + "Custom" + ], + "type": "string" + }, + "vscodeLaunch": { + "description": "VscodeLaunch command", + "properties": { + "attributes": { + "additionalProperties": { + "type": "string" + }, + "description": "Optional map of free-form additional command attributes", + "type": "object" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": [ + "build", + "run", + "test", + "debug" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, or from a parent, or in events.", + "type": "string" + }, + "inlined": { + "description": "Embedded content of the vscode configuration file", + "type": "string" + }, + "locationType": { + "description": "Type of Vscode configuration command location", + "type": "string" + }, + "url": { + "description": "Location as an absolute of relative URL", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "vscodeTask": { + "description": "VscodeTask command", + "properties": { + "attributes": { + "additionalProperties": { + "type": "string" + }, + "description": "Optional map of free-form additional command attributes", + "type": "object" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": [ + "build", + "run", + "test", + "debug" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, or from a parent, or in events.", + "type": "string" + }, + "inlined": { + "description": "Embedded content of the vscode configuration file", + "type": "string" + }, + "locationType": { + "description": "Type of Vscode configuration command location", + "type": "string" + }, + "url": { + "description": "Location as an absolute of relative URL", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "components": { + "description": "List of the workspace components, such as editor and plugins, user-provided containers, or other types of components", + "items": { + "properties": { + "cheEditor": { + "description": "CheEditor component", + "properties": { + "locationType": { + "description": "Type of plugin location", + "enum": [ + "RegistryEntry", + "Uri" + ], + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "name": { + "description": "Optional name that allows referencing the component in commands, or inside a parent If omitted it will be infered from the location (uri or registryEntry)", + "type": "string" + }, + "registryEntry": { + "description": "Location of an entry inside a plugin registry", + "properties": { + "baseUrl": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "uri": { + "description": "Location defined as an URI", + "type": "string" + } + }, + "type": "object" + }, + "chePlugin": { + "description": "ChePlugin component", + "properties": { + "locationType": { + "description": "Type of plugin location", + "enum": [ + "RegistryEntry", + "Uri" + ], + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "name": { + "description": "Optional name that allows referencing the component in commands, or inside a parent If omitted it will be infered from the location (uri or registryEntry)", + "type": "string" + }, + "registryEntry": { + "description": "Location of an entry inside a plugin registry", + "properties": { + "baseUrl": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "uri": { + "description": "Location defined as an URI", + "type": "string" + } + }, + "type": "object" + }, + "container": { + "description": "Container component", + "properties": { + "endpoints": { + "items": { + "properties": { + "attributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "configuration": { + "properties": { + "cookiesAuthEnabled": { + "type": "boolean" + }, + "discoverable": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "protocol": { + "description": "The is the low-level protocol of traffic coming through this endpoint. Default value is \"tcp\"", + "type": "string" + }, + "public": { + "type": "boolean" + }, + "scheme": { + "description": "The is the URL scheme to use when accessing the endpoint. Default value is \"http\"", + "type": "string" + }, + "secure": { + "type": "boolean" + }, + "type": { + "enum": [ + "ide", + "terminal", + "ide-dev" + ], + "type": "string" + } + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "targetPort": { + "type": "integer" + } + }, + "required": [ + "configuration", + "name", + "targetPort" + ], + "type": "object" + }, + "type": "array" + }, + "env": { + "description": "Environment variables used in this container", + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "mountSources": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when mountSources is true. When omitted, the value of the PROJECTS_ROOT environment variable is used.", + "type": "string" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "items": { + "description": "Volume that should be mounted to a component container", + "properties": { + "name": { + "description": "The volume mount name is the name of an existing Volume component. If no corresponding Volume component exist it is implicitly added. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "type": "string" + }, + "path": { + "description": "The path in the component container where the volume should be mounted", + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "image", + "name" + ], + "type": "object" + }, + "custom": { + "description": "Custom component", + "properties": { + "componentClass": { + "type": "string" + }, + "embeddedResource": { + "type": "object" + }, + "name": { + "type": "string" + } + }, + "required": [ + "componentClass", + "embeddedResource", + "name" + ], + "type": "object" + }, + "kubernetes": { + "description": "Kubernetes component", + "properties": { + "inlined": { + "description": "Reference to the plugin definition", + "type": "string" + }, + "locationType": { + "description": "Type of Kubernetes-like location", + "type": "string" + }, + "name": { + "description": "Mandatory name that allows referencing the component in commands, or inside a parent", + "type": "string" + }, + "url": { + "description": "Location in a plugin registry", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "openshift": { + "description": "Openshift component", + "properties": { + "inlined": { + "description": "Reference to the plugin definition", + "type": "string" + }, + "locationType": { + "description": "Type of Kubernetes-like location", + "type": "string" + }, + "name": { + "description": "Mandatory name that allows referencing the component in commands, or inside a parent", + "type": "string" + }, + "url": { + "description": "Location in a plugin registry", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": { + "description": "Type of project source", + "enum": [ + "Container", + "Kubernetes", + "Openshift", + "CheEditor", + "Volume", + "ChePlugin", + "Custom" + ], + "type": "string" + }, + "volume": { + "description": "Volume component", + "properties": { + "name": { + "description": "Mandatory name that allows referencing the Volume component in Container volume mounts or inside a parent", + "type": "string" + }, + "size": { + "description": "Size of the volume", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "events": { + "description": "Bindings of commands to events. Each command is referred-to by its name.", + "properties": { + "postStart": { + "description": "Names of commands that should be executed after the workspace is completely started. In the case of Che-Theia, these commands should be executed after all plugins and extensions have started, including project cloning. This means that those commands are not triggered until the user opens the IDE in his browser.", + "items": { + "type": "string" + }, + "type": "array" + }, + "postStop": { + "description": "Names of commands that should be executed after stopping the workspace.", + "items": { + "type": "string" + }, + "type": "array" + }, + "preStart": { + "description": "Names of commands that should be executed before the workspace start. Kubernetes-wise, these commands would typically be executed in init containers of the workspace POD.", + "items": { + "type": "string" + }, + "type": "array" + }, + "preStop": { + "description": "Names of commands that should be executed before stopping the workspace.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "parent": { + "description": "Parent workspace template", + "properties": { + "kubernetes": { + "description": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "locationType": { + "description": "Type of parent location", + "enum": [ + "Uri", + "RegistryEntry", + "Kubernetes" + ], + "type": "string" + }, + "registryEntry": { + "description": "Entry in a registry (base URL + ID) that contains a Devfile yaml file", + "properties": { + "baseUrl": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "uri": { + "description": "Uri of a Devfile yaml file", + "type": "string" + } + }, + "type": "object" + }, + "projects": { + "description": "Projects worked on in the workspace, containing names and sources locations", + "items": { + "properties": { + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string" + }, + "custom": { + "description": "Project's Custom source", + "properties": { + "embeddedResource": { + "type": "object" + }, + "projectSourceClass": { + "type": "string" + } + }, + "required": [ + "embeddedResource", + "projectSourceClass" + ], + "type": "object" + }, + "git": { + "description": "Project's Git source", + "properties": { + "branch": { + "description": "The branch to check", + "type": "string" + }, + "location": { + "description": "Project's source location address. Should be URL for git and github located projects, or; file:// for zip", + "type": "string" + }, + "sparseCheckoutDir": { + "description": "Part of project to populate in the working directory.", + "type": "string" + }, + "startPoint": { + "description": "The tag or commit id to reset the checked out branch to", + "type": "string" + } + }, + "required": [ + "location" + ], + "type": "object" + }, + "github": { + "description": "Project's GitHub source", + "properties": { + "branch": { + "description": "The branch to check", + "type": "string" + }, + "location": { + "description": "Project's source location address. Should be URL for git and github located projects, or; file:// for zip", + "type": "string" + }, + "sparseCheckoutDir": { + "description": "Part of project to populate in the working directory.", + "type": "string" + }, + "startPoint": { + "description": "The tag or commit id to reset the checked out branch to", + "type": "string" + } + }, + "required": [ + "location" + ], + "type": "object" + }, + "name": { + "description": "Project name", + "type": "string" + }, + "sourceType": { + "description": "Type of project source", + "enum": [ + "Git", + "Github", + "Zip", + "Custom" + ], + "type": "string" + }, + "zip": { + "description": "Project's Zip source", + "properties": { + "location": { + "description": "Project's source location address. Should be URL for git and github located projects, or; file:// for zip", + "type": "string" + }, + "sparseCheckoutDir": { + "description": "Part of project to populate in the working directory.", + "type": "string" + } + }, + "required": [ + "location" + ], + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "metadata": { + "type": "object", + "description": "Optional metadata", + "properties": { + "version": { + "type": "string", + "description": "Optional semver-compatible version", + "pattern": "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" + }, + "name": { + "type": "string", + "description": "Optional devfile name" + } + } + }, + "schemaVersion": { + "type": "string", + "description": "Devfile schema version", + "pattern": "^([2-9]+)\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" + } + }, + "type": "object", + "required": [ + "schemaVersion" + ] + }` diff --git a/pkg/devfile/parser/data/2.0.0/types.go b/pkg/devfile/parser/data/2.0.0/types.go new file mode 100644 index 00000000000..5c801cf9d51 --- /dev/null +++ b/pkg/devfile/parser/data/2.0.0/types.go @@ -0,0 +1,485 @@ +package version200 + +import ( + "github.com/openshift/odo/pkg/devfile/parser/data/common" +) + +// Devfile200 Devfile schema. +type Devfile200 struct { + + // Devfile schema version + SchemaVersion string `json:"schemaVersion"` + + // Optional metadata + Metadata common.DevfileMetadata `json:"metadata,omitempty"` + + // Projects worked on in the workspace, containing names and sources locations + Projects []common.DevfileProject `json:"projects,omitempty"` + + // Parent workspace template + Parent common.DevfileParent `json:"parent,omitempty"` + + // Predefined, ready-to-use, workspace-related commands + Commands []common.DevfileCommand `json:"commands,omitempty"` + + // List of the workspace components, such as editor and plugins, user-provided containers, or other types of components + Components []common.DevfileComponent `json:"components,omitempty"` + + // Bindings of commands to events. Each command is referred-to by its name. + Events common.DevfileEvents `json:"events,omitempty"` +} + +// ProjectSourceType describes the type of Project sources. +// Only one of the following project sources may be specified. +type ProjectSourceType string + +const ( + GitProjectSourceType ProjectSourceType = "Git" + GitHubProjectSourceType ProjectSourceType = "Github" + ZipProjectSourceType ProjectSourceType = "Zip" + CustomProjectSourceType ProjectSourceType = "Custom" +) + +type ComponentType string + +const ( + ContainerComponentType ComponentType = "Container" + KubernetesComponentType ComponentType = "Kubernetes" + OpenshiftComponentType ComponentType = "Openshift" + PluginComponentType ComponentType = "Plugin" + VolumeComponentType ComponentType = "Volume" + CustomComponentType ComponentType = "Custom" +) + +type CommandType string + +const ( + ExecCommandType CommandType = "Exec" + VscodeTaskCommandType CommandType = "VscodeTask" + VscodeLaunchCommandType CommandType = "VscodeLaunch" + CompositeCommandType CommandType = "Composite" + CustomCommandType CommandType = "Custom" +) + +// CommandGroupType describes the kind of command group. +// +kubebuilder:validation:Enum=build;run;test;debug +type CommandGroupType string + +const ( + BuildCommandGroupType CommandGroupType = "build" + RunCommandGroupType CommandGroupType = "run" + TestCommandGroupType CommandGroupType = "test" + DebugCommandGroupType CommandGroupType = "debug" +) + +// Metadata Optional metadata +type Metadata struct { + + // Optional devfile name + Name string `json:"name,omitempty"` + + // Optional semver-compatible version + Version string `json:"version,omitempty"` +} + +// CommandsItems +type Command struct { + + // Composite command + Composite *Composite `json:"composite,omitempty"` + + // Custom command + Custom *Custom `json:"custom,omitempty"` + + // Exec command + Exec *Exec `json:"exec,omitempty"` + + // Type of workspace command + Type CommandType `json:"type,omitempty"` + + // VscodeLaunch command + VscodeLaunch *VscodeLaunch `json:"vscodeLaunch,omitempty"` + + // VscodeTask command + VscodeTask *VscodeTask `json:"vscodeTask,omitempty"` +} + +// ComponentsItems +type Component struct { + + // CheEditor component + CheEditor *CheEditor `json:"cheEditor,omitempty"` + + // ChePlugin component + ChePlugin *ChePlugin `json:"chePlugin,omitempty"` + + // Container component + Container *Container `json:"container,omitempty"` + + // Custom component + Custom *Custom `json:"custom,omitempty"` + + // Kubernetes component + Kubernetes *Kubernetes `json:"kubernetes,omitempty"` + + // Openshift component + Openshift *Openshift `json:"openshift,omitempty"` + + // Type of project source + Type CommandType `json:"type,omitempty"` + + // Volume component + Volume *Volume `json:"volume,omitempty"` +} + +// ProjectsItems +type Project struct { + + // Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name. + ClonePath *string `json:"clonePath,omitempty"` + + // Project's Custom source + Custom *Custom `json:"custom,omitempty"` + + // Project's Git source + Git *Git `json:"git,omitempty"` + + // Project's GitHub source + Github *Github `json:"github,omitempty"` + + // Project name + Name string `json:"name"` + + // Type of project source + SourceType ProjectSourceType `json:"sourceType,omitempty"` + + // Project's Zip source + Zip *Zip `json:"zip,omitempty"` +} + +// CheEditor CheEditor component +type CheEditor struct { + + // Type of plugin location + LocationType string `json:"locationType,omitempty"` + MemoryLimit string `json:"memoryLimit,omitempty"` + + // Optional name that allows referencing the component in commands, or inside a parent If omitted it will be infered from the location (uri or registryEntry) + Name string `json:"name,omitempty"` + + // Location of an entry inside a plugin registry + RegistryEntry *RegistryEntry `json:"registryEntry,omitempty"` + + // Location defined as an URI + Uri string `json:"uri,omitempty"` +} + +// ChePlugin ChePlugin component +type ChePlugin struct { + + // Type of plugin location + LocationType string `json:"locationType,omitempty"` + MemoryLimit string `json:"memoryLimit,omitempty"` + + // Optional name that allows referencing the component in commands, or inside a parent If omitted it will be infered from the location (uri or registryEntry) + Name string `json:"name,omitempty"` + + // Location of an entry inside a plugin registry + RegistryEntry *RegistryEntry `json:"registryEntry,omitempty"` + + // Location defined as an URI + Uri string `json:"uri,omitempty"` +} + +// Composite Composite command +type Composite struct { + + // Optional map of free-form additional command attributes + Attributes map[string]string `json:"attributes,omitempty"` + + // The commands that comprise this composite command + Commands []string `json:"commands,omitempty"` + + // Defines the group this command is part of + Group *Group `json:"group,omitempty"` + + // Mandatory identifier that allows referencing this command in composite commands, or from a parent, or in events. + Id string `json:"id"` + + // Optional label that provides a label for this command to be used in Editor UI menus for example + Label string `json:"label,omitempty"` + Parallel bool `json:"parallel,omitempty"` +} + +// Configuration +type Configuration struct { + CookiesAuthEnabled bool `json:"cookiesAuthEnabled,omitempty"` + Discoverable bool `json:"discoverable,omitempty"` + Path string `json:"path,omitempty"` + + // The is the low-level protocol of traffic coming through this endpoint. Default value is "tcp" + Protocol string `json:"protocol,omitempty"` + Public bool `json:"public,omitempty"` + + // The is the URL scheme to use when accessing the endpoint. Default value is "http" + Scheme string `json:"scheme,omitempty"` + Secure bool `json:"secure,omitempty"` + Type string `json:"type,omitempty"` +} + +// Container Container component +type Container struct { + Endpoints []*Endpoint `json:"endpoints,omitempty"` + + // Environment variables used in this container + Env []*Env `json:"env,omitempty"` + Image string `json:"image"` + MemoryLimit string `json:"memoryLimit,omitempty"` + MountSources bool `json:"mountSources,omitempty"` + Name string `json:"name"` + + // Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the value of the `PROJECTS_ROOT` environment variable is used. + SourceMapping string `json:"sourceMapping,omitempty"` + + // List of volumes mounts that should be mounted is this container. + VolumeMounts []*VolumeMount `json:"volumeMounts,omitempty"` +} + +// Custom Custom component +type Custom struct { + ComponentClass string `json:"componentClass"` + EmbeddedResource *EmbeddedResource `json:"embeddedResource"` + Name string `json:"name"` +} + +// EmbeddedResource +type EmbeddedResource struct { +} + +// Endpoint +type Endpoint struct { + Attributes map[string]string `json:"attributes,omitempty"` + Configuration *Configuration `json:"configuration"` + Name string `json:"name"` + TargetPort int32 `json:"targetPort"` +} + +// Env +type Env struct { + Name string `json:"name"` + Value string `json:"value"` +} + +// Events Bindings of commands to events. Each command is referred-to by its name. +type Events struct { + + // Names of commands that should be executed after the workspace is completely started. In the case of Che-Theia, these commands should be executed after all plugins and extensions have started, including project cloning. This means that those commands are not triggered until the user opens the IDE in his browser. + PostStart []string `json:"postStart,omitempty"` + + // Names of commands that should be executed after stopping the workspace. + PostStop []string `json:"postStop,omitempty"` + + // Names of commands that should be executed before the workspace start. Kubernetes-wise, these commands would typically be executed in init containers of the workspace POD. + PreStart []string `json:"preStart,omitempty"` + + // Names of commands that should be executed before stopping the workspace. + PreStop []string `json:"preStop,omitempty"` +} + +// Exec Exec command +type Exec struct { + + // Optional map of free-form additional command attributes + Attributes map[string]string `json:"attributes,omitempty"` + + // The actual command-line string + CommandLine *string `json:"commandLine"` + + // Describes component to which given action relates + Component string `json:"component,omitempty"` + + // Optional list of environment variables that have to be set before running the command + Env []*Env `json:"env,omitempty"` + + // Defines the group this command is part of + Group *Group `json:"group,omitempty"` + + // Mandatory identifier that allows referencing this command in composite commands, or from a parent, or in events. + Id string `json:"id"` + + // Optional label that provides a label for this command to be used in Editor UI menus for example + Label string `json:"label,omitempty"` + + // Working directory where the command should be executed + WorkingDir *string `json:"workingDir,omitempty"` +} + +// Git Project's Git source +type Git struct { + + // The branch to check + Branch string `json:"branch,omitempty"` + + // Project's source location address. Should be URL for git and github located projects, or; file:// for zip + Location string `json:"location"` + + // Part of project to populate in the working directory. + SparseCheckoutDir string `json:"sparseCheckoutDir,omitempty"` + + // The tag or commit id to reset the checked out branch to + StartPoint string `json:"startPoint,omitempty"` +} + +// Github Project's GitHub source +type Github struct { + + // The branch to check + Branch string `json:"branch,omitempty"` + + // Project's source location address. Should be URL for git and github located projects, or; file:// for zip + Location string `json:"location"` + + // Part of project to populate in the working directory. + SparseCheckoutDir string `json:"sparseCheckoutDir,omitempty"` + + // The tag or commit id to reset the checked out branch to + StartPoint string `json:"startPoint,omitempty"` +} + +// Group Defines the group this command is part of +type Group struct { + + // Identifies the default command for a given group kind + IsDefault bool `json:"isDefault,omitempty"` + + // Kind of group the command is part of + Kind CommandGroupType `json:"kind"` +} + +// Kubernetes Kubernetes component +type Kubernetes struct { + + // Reference to the plugin definition + Inlined string `json:"inlined,omitempty"` + + // Type of Kubernetes-like location + LocationType string `json:"locationType,omitempty"` + + // Mandatory name that allows referencing the component in commands, or inside a parent + Name string `json:"name"` + + // Location in a plugin registry + Url string `json:"url,omitempty"` +} + +// Openshift Openshift component +type Openshift struct { + + // Reference to the plugin definition + Inlined string `json:"inlined,omitempty"` + + // Type of Kubernetes-like location + LocationType string `json:"locationType,omitempty"` + + // Mandatory name that allows referencing the component in commands, or inside a parent + Name string `json:"name"` + + // Location in a plugin registry + Url string `json:"url,omitempty"` +} + +// Parent Parent workspace template +type Parent struct { + + // Reference to a Kubernetes CRD of type DevWorkspaceTemplate + Kubernetes *Kubernetes `json:"kubernetes,omitempty"` + + // Type of parent location + LocationType string `json:"locationType,omitempty"` + + // Entry in a registry (base URL + ID) that contains a Devfile yaml file + RegistryEntry *RegistryEntry `json:"registryEntry,omitempty"` + + // Uri of a Devfile yaml file + Uri string `json:"uri,omitempty"` +} + +// RegistryEntry Location of an entry inside a plugin registry +type RegistryEntry struct { + BaseUrl string `json:"baseUrl,omitempty"` + Id string `json:"id"` +} + +// Volume Volume component +type Volume struct { + + // Mandatory name that allows referencing the Volume component in Container volume mounts or inside a parent + Name string `json:"name"` + + // Size of the volume + Size string `json:"size,omitempty"` +} + +// VolumeMountsItems Volume that should be mounted to a component container +type VolumeMount struct { + + // The volume mount name is the name of an existing `Volume` component. If no corresponding `Volume` component exist it is implicitly added. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files. + Name string `json:"name"` + + // The path in the component container where the volume should be mounted + Path string `json:"path"` +} + +// VscodeLaunch VscodeLaunch command +type VscodeLaunch struct { + + // Optional map of free-form additional command attributes + Attributes map[string]string `json:"attributes,omitempty"` + + // Defines the group this command is part of + Group *Group `json:"group,omitempty"` + + // Mandatory identifier that allows referencing this command in composite commands, or from a parent, or in events. + Id string `json:"id"` + + // Embedded content of the vscode configuration file + Inlined string `json:"inlined,omitempty"` + + // Type of Vscode configuration command location + LocationType string `json:"locationType,omitempty"` + + // Location as an absolute of relative URL + Url string `json:"url,omitempty"` +} + +// VscodeTask VscodeTask command +type VscodeTask struct { + + // Optional map of free-form additional command attributes + Attributes map[string]string `json:"attributes,omitempty"` + + // Defines the group this command is part of + Group *Group `json:"group,omitempty"` + + // Mandatory identifier that allows referencing this command in composite commands, or from a parent, or in events. + Id string `json:"id"` + + // Embedded content of the vscode configuration file + Inlined string `json:"inlined,omitempty"` + + // Type of Vscode configuration command location + LocationType string `json:"locationType,omitempty"` + + // Location as an absolute of relative URL + Url string `json:"url,omitempty"` +} + +// Zip Project's Zip source +type Zip struct { + + // Project's source location address. Should be URL for git and github located projects, or; file:// for zip + Location string `json:"location"` + + // Part of project to populate in the working directory. + SparseCheckoutDir string `json:"sparseCheckoutDir,omitempty"` +} diff --git a/pkg/devfile/parser/data/common/types.go b/pkg/devfile/parser/data/common/types.go index 6c21062cb94..888f0fb36b7 100644 --- a/pkg/devfile/parser/data/common/types.go +++ b/pkg/devfile/parser/data/common/types.go @@ -1,210 +1,458 @@ package common -// -------------- Supported devfile project types ------------ // -// DevfileProjectType store valid devfile project types -type DevfileProjectType string +// ProjectSourceType describes the type of Project sources. +// Only one of the following project sources may be specified. +type DevfileProjectSourceType string const ( - DevfileProjectTypeGit DevfileProjectType = "git" + GitProjectSourceType DevfileProjectSourceType = "Git" + GitHubProjectSourceType DevfileProjectSourceType = "Github" + ZipProjectSourceType DevfileProjectSourceType = "Zip" + CustomProjectSourceType DevfileProjectSourceType = "Custom" ) -var SupportedDevfileProjectTypes = []DevfileProjectType{DevfileProjectTypeGit} - -// -------------- Supported devfile component types ------------ // -// DevfileComponentType stores valid devfile component types type DevfileComponentType string const ( - DevfileComponentTypeCheEditor DevfileComponentType = "cheEditor" - DevfileComponentTypeChePlugin DevfileComponentType = "chePlugin" - DevfileComponentTypeDockerimage DevfileComponentType = "dockerimage" - DevfileComponentTypeKubernetes DevfileComponentType = "kubernetes" - DevfileComponentTypeOpenshift DevfileComponentType = "openshift" + ContainerComponentType DevfileComponentType = "Container" + KubernetesComponentType DevfileComponentType = "Kubernetes" + OpenshiftComponentType DevfileComponentType = "Openshift" + PluginComponentType DevfileComponentType = "Plugin" + VolumeComponentType DevfileComponentType = "Volume" + CustomComponentType DevfileComponentType = "Custom" ) -// -------------- Supported devfile command types ------------ // type DevfileCommandType string const ( - DevfileCommandTypeInit DevfileCommandType = "init" - DevfileCommandTypeBuild DevfileCommandType = "build" - DevfileCommandTypeRun DevfileCommandType = "run" - DevfileCommandTypeDebug DevfileCommandType = "debug" - DevfileCommandTypeExec DevfileCommandType = "exec" + ExecCommandType DevfileCommandType = "Exec" + VscodeTaskCommandType DevfileCommandType = "VscodeTask" + VscodeLaunchCommandType DevfileCommandType = "VscodeLaunch" + CompositeCommandType DevfileCommandType = "Composite" + CustomCommandType DevfileCommandType = "Custom" ) -// ----------- Devfile Schema ---------- // -type Attributes map[string]string +// CommandGroupType describes the kind of command group. +// +kubebuilder:validation:Enum=build;run;test;debug +type DevfileCommandGroupType string -type ApiVersion string +const ( + BuildCommandGroupType DevfileCommandGroupType = "build" + RunCommandGroupType DevfileCommandGroupType = "run" + TestCommandGroupType DevfileCommandGroupType = "test" + DebugCommandGroupType DevfileCommandGroupType = "debug" + // To Support V1 + InitCommandGroupType DevfileCommandGroupType = "init" +) +// Metadata Optional metadata type DevfileMetadata struct { - // Workspaces created from devfile, will use it as base and append random suffix. - // It's used when name is not defined. - GenerateName *string `yaml:"generateName,omitempty" json:"generateName,omitempty"` + // Optional devfile name + Name string `json:"name,omitempty"` + + // Optional semver-compatible version + Version string `json:"version,omitempty"` +} + +// CommandsItems +type DevfileCommand struct { + + // Composite command + Composite *Composite `json:"composite,omitempty"` + + // Custom command + Custom *Custom `json:"custom,omitempty"` + + // Exec command + Exec *Exec `json:"exec,omitempty"` + + // Type of workspace command + Type DevfileCommandType `json:"type,omitempty"` + + // VscodeLaunch command + VscodeLaunch *VscodeLaunch `json:"vscodeLaunch,omitempty"` + + // VscodeTask command + VscodeTask *VscodeTask `json:"vscodeTask,omitempty"` +} + +// ComponentsItems +type DevfileComponent struct { + + // CheEditor component + CheEditor *CheEditor `json:"cheEditor,omitempty"` + + // ChePlugin component + ChePlugin *ChePlugin `json:"chePlugin,omitempty"` + + // Container component + Container *Container `json:"container,omitempty"` + + // Custom component + Custom *Custom `json:"custom,omitempty"` - // The name of the devfile. Workspaces created from devfile, will inherit this - // name - Name *string `yaml:"name,omitempty" json:"name,omitempty"` + // Kubernetes component + Kubernetes *Kubernetes `json:"kubernetes,omitempty"` + + // Openshift component + Openshift *Openshift `json:"openshift,omitempty"` + + // Type of project source + Type DevfileComponentType `json:"type,omitempty"` + + // Volume component + Volume *Volume `json:"volume,omitempty"` } -// Description of the projects, containing names and sources locations +// ProjectsItems type DevfileProject struct { - // The path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name." - ClonePath *string `yaml:"clonePath,omitempty" json:"clonePath,omitempty"` + // Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name. + ClonePath *string `json:"clonePath,omitempty"` + + // Project's Custom source + Custom *Custom `json:"custom,omitempty"` + + // Project's Git source + Git *Git `json:"git,omitempty"` + + // Project's GitHub source + Github *Github `json:"github,omitempty"` - // The Project Name - Name string `yaml:"name" json:"name"` + // Project name + Name string `json:"name"` - // Describes the project's source - type and location - Source DevfileProjectSource `yaml:"source" json:"source"` + // Type of project source + SourceType DevfileProjectSourceType `json:"sourceType,omitempty"` + + // Project's Zip source + Zip *Zip `json:"zip,omitempty"` } -type DevfileProjectSource struct { - Type DevfileProjectType `yaml:"type" json:"type"` +// CheEditor CheEditor component +type CheEditor struct { - // Project's source location address. Should be URL for git and github located projects" - Location string `yaml:"location" json:"location"` + // Type of plugin location + LocationType string `json:"locationType,omitempty"` + MemoryLimit string `json:"memoryLimit,omitempty"` - // The name of the of the branch to check out after obtaining the source from the location. - // The branch has to already exist in the source otherwise the default branch is used. - // In case of git, this is also the name of the remote branch to push to. - Branch *string `yaml:"branch,omitempty" json:"branch,omitempty"` + // Optional name that allows referencing the component in commands, or inside a parent If omitted it will be infered from the location (uri or registryEntry) + Name string `json:"name,omitempty"` - // The id of the commit to reset the checked out branch to. - // Note that this is equivalent to 'startPoint' and provided for convenience. - CommitId *string `yaml:"commitId,omitempty" json:"commitId,omitempty"` + // Location of an entry inside a plugin registry + RegistryEntry *RegistryEntry `json:"registryEntry,omitempty"` - // Part of project to populate in the working directory. - SparseCheckoutDir *string `yaml:"sparseCheckoutDir,omitempty" json:"sparseCheckoutDir,omitempty"` + // Location defined as an URI + Uri string `json:"uri,omitempty"` +} + +// ChePlugin ChePlugin component +type ChePlugin struct { - // The tag or commit id to reset the checked out branch to. - StartPoint *string `yaml:"startPoint,omitempty" json:"startPoint,omitempty"` + // Type of plugin location + LocationType string `json:"locationType,omitempty"` + MemoryLimit string `json:"memoryLimit,omitempty"` - // The name of the tag to reset the checked out branch to. - // Note that this is equivalent to 'startPoint' and provided for convenience. - Tag *string `yaml:"tag,omitempty" json:"tag,omitempty"` + // Optional name that allows referencing the component in commands, or inside a parent If omitted it will be infered from the location (uri or registryEntry) + Name string `json:"name,omitempty"` + + // Location of an entry inside a plugin registry + RegistryEntry *RegistryEntry `json:"registryEntry,omitempty"` + + // Location defined as an URI + Uri string `json:"uri,omitempty"` } -type DevfileCommand struct { +// Composite Composite command +type Composite struct { + + // Optional map of free-form additional command attributes + Attributes map[string]string `json:"attributes,omitempty"` + + // The commands that comprise this composite command + Commands []string `json:"commands,omitempty"` + + // Defines the group this command is part of + Group *Group `json:"group,omitempty"` + + // Mandatory identifier that allows referencing this command in composite commands, or from a parent, or in events. + Id string `json:"id"` + + // Optional label that provides a label for this command to be used in Editor UI menus for example + Label string `json:"label,omitempty"` + Parallel bool `json:"parallel,omitempty"` +} - // List of the actions of given command. Now the only one command must be - // specified in list but there are plans to implement supporting multiple actions - // commands. - Actions []DevfileCommandAction `yaml:"actions" json:"actions"` +// Configuration +type Configuration struct { + CookiesAuthEnabled bool `json:"cookiesAuthEnabled,omitempty"` + Discoverable bool `json:"discoverable,omitempty"` + Path string `json:"path,omitempty"` - // Additional command attributes - Attributes Attributes `yaml:"attributes,omitempty" json:"attributes,omitempty"` + // The is the low-level protocol of traffic coming through this endpoint. Default value is "tcp" + Protocol string `json:"protocol,omitempty"` + Public bool `json:"public,omitempty"` - // Describes the name of the command. Should be unique per commands set. - Name string `yaml:"name"` + // The is the URL scheme to use when accessing the endpoint. Default value is "http" + Scheme string `json:"scheme,omitempty"` + Secure bool `json:"secure,omitempty"` + Type string `json:"type,omitempty"` +} + +// Container Container component +type Container struct { + Endpoints []*Endpoint `json:"endpoints,omitempty"` + + // Environment variables used in this container + Env []*Env `json:"env,omitempty"` + Image string `json:"image"` + MemoryLimit string `json:"memoryLimit,omitempty"` + MountSources bool `json:"mountSources,omitempty"` + Name string `json:"name"` + + // Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the value of the `PROJECTS_ROOT` environment variable is used. + SourceMapping string `json:"sourceMapping,omitempty"` + + // List of volumes mounts that should be mounted is this container. + VolumeMounts []*VolumeMount `json:"volumeMounts,omitempty"` +} + +// Custom Custom component +type Custom struct { + ComponentClass string `json:"componentClass"` + EmbeddedResource *EmbeddedResource `json:"embeddedResource"` + Name string `json:"name"` +} + +// EmbeddedResource +type EmbeddedResource struct { +} + +// Endpoint +type Endpoint struct { + Attributes map[string]string `json:"attributes,omitempty"` + Configuration *Configuration `json:"configuration"` + Name string `json:"name"` + TargetPort int32 `json:"targetPort"` +} - // Preview url - PreviewUrl DevfileCommandPreviewUrl `yaml:"previewUrl,omitempty" json:"previewUrl,omitempty"` +// Env +type Env struct { + Name string `json:"name"` + Value string `json:"value"` } -type DevfileCommandPreviewUrl struct { - Port *int32 `yaml:"port,omitempty" json:"port,omitempty"` - Path *string `yaml:"path,omitempty" json:"path,omitempty"` +// Events Bindings of commands to events. Each command is referred-to by its name. +type DevfileEvents struct { + + // Names of commands that should be executed after the workspace is completely started. In the case of Che-Theia, these commands should be executed after all plugins and extensions have started, including project cloning. This means that those commands are not triggered until the user opens the IDE in his browser. + PostStart []string `json:"postStart,omitempty"` + + // Names of commands that should be executed after stopping the workspace. + PostStop []string `json:"postStop,omitempty"` + + // Names of commands that should be executed before the workspace start. Kubernetes-wise, these commands would typically be executed in init containers of the workspace POD. + PreStart []string `json:"preStart,omitempty"` + + // Names of commands that should be executed before stopping the workspace. + PreStop []string `json:"preStop,omitempty"` } -type DevfileCommandAction struct { +// Exec Exec command +type Exec struct { - // The actual action command-line string - Command *string `yaml:"command,omitempty" json:"command,omitempty"` + // Optional map of free-form additional command attributes + Attributes map[string]string `json:"attributes,omitempty"` + + // The actual command-line string + CommandLine string `json:"commandLine"` // Describes component to which given action relates - Component *string `yaml:"component,omitempty" json:"component,omitempty"` + Component string `json:"component,omitempty"` + + // Optional list of environment variables that have to be set before running the command + Env []*Env `json:"env,omitempty"` - // the path relative to the location of the devfile to the configuration file - // defining one or more actions in the editor-specific format - Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` + // Defines the group this command is part of + Group *Group `json:"group,omitempty"` - // The content of the referenced configuration file that defines one or more - // actions in the editor-specific format - ReferenceContent *string `yaml:"referenceContent,omitempty" json:"referenceContent,omitempty"` + // Mandatory identifier that allows referencing this command in composite commands, or from a parent, or in events. + Id string `json:"id"` - // Describes action type - Type *DevfileCommandType `yaml:"type,omitempty" json:"type,omitempty"` + // Optional label that provides a label for this command to be used in Editor UI menus for example + Label string `json:"label,omitempty"` // Working directory where the command should be executed - Workdir *string `yaml:"workdir,omitempty" json:"workdir,omitempty"` + WorkingDir *string `json:"workingDir,omitempty"` } -type DevfileComponent struct { +// Git Project's Git source +type Git struct { + + // The branch to check + Branch string `json:"branch,omitempty"` + + // Project's source location address. Should be URL for git and github located projects, or; file:// for zip + Location string `json:"location"` + + // Part of project to populate in the working directory. + SparseCheckoutDir string `json:"sparseCheckoutDir,omitempty"` + + // The tag or commit id to reset the checked out branch to + StartPoint string `json:"startPoint,omitempty"` +} - // The name using which other places of this devfile (like commands) can refer to - // this component. This attribute is optional but must be unique in the devfile if - // specified. - Alias *string `yaml:"alias,omitempty" json:"alias,omitempty"` +// Github Project's GitHub source +type Github struct { - // Describes whether projects sources should be mount to the component. - // `CHE_PROJECTS_ROOT` environment variable should contains a path where projects - // sources are mount - MountSources bool `yaml:"mountSources,omitempty" json:"mountSources,omitempty"` + // The branch to check + Branch string `json:"branch,omitempty"` - // Describes type of the component, e.g. whether it is an plugin or editor or - // other type - Type DevfileComponentType `yaml:"type" json:"type"` + // Project's source location address. Should be URL for git and github located projects, or; file:// for zip + Location string `json:"location"` - // for type ChePlugin - DevfileComponentChePlugin `yaml:",inline" json:",inline"` + // Part of project to populate in the working directory. + SparseCheckoutDir string `json:"sparseCheckoutDir,omitempty"` - // for type=dockerfile - DevfileComponentDockerimage `yaml:",inline" json:",inline"` + // The tag or commit id to reset the checked out branch to + StartPoint string `json:"startPoint,omitempty"` } -type DevfileComponentChePlugin struct { - Id *string `yaml:"id,omitempty" json:"id,omitempty"` - Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` - RegistryUrl *string `yaml:"registryUrl,omitempty" json:"registryUrl,omitempty"` +// Group Defines the group this command is part of +type Group struct { + + // Identifies the default command for a given group kind + IsDefault bool `json:"isDefault,omitempty"` + + // Kind of group the command is part of + Kind DevfileCommandGroupType `json:"kind"` } -type DevfileComponentCheEditor struct { - Id *string `yaml:"id,omitempty" json:"id,omitempty"` - Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` - RegistryUrl *string `yaml:"registryUrl,omitempty" json:"registryUrl,omitempty"` +// Kubernetes Kubernetes component +type Kubernetes struct { + + // Reference to the plugin definition + Inlined string `json:"inlined,omitempty"` + + // Type of Kubernetes-like location + LocationType string `json:"locationType,omitempty"` + + // Mandatory name that allows referencing the component in commands, or inside a parent + Name string `json:"name"` + + // Location in a plugin registry + Url string `json:"url,omitempty"` } -type DevfileComponentOpenshift struct { - Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` - ReferenceContent *string `yaml:"referenceContent,omitempty" json:"referenceContent,omitempty"` - Selector *string `yaml:"selector,omitempty" json:"selector,omitempty"` - EntryPoints *string `yaml:"entryPoints,omitempty" json:"entryPoints,omitempty"` - MemoryLimit *string `yaml:"memoryLimit,omitempty" json:"memoryLimit,omitempty"` +// Openshift Openshift component +type Openshift struct { + + // Reference to the plugin definition + Inlined string `json:"inlined,omitempty"` + + // Type of Kubernetes-like location + LocationType string `json:"locationType,omitempty"` + + // Mandatory name that allows referencing the component in commands, or inside a parent + Name string `json:"name"` + + // Location in a plugin registry + Url string `json:"url,omitempty"` } -type DevfileComponentKubernetes struct { - Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` - ReferenceContent *string `yaml:"referenceContent,omitempty" json:"referenceContent,omitempty"` - Selector *string `yaml:"selector,omitempty" json:"selector,omitempty"` - EntryPoints *string `yaml:"entryPoints,omitempty" json:"entryPoints,omitempty"` - MemoryLimit *string `yaml:"memoryLimit,omitempty" json:"memoryLimit,omitempty"` +// Parent Parent workspace template +type DevfileParent struct { + + // Reference to a Kubernetes CRD of type DevWorkspaceTemplate + Kubernetes *Kubernetes `json:"kubernetes,omitempty"` + + // Type of parent location + LocationType string `json:"locationType,omitempty"` + + // Entry in a registry (base URL + ID) that contains a Devfile yaml file + RegistryEntry *RegistryEntry `json:"registryEntry,omitempty"` + + // Uri of a Devfile yaml file + Uri string `json:"uri,omitempty"` } -type DevfileComponentDockerimage struct { - Image *string `yaml:"image,omitempty" json:"image,omitempty"` - MemoryLimit *string `yaml:"memoryLimit,omitempty" json:"memoryLimit,omitempty"` - Command []string `yaml:"command,omitempty" json:"command,omitempty"` - Args []string `yaml:"args,omitempty" json:"args,omitempty"` - Volumes []DockerimageVolume `yaml:"volumes,omitempty" json:"volumes,omitempty"` - Env []DockerimageEnv `yaml:"env,omitempty" json:"env,omitempty"` - Endpoints []DockerimageEndpoint `yaml:"endpoints,omitempty" json:"endpoints,omitempty"` +// RegistryEntry Location of an entry inside a plugin registry +type RegistryEntry struct { + BaseUrl string `json:"baseUrl,omitempty"` + Id string `json:"id"` } -type DockerimageVolume struct { - Name *string `yaml:"name,omitempty" json:"name,omitempty"` - ContainerPath *string `yaml:"containerPath,omitempty" json:"containerPath,omitempty"` +// Volume Volume component +type Volume struct { + + // Mandatory name that allows referencing the Volume component in Container volume mounts or inside a parent + Name string `json:"name"` + + // Size of the volume + Size string `json:"size,omitempty"` } -type DockerimageEnv struct { - Name *string `yaml:"name,omitempty" json:"name,omitempty"` - Value *string `yaml:"value,omitempty" json:"value,omitempty"` +// VolumeMountsItems Volume that should be mounted to a component container +type VolumeMount struct { + + // The volume mount name is the name of an existing `Volume` component. If no corresponding `Volume` component exist it is implicitly added. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files. + Name string `json:"name"` + + // The path in the component container where the volume should be mounted + Path string `json:"path"` } -type DockerimageEndpoint struct { - Name *string `yaml:"name,omitempty" json:"name,omitempty"` - Port *int32 `yaml:"port,omitempty" json:"port,omitempty"` +// VscodeLaunch VscodeLaunch command +type VscodeLaunch struct { + + // Optional map of free-form additional command attributes + Attributes map[string]string `json:"attributes,omitempty"` + + // Defines the group this command is part of + Group *Group `json:"group,omitempty"` + + // Mandatory identifier that allows referencing this command in composite commands, or from a parent, or in events. + Id string `json:"id"` + + // Embedded content of the vscode configuration file + Inlined string `json:"inlined,omitempty"` + + // Type of Vscode configuration command location + LocationType string `json:"locationType,omitempty"` + + // Location as an absolute of relative URL + Url string `json:"url,omitempty"` +} + +// VscodeTask VscodeTask command +type VscodeTask struct { + + // Optional map of free-form additional command attributes + Attributes map[string]string `json:"attributes,omitempty"` + + // Defines the group this command is part of + Group *Group `json:"group,omitempty"` + + // Mandatory identifier that allows referencing this command in composite commands, or from a parent, or in events. + Id string `json:"id"` + + // Embedded content of the vscode configuration file + Inlined string `json:"inlined,omitempty"` + + // Type of Vscode configuration command location + LocationType string `json:"locationType,omitempty"` + + // Location as an absolute of relative URL + Url string `json:"url,omitempty"` +} + +// Zip Project's Zip source +type Zip struct { + + // Project's source location address. Should be URL for git and github located projects, or; file:// for zip + Location string `json:"location"` + + // Part of project to populate in the working directory. + SparseCheckoutDir string `json:"sparseCheckoutDir,omitempty"` } diff --git a/pkg/devfile/parser/data/interface.go b/pkg/devfile/parser/data/interface.go index 8b6f8221a65..314bbfa603f 100644 --- a/pkg/devfile/parser/data/interface.go +++ b/pkg/devfile/parser/data/interface.go @@ -6,6 +6,9 @@ import ( // DevfileData is an interface that defines functions for Devfile data operations type DevfileData interface { + GetMetadata() common.DevfileMetadata + GetParent() common.DevfileParent + GetEvents() common.DevfileEvents GetComponents() []common.DevfileComponent GetAliasedComponents() []common.DevfileComponent GetProjects() []common.DevfileProject diff --git a/pkg/devfile/parser/data/versions.go b/pkg/devfile/parser/data/versions.go index 8b025f6e410..10615f285e9 100644 --- a/pkg/devfile/parser/data/versions.go +++ b/pkg/devfile/parser/data/versions.go @@ -4,6 +4,7 @@ import ( "reflect" v100 "github.com/openshift/odo/pkg/devfile/parser/data/1.0.0" + v200 "github.com/openshift/odo/pkg/devfile/parser/data/2.0.0" ) // SupportedApiVersions stores the supported devfile API versions @@ -12,10 +13,11 @@ type supportedApiVersion string // Supported devfile API versions in odo const ( apiVersion100 supportedApiVersion = "1.0.0" + apiVersion200 supportedApiVersion = "2.0.0" ) // List of supported devfile API versions -var supportedApiVersionsList = []supportedApiVersion{apiVersion100} +var supportedApiVersionsList = []supportedApiVersion{apiVersion100, apiVersion200} // ------------- Init functions ------------- // @@ -26,6 +28,8 @@ var apiVersionToDevfileStruct map[supportedApiVersion]reflect.Type func init() { apiVersionToDevfileStruct = make(map[supportedApiVersion]reflect.Type) apiVersionToDevfileStruct[apiVersion100] = reflect.TypeOf(v100.Devfile100{}) + apiVersionToDevfileStruct[apiVersion200] = reflect.TypeOf(v200.Devfile200{}) + } // Map to store mappings between supported devfile API versions and respective devfile JSON schemas @@ -35,4 +39,6 @@ var devfileApiVersionToJSONSchema map[supportedApiVersion]string func init() { devfileApiVersionToJSONSchema = make(map[supportedApiVersion]string) devfileApiVersionToJSONSchema[apiVersion100] = v100.JsonSchema100 + devfileApiVersionToJSONSchema[apiVersion200] = v200.JsonSchema200 + } diff --git a/pkg/devfile/validate/components.go b/pkg/devfile/validate/components.go index 3afe6f47e69..30168bfa954 100644 --- a/pkg/devfile/validate/components.go +++ b/pkg/devfile/validate/components.go @@ -2,13 +2,14 @@ package validate import ( "fmt" + "github.com/openshift/odo/pkg/devfile/parser/data/common" ) // Errors var ( - ErrorNoComponents = "no components present" - ErrorNoDockerImageComponent = fmt.Sprintf("odo requires atleast one component of type '%s' in devfile", common.DevfileComponentTypeDockerimage) + ErrorNoComponents = "no components present" + ErrorNoContainerComponent = fmt.Sprintf("odo requires atleast one component of type '%s' in devfile", common.ContainerComponentType) ) // ValidateComponents validates all the devfile components @@ -19,17 +20,17 @@ func ValidateComponents(components []common.DevfileComponent) error { return fmt.Errorf(ErrorNoComponents) } - // Check wether component of type dockerimage is present - isDockerImageComponentPresent := false + // Check weather component of type container is present + isContainerComponentPresent := false for _, component := range components { - if component.Type == common.DevfileComponentTypeDockerimage { - isDockerImageComponentPresent = true + if component.Type == common.ContainerComponentType { + isContainerComponentPresent = true break } } - if !isDockerImageComponentPresent { - return fmt.Errorf(ErrorNoDockerImageComponent) + if !isContainerComponentPresent { + return fmt.Errorf(ErrorNoContainerComponent) } // Successful diff --git a/pkg/devfile/validate/validate.go b/pkg/devfile/validate/validate.go index 0feb47f6c72..e976bdd3199 100644 --- a/pkg/devfile/validate/validate.go +++ b/pkg/devfile/validate/validate.go @@ -3,25 +3,36 @@ package validate import ( "reflect" - v100 "github.com/openshift/odo/pkg/devfile/parser/data/1.0.0" + "github.com/openshift/odo/pkg/devfile/parser/data/common" "k8s.io/klog" + + v100 "github.com/openshift/odo/pkg/devfile/parser/data/1.0.0" + v200 "github.com/openshift/odo/pkg/devfile/parser/data/2.0.0" ) // ValidateDevfileData validates whether sections of devfile are odo compatible func ValidateDevfileData(data interface{}) error { + var components []common.DevfileComponent typeData := reflect.TypeOf(data) + if typeData == reflect.TypeOf(&v100.Devfile100{}) { d := data.(*v100.Devfile100) + components = d.GetComponents() + } - // Validate Components - if err := ValidateComponents(d.Components); err != nil { - return err - } + if typeData == reflect.TypeOf(&v200.Devfile200{}) { + d := data.(*v200.Devfile200) + components = d.GetComponents() + } - // Successful - klog.V(4).Info("Successfully validated devfile sections") - return nil + // Validate Components + if err := ValidateComponents(components); err != nil { + return err } + + // Successful + klog.V(4).Info("Successfully validated devfile sections") return nil + } diff --git a/tests/examples/source/devfiles/nodejs/devfileV2.yaml b/tests/examples/source/devfiles/nodejs/devfileV2.yaml new file mode 100644 index 00000000000..b4126250a4a --- /dev/null +++ b/tests/examples/source/devfiles/nodejs/devfileV2.yaml @@ -0,0 +1,39 @@ +schemaVersion: "2.0.0" +metadata: + name: test-devfile +projects: + - name: nodejs-web-app + git: + location: "https://github.com/che-samples/web-nodejs-sample.git" +components: + - container: + image: quay.io/eclipse/che-nodejs10-ubi:nightly + mountSources: true + name: "runtime" + memoryLimit: 1024Mi + env: + - name: FOO + value: "bar" + endpoints: + - name: '3000/tcp' + targetPort: 3000 + # odo not using currently, added to validate JSON Schema + configuration: + protocol: tcp + scheme: http + type: terminal +commands: + - exec: + id: download dependencies + commandLine: "npm install" + component: runtime + workingDir: ${CHE_PROJECTS_ROOT}/nodejs-web-app/app + group: + kind: build + - exec: + id: run the app + commandLine: "nodemon app.js" + component: runtime + workingDir: ${CHE_PROJECTS_ROOT}/nodejs-web-app/app + group: + kind: run From 9a5b45ea9c2b6993511d6d8397171558c118b2f9 Mon Sep 17 00:00:00 2001 From: Mrinal Das Date: Mon, 18 May 2020 14:34:03 +0530 Subject: [PATCH 02/23] Fixes command.go and kubernetes adapter (#3194) Signed-off-by: mik-dass --- pkg/devfile/adapters/common/command.go | 64 +++-------- pkg/devfile/adapters/common/command_test.go | 103 +----------------- pkg/devfile/adapters/common/utils.go | 16 +-- .../adapters/kubernetes/component/adapter.go | 57 +++++----- pkg/devfile/parser/data/common/types.go | 12 -- pkg/exec/devfile.go | 20 ++-- 6 files changed, 62 insertions(+), 210 deletions(-) diff --git a/pkg/devfile/adapters/common/command.go b/pkg/devfile/adapters/common/command.go index 77966faac6f..d613caf75fa 100644 --- a/pkg/devfile/adapters/common/command.go +++ b/pkg/devfile/adapters/common/command.go @@ -7,29 +7,21 @@ 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 { + if command.Exec.Id == commandName { // Get the supported actions - supportedCommandActions, err := getSupportedCommandActions(data, command) + err = validateCommand(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)) + if err != nil { + return common.DevfileCommand{}, err } - // The command is supported, use it - supportedCommand.Name = command.Name - supportedCommand.Actions = supportedCommandActions - supportedCommand.Attributes = command.Attributes + supportedCommand.Exec = command.Exec return supportedCommand, nil } } @@ -47,48 +39,24 @@ func getCommand(data data.DevfileData, commandName string, required bool) (suppo 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 len(problemMsg) > 0 { - err = fmt.Errorf(problemMsg) - } - - 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) { +func validateCommand(data data.DevfileData, command common.DevfileCommand) (err error) { // type must be exec - if *action.Type != common.DevfileCommandTypeExec { + if command.Type != common.ExecCommandType { return fmt.Errorf("Actions must be of type \"exec\"") } // component must be specified - if action.Component == nil || *action.Component == "" { + if &command.Exec.Component == nil || command.Exec.Component == "" { return fmt.Errorf("Actions must reference a component") } // must specify a command - if action.Command == nil || *action.Command == "" { + if &command.Exec.CommandLine == nil || command.Exec.CommandLine == "" { return fmt.Errorf("Actions must have a command") } @@ -97,12 +65,12 @@ func validateAction(data data.DevfileData, action common.DevfileCommandAction) ( isActionValid := false for _, component := range components { - if *action.Component == *component.Alias && isComponentSupported(component) { + if command.Exec.Component == component.Container.Name && isComponentSupported(component) { isActionValid = true } } if !isActionValid { - return fmt.Errorf("The action does not map to a supported component") + return fmt.Errorf("the command does not map to a supported component") } return @@ -153,7 +121,7 @@ func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, de } else if !isInitCmdEmpty && initCmdErr == nil { isInitCommandValid = true pushDevfileCommands = append(pushDevfileCommands, initCommand) - klog.V(3).Infof("Init command: %v", initCommand.Name) + klog.V(3).Infof("Init command: %v", initCommand.Exec.Id) } buildCommand, buildCmdErr := GetBuildCommand(data, devfileBuildCmd) @@ -166,14 +134,14 @@ func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, de } else if !reflect.DeepEqual(emptyCommand, buildCommand) && buildCmdErr == nil { isBuildCommandValid = true pushDevfileCommands = append(pushDevfileCommands, buildCommand) - klog.V(3).Infof("Build command: %v", buildCommand.Name) + klog.V(3).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) + klog.V(3).Infof("Run command: %v", runCommand.Exec.Id) } // If either command had a problem, return an empty list of commands and an error diff --git a/pkg/devfile/adapters/common/command_test.go b/pkg/devfile/adapters/common/command_test.go index c6efc7052f3..262e40f01f4 100644 --- a/pkg/devfile/adapters/common/command_test.go +++ b/pkg/devfile/adapters/common/command_test.go @@ -225,105 +225,6 @@ func TestGetCommand(t *testing.T) { } -func TestGetSupportedCommandActions(t *testing.T) { - - command := "ls -la" - component := "alias1" - workDir := "/" - validCommandType := common.DevfileCommandTypeExec - invalidCommandType := common.DevfileCommandType("garbage") - emptyString := "" - - tests := []struct { - name string - command common.DevfileCommand - wantErr bool - }{ - { - name: "Case: Valid Command Action", - command: common.DevfileCommand{ - Name: "testCommand", - Actions: []common.DevfileCommandAction{ - { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, - }, - }, - }, - wantErr: false, - }, - { - name: "Case: Invalid Command Action with empty command", - command: common.DevfileCommand{ - Name: "testCommand", - Actions: []common.DevfileCommandAction{ - { - Command: &emptyString, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, - }, - }, - }, - wantErr: true, - }, - { - name: "Case: Invalid Command Action with missing component", - command: common.DevfileCommand{ - Name: "testCommand", - Actions: []common.DevfileCommandAction{ - { - Command: &command, - Workdir: &workDir, - Type: &validCommandType, - }, - }, - }, - wantErr: true, - }, - { - name: "Case: Invalid Command Action with wrong type", - command: common.DevfileCommand{ - Name: "testCommand", - Actions: []common.DevfileCommandAction{ - { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &invalidCommandType, - }, - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - devObj := devfileParser.DevfileObj{ - Data: testingutil.TestDevfileData{ - CommandActions: []common.DevfileCommandAction{ - { - Command: &command, - Component: &component, - Type: &validCommandType, - }, - }, - ComponentType: common.DevfileComponentTypeDockerimage, - }, - } - t.Run(tt.name, func(t *testing.T) { - supportedCommandActions, _ := getSupportedCommandActions(devObj.Data, tt.command) - if !tt.wantErr && len(supportedCommandActions) != len(tt.command.Actions) { - t.Errorf("TestGetSupportedCommandActions error: incorrect number of command actions expected: %v actual: %v", len(tt.command.Actions), len(supportedCommandActions)) - } else if tt.wantErr && len(supportedCommandActions) != 0 { - t.Errorf("TestGetSupportedCommandActions error: incorrect number of command actions expected: %v actual: %v", 0, len(supportedCommandActions)) - } - }) - } - -} - func TestValidateAction(t *testing.T) { command := "ls -la" @@ -392,7 +293,7 @@ func TestValidateAction(t *testing.T) { }, } t.Run(tt.name, func(t *testing.T) { - err := validateAction(devObj.Data, tt.action) + err := validateCommand(devObj.Data, tt.action) if !tt.wantErr == (err != nil) { t.Errorf("TestValidateAction unexpected error: %v", err) return @@ -407,7 +308,7 @@ func TestGetInitCommand(t *testing.T) { command := "ls -la" component := "alias1" workDir := "/" - validCommandType := common.DevfileCommandTypeExec + validCommandType := common.ExecCommandType emptyString := "" var emptyCommand common.DevfileCommand diff --git a/pkg/devfile/adapters/common/utils.go b/pkg/devfile/adapters/common/utils.go index 7a754804aa4..70f593c3e44 100644 --- a/pkg/devfile/adapters/common/utils.go +++ b/pkg/devfile/adapters/common/utils.go @@ -82,7 +82,7 @@ type CommandNames struct { func isComponentSupported(component common.DevfileComponent) bool { // Currently odo only uses devfile components of type dockerimage, since most of the Che registry devfiles use it - return component.Type == common.DevfileComponentTypeDockerimage + return component.Type == common.ContainerComponentType } // GetBootstrapperImage returns the odo-init bootstrapper image @@ -99,7 +99,7 @@ func GetSupportedComponents(data data.DevfileData) []common.DevfileComponent { // Only components with aliases are considered because without an alias commands cannot reference them for _, comp := range data.GetAliasedComponents() { if isComponentSupported(comp) { - klog.V(3).Infof("Found component \"%v\" with alias \"%v\"\n", comp.Type, *comp.Alias) + klog.V(3).Infof("Found component \"%v\" with alias \"%v\"\n", comp.Type, comp.Container.Name) components = append(components, comp) } } @@ -112,7 +112,7 @@ func GetVolumes(devfileObj devfileParser.DevfileObj) map[string][]DevfileVolume componentAliasToVolumes := make(map[string][]DevfileVolume) size := volumeSize for _, comp := range GetSupportedComponents(devfileObj.Data) { - if comp.Volumes != nil { + if comp.Volume != nil { for _, volume := range comp.Volumes { vol := DevfileVolume{ Name: volume.Name, @@ -127,9 +127,9 @@ func GetVolumes(devfileObj devfileParser.DevfileObj) map[string][]DevfileVolume } // IsEnvPresent checks if the env variable is present in an array of env variables -func IsEnvPresent(envVars []common.DockerimageEnv, envVarName string) bool { +func IsEnvPresent(envVars []common.Container, envVarName string) bool { for _, envVar := range envVars { - if *envVar.Name == envVarName { + if envVar.Name == envVarName { return true } } @@ -138,9 +138,9 @@ func IsEnvPresent(envVars []common.DockerimageEnv, envVarName string) bool { } // IsPortPresent checks if the port is present in the endpoints array -func IsPortPresent(endpoints []common.DockerimageEndpoint, port int) bool { +func IsPortPresent(endpoints []common.Endpoint, port int) bool { for _, endpoint := range endpoints { - if *endpoint.Port == int32(port) { + if endpoint.TargetPort == int32(port) { return true } } @@ -152,7 +152,7 @@ func IsPortPresent(endpoints []common.DockerimageEndpoint, port int) bool { func IsRestartRequired(command common.DevfileCommand) bool { var restart = true var err error - rs, ok := command.Attributes["restart"] + rs, ok := command.Exec.Attributes["restart"] if ok { restart, err = strconv.ParseBool(rs) // Ignoring error here as restart is true for all error and default cases. diff --git a/pkg/devfile/adapters/kubernetes/component/adapter.go b/pkg/devfile/adapters/kubernetes/component/adapter.go index bbede80e76e..4515ee6d322 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter.go @@ -329,54 +329,49 @@ func (a Adapter) execDevfile(pushDevfileCommands []versionsCommon.DevfileCommand // Loop through each of the command given from the devfile for _, command := range pushDevfileCommands { // If the current command from the devfile is the currently expected command from the devfile - if command.Name == currentCommand.DefaultName || command.Name == currentCommand.AdapterName { + if command.Exec.Id == currentCommand.DefaultName || command.Exec.Id == currentCommand.AdapterName { // If the current command is not the last command in the slice // it is not expected to be the run command if i < len(commandOrder)-1 { // Any exec command such as "Init" and "Build" - for _, action := range command.Actions { - compInfo := common.ComponentInfo{ - ContainerName: *action.Component, - PodName: podName, - } + compInfo := common.ComponentInfo{ + ContainerName: command.Exec.Component, + PodName: podName, + } - err = exec.ExecuteDevfileBuildAction(&a.Client, action, command.Name, compInfo, show) - if err != nil { - return err - } + err = exec.ExecuteDevfileBuildAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) + if err != nil { + return err } // If the current command is the last command in the slice // it is expected to be the run command } else { // Last command is "Run" - klog.V(4).Infof("Executing devfile command %v", command.Name) - - for _, action := range command.Actions { - - // Check if the devfile run component containers have supervisord as the entrypoint. - // Start the supervisord if the odo component does not exist - if !componentExists { - err = a.InitRunContainerSupervisord(*action.Component, podName, containers) - if err != nil { - return - } - } + klog.V(4).Infof("Executing devfile command %v", command.Exec.Id) - compInfo := common.ComponentInfo{ - ContainerName: *action.Component, - PodName: podName, - } - - if componentExists && !common.IsRestartRequired(command) { - klog.V(4).Infof("restart:false, Not restarting DevRun Command") - err = exec.ExecuteDevfileRunActionWithoutRestart(&a.Client, action, command.Name, compInfo, show) + // Check if the devfile run component containers have supervisord as the entrypoint. + // Start the supervisord if the odo component does not exist + if !componentExists { + err = a.InitRunContainerSupervisord(command.Exec.Component, podName, containers) + if err != nil { return } + } - err = exec.ExecuteDevfileRunAction(&a.Client, action, command.Name, compInfo, show) + compInfo := common.ComponentInfo{ + ContainerName: command.Exec.Component, + PodName: podName, } + + if componentExists && !common.IsRestartRequired(command) { + klog.V(4).Infof("restart:false, Not restarting DevRun Command") + err = exec.ExecuteDevfileRunActionWithoutRestart(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) + return + } + + err = exec.ExecuteDevfileRunAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) } } } diff --git a/pkg/devfile/parser/data/common/types.go b/pkg/devfile/parser/data/common/types.go index 888f0fb36b7..6e0b5064eff 100644 --- a/pkg/devfile/parser/data/common/types.go +++ b/pkg/devfile/parser/data/common/types.go @@ -58,23 +58,11 @@ type DevfileMetadata struct { // CommandsItems type DevfileCommand struct { - // Composite command - Composite *Composite `json:"composite,omitempty"` - - // Custom command - Custom *Custom `json:"custom,omitempty"` - // Exec command Exec *Exec `json:"exec,omitempty"` // Type of workspace command Type DevfileCommandType `json:"type,omitempty"` - - // VscodeLaunch command - VscodeLaunch *VscodeLaunch `json:"vscodeLaunch,omitempty"` - - // VscodeTask command - VscodeTask *VscodeTask `json:"vscodeTask,omitempty"` } // ComponentsItems diff --git a/pkg/exec/devfile.go b/pkg/exec/devfile.go index 9116e8b76a4..ce639ab422b 100644 --- a/pkg/exec/devfile.go +++ b/pkg/exec/devfile.go @@ -10,22 +10,22 @@ import ( ) // ExecuteDevfileBuildAction executes the devfile build command action -func ExecuteDevfileBuildAction(client ExecClient, action common.DevfileCommandAction, commandName string, compInfo adaptersCommon.ComponentInfo, show bool) error { +func ExecuteDevfileBuildAction(client ExecClient, exec common.Exec, commandName string, compInfo adaptersCommon.ComponentInfo, show bool) error { var s *log.Status // Change to the workdir and execute the command var cmdArr []string - if action.Workdir != nil { + if exec.WorkingDir != nil { // 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{adaptersCommon.ShellExecutable, "-c", "cd " + *action.Workdir + " && " + *action.Command} + cmdArr = []string{adaptersCommon.ShellExecutable, "-c", "cd " + *exec.WorkingDir + " && " + exec.CommandLine} } else { - cmdArr = []string{adaptersCommon.ShellExecutable, "-c", *action.Command} + cmdArr = []string{adaptersCommon.ShellExecutable, "-c", exec.CommandLine} } if show { - s = log.SpinnerNoSpin("Executing " + commandName + " command " + fmt.Sprintf("%q", *action.Command)) + s = log.SpinnerNoSpin("Executing " + commandName + " command " + fmt.Sprintf("%q", exec.CommandLine)) } else { - s = log.Spinnerf("Executing %s command %q", commandName, *action.Command) + s = log.Spinnerf("Executing %s command %q", commandName, exec.CommandLine) } defer s.End(false) @@ -40,7 +40,7 @@ func ExecuteDevfileBuildAction(client ExecClient, action common.DevfileCommandAc } // ExecuteDevfileRunAction executes the devfile run command action using the supervisord devrun program -func ExecuteDevfileRunAction(client ExecClient, action common.DevfileCommandAction, commandName string, compInfo adaptersCommon.ComponentInfo, show bool) error { +func ExecuteDevfileRunAction(client ExecClient, exec common.Exec, commandName string, compInfo adaptersCommon.ComponentInfo, show bool) error { var s *log.Status // Exec the supervisord ctl stop and start for the devrun program @@ -56,7 +56,7 @@ func ExecuteDevfileRunAction(client ExecClient, action common.DevfileCommandActi }, } - s = log.Spinnerf("Executing %s command %q", commandName, *action.Command) + s = log.Spinnerf("Executing %s command %q", commandName, exec.CommandLine) defer s.End(false) for _, devRunExec := range devRunExecs { @@ -72,7 +72,7 @@ func ExecuteDevfileRunAction(client ExecClient, action common.DevfileCommandActi } // ExecuteDevfileRunActionWithoutRestart executes devfile run command without restarting. -func ExecuteDevfileRunActionWithoutRestart(client ExecClient, action common.DevfileCommandAction, commandName string, compInfo adaptersCommon.ComponentInfo, show bool) error { +func ExecuteDevfileRunActionWithoutRestart(client ExecClient, exec common.Exec, commandName string, compInfo adaptersCommon.ComponentInfo, show bool) error { var s *log.Status type devRunExecutable struct { @@ -84,7 +84,7 @@ func ExecuteDevfileRunActionWithoutRestart(client ExecClient, action common.Devf command: []string{adaptersCommon.SupervisordBinaryPath, adaptersCommon.SupervisordCtlSubCommand, "start", string(adaptersCommon.DefaultDevfileRunCommand)}, } - s = log.Spinnerf("Executing %s command %q, if not running", commandName, *action.Command) + s = log.Spinnerf("Executing %s command %q, if not running", commandName, exec.CommandLine) defer s.End(false) err := ExecuteCommand(client, compInfo, devRunExec.command, show) From ce9c99e9d00f1485f20ab4cb66a0cdd44fac041c Mon Sep 17 00:00:00 2001 From: Mrinal Das Date: Mon, 18 May 2020 16:32:45 +0530 Subject: [PATCH 03/23] Fixes utils of k8s adapter (#3195) --- pkg/devfile/adapters/common/utils.go | 8 +- .../adapters/kubernetes/utils/utils.go | 100 +++++++++--------- pkg/kclient/generators.go | 4 +- 3 files changed, 54 insertions(+), 58 deletions(-) diff --git a/pkg/devfile/adapters/common/utils.go b/pkg/devfile/adapters/common/utils.go index 70f593c3e44..db487b78b55 100644 --- a/pkg/devfile/adapters/common/utils.go +++ b/pkg/devfile/adapters/common/utils.go @@ -113,13 +113,13 @@ func GetVolumes(devfileObj devfileParser.DevfileObj) map[string][]DevfileVolume size := volumeSize for _, comp := range GetSupportedComponents(devfileObj.Data) { if comp.Volume != nil { - for _, volume := range comp.Volumes { + for _, volume := range comp.Container.VolumeMounts { vol := DevfileVolume{ - Name: volume.Name, - ContainerPath: volume.ContainerPath, + Name: &volume.Name, + ContainerPath: &volume.Path, Size: &size, } - componentAliasToVolumes[*comp.Alias] = append(componentAliasToVolumes[*comp.Alias], vol) + componentAliasToVolumes[comp.Container.Name] = append(componentAliasToVolumes[comp.Container.Name], vol) } } } diff --git a/pkg/devfile/adapters/kubernetes/utils/utils.go b/pkg/devfile/adapters/kubernetes/utils/utils.go index 8a39be97418..aa393980894 100644 --- a/pkg/devfile/adapters/kubernetes/utils/utils.go +++ b/pkg/devfile/adapters/kubernetes/utils/utils.go @@ -22,31 +22,31 @@ func ComponentExists(client kclient.Client, name string) bool { } // ConvertEnvs converts environment variables from the devfile structure to kubernetes structure -func ConvertEnvs(vars []common.DockerimageEnv) []corev1.EnvVar { +func ConvertEnvs(vars []*common.Env) []corev1.EnvVar { kVars := []corev1.EnvVar{} for _, env := range vars { kVars = append(kVars, corev1.EnvVar{ - Name: *env.Name, - Value: *env.Value, + Name: env.Name, + Value: env.Value, }) } return kVars } // ConvertPorts converts endpoint variables from the devfile structure to kubernetes ContainerPort -func ConvertPorts(endpoints []common.DockerimageEndpoint) ([]corev1.ContainerPort, error) { +func ConvertPorts(endpoints []*common.Endpoint) ([]corev1.ContainerPort, error) { containerPorts := []corev1.ContainerPort{} for _, endpoint := range endpoints { - name := strings.TrimSpace(util.GetDNS1123Name(strings.ToLower(*endpoint.Name))) + name := strings.TrimSpace(util.GetDNS1123Name(strings.ToLower(endpoint.Name))) name = util.TruncateString(name, 15) for _, c := range containerPorts { - if c.ContainerPort == *endpoint.Port { - return nil, fmt.Errorf("Devfile contains multiple identical ports: %v", *endpoint.Port) + if c.ContainerPort == endpoint.TargetPort { + return nil, fmt.Errorf("Devfile contains multiple identical ports: %v", endpoint.TargetPort) } } containerPorts = append(containerPorts, corev1.ContainerPort{ Name: name, - ContainerPort: *endpoint.Port, + ContainerPort: endpoint.TargetPort, }) } return containerPorts, nil @@ -56,13 +56,13 @@ func ConvertPorts(endpoints []common.DockerimageEndpoint) ([]corev1.ContainerPor func GetContainers(devfileObj devfileParser.DevfileObj) ([]corev1.Container, error) { var containers []corev1.Container for _, comp := range adaptersCommon.GetSupportedComponents(devfileObj.Data) { - envVars := ConvertEnvs(comp.Env) + envVars := ConvertEnvs(comp.Container.Env) resourceReqs := GetResourceReqs(comp) - ports, err := ConvertPorts(comp.Endpoints) + ports, err := ConvertPorts(comp.Container.Endpoints) if err != nil { return nil, err } - container := kclient.GenerateContainer(*comp.Alias, *comp.Image, false, comp.Command, comp.Args, envVars, resourceReqs, ports) + container := kclient.GenerateContainer(comp.Container.Name, comp.Container.Image, false, envVars, resourceReqs, ports) for _, c := range containers { for _, containerPort := range c.Ports { for _, curPort := range container.Ports { @@ -74,7 +74,7 @@ func GetContainers(devfileObj devfileParser.DevfileObj) ([]corev1.Container, err } // If `mountSources: true` was set, add an empty dir volume to the container to sync the source to - if comp.MountSources { + if comp.Container.MountSources { container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ Name: kclient.OdoSourceVolume, MountPath: kclient.OdoSourceVolumeMount, @@ -117,47 +117,45 @@ func UpdateContainersWithSupervisord(devfileObj devfileParser.DevfileObj, contai } for i, container := range containers { - for _, action := range runCommand.Actions { - // Check if the container belongs to a run command component - if container.Name == *action.Component { - // If the run component container has no entrypoint and arguments, override the entrypoint with supervisord - if len(container.Command) == 0 && len(container.Args) == 0 { - klog.V(3).Infof("Updating container %v entrypoint with supervisord", container.Name) - container.Command = append(container.Command, adaptersCommon.SupervisordBinaryPath) - container.Args = append(container.Args, "-c", adaptersCommon.SupervisordConfFile) - } + // Check if the container belongs to a run command component + if container.Name == runCommand.Exec.Component { + // If the run component container has no entrypoint and arguments, override the entrypoint with supervisord + if len(container.Command) == 0 && len(container.Args) == 0 { + klog.V(3).Infof("Updating container %v entrypoint with supervisord", container.Name) + container.Command = append(container.Command, adaptersCommon.SupervisordBinaryPath) + container.Args = append(container.Args, "-c", adaptersCommon.SupervisordConfFile) + } - // Always mount the supervisord volume in the run component container - klog.V(3).Infof("Updating container %v with supervisord volume mounts", container.Name) - container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ - Name: adaptersCommon.SupervisordVolumeName, - MountPath: adaptersCommon.SupervisordMountPath, - }) - - // Update the run container's ENV for work dir and command - // only if the env var is not set in the devfile - // This is done, so supervisord can use it in it's program - if !isEnvPresent(container.Env, adaptersCommon.EnvOdoCommandRun) { - klog.V(3).Infof("Updating container %v env with run command", container.Name) - container.Env = append(container.Env, - corev1.EnvVar{ - Name: adaptersCommon.EnvOdoCommandRun, - Value: *action.Command, - }) - } + // Always mount the supervisord volume in the run component container + klog.V(3).Infof("Updating container %v with supervisord volume mounts", container.Name) + container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ + Name: adaptersCommon.SupervisordVolumeName, + MountPath: adaptersCommon.SupervisordMountPath, + }) - if !isEnvPresent(container.Env, adaptersCommon.EnvOdoCommandRunWorkingDir) && action.Workdir != nil { - klog.V(3).Infof("Updating container %v env with run command's workdir", container.Name) - container.Env = append(container.Env, - corev1.EnvVar{ - Name: adaptersCommon.EnvOdoCommandRunWorkingDir, - Value: *action.Workdir, - }) - } + // Update the run container's ENV for work dir and command + // only if the env var is not set in the devfile + // This is done, so supervisord can use it in it's program + if !isEnvPresent(container.Env, adaptersCommon.EnvOdoCommandRun) { + klog.V(3).Infof("Updating container %v env with run command", container.Name) + container.Env = append(container.Env, + corev1.EnvVar{ + Name: adaptersCommon.EnvOdoCommandRun, + Value: runCommand.Exec.CommandLine, + }) + } - // Update the containers array since the array is not a pointer to the container - containers[i] = container + if !isEnvPresent(container.Env, adaptersCommon.EnvOdoCommandRunWorkingDir) && runCommand.Exec.WorkingDir != nil { + klog.V(3).Infof("Updating container %v env with run command's workdir", container.Name) + container.Env = append(container.Env, + corev1.EnvVar{ + Name: adaptersCommon.EnvOdoCommandRunWorkingDir, + Value: *runCommand.Exec.WorkingDir, + }) } + + // Update the containers array since the array is not a pointer to the container + containers[i] = container } } @@ -169,8 +167,8 @@ func UpdateContainersWithSupervisord(devfileObj devfileParser.DevfileObj, contai func GetResourceReqs(comp common.DevfileComponent) corev1.ResourceRequirements { reqs := corev1.ResourceRequirements{} limits := make(corev1.ResourceList) - if comp.MemoryLimit != nil { - memoryLimit, err := resource.ParseQuantity(*comp.MemoryLimit) + if &comp.Container.MemoryLimit != nil { + memoryLimit, err := resource.ParseQuantity(comp.Container.MemoryLimit) if err == nil { limits[corev1.ResourceMemory] = memoryLimit } diff --git a/pkg/kclient/generators.go b/pkg/kclient/generators.go index ab99888eda5..8bf9e99f05b 100644 --- a/pkg/kclient/generators.go +++ b/pkg/kclient/generators.go @@ -33,14 +33,12 @@ const ( ) // GenerateContainer creates a container spec that can be used when creating a pod -func GenerateContainer(name, image string, isPrivileged bool, command, args []string, envVars []corev1.EnvVar, resourceReqs corev1.ResourceRequirements, ports []corev1.ContainerPort) *corev1.Container { +func GenerateContainer(name, image string, isPrivileged bool, envVars []corev1.EnvVar, resourceReqs corev1.ResourceRequirements, ports []corev1.ContainerPort) *corev1.Container { container := &corev1.Container{ Name: name, Image: image, ImagePullPolicy: corev1.PullAlways, Resources: resourceReqs, - Command: command, - Args: args, Env: envVars, Ports: ports, } From f9bcff99f633bbd51e3e7b9c620422d54de7eaf8 Mon Sep 17 00:00:00 2001 From: Aditi Sharma Date: Mon, 18 May 2020 16:56:04 +0530 Subject: [PATCH 04/23] Update Command Logic to use groups (#3196) --- pkg/devfile/adapters/common/command.go | 54 ++++++++++++++++++-------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/pkg/devfile/adapters/common/command.go b/pkg/devfile/adapters/common/command.go index d613caf75fa..18f27786790 100644 --- a/pkg/devfile/adapters/common/command.go +++ b/pkg/devfile/adapters/common/command.go @@ -10,18 +10,37 @@ import ( ) // 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) { +func getCommand(data data.DevfileData, commandName string, groupType common.DevfileCommandGroupType, required bool) (supportedCommand common.DevfileCommand, err error) { + for _, command := range data.GetCommands() { - if command.Exec.Id == commandName { + // validate command + err = validateCommand(data, command) + + if err != nil { + return common.DevfileCommand{}, err + } - // Get the supported actions - err = validateCommand(data, command) + // 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 != "" { - if err != nil { - return common.DevfileCommand{}, err + if command.Exec.Id == commandName { + supportedCommand = command + return supportedCommand, nil } - // The command is supported, use it - supportedCommand.Exec = command.Exec + continue + } + + // if not command specified via flag, default command has the highest priority + if command.Exec.Group.Kind == groupType && command.Exec.Group.IsDefault { + supportedCommand = command + return supportedCommand, nil + } + + // return the first command found for the matching type. + if command.Exec.Group.Kind == groupType { + supportedCommand = command return supportedCommand, nil } } @@ -47,17 +66,17 @@ func validateCommand(data data.DevfileData, command common.DevfileCommand) (err // type must be exec if command.Type != common.ExecCommandType { - return fmt.Errorf("Actions must be of type \"exec\"") + return fmt.Errorf("Command must be of type \"exec\"") } // component must be specified if &command.Exec.Component == nil || command.Exec.Component == "" { - return fmt.Errorf("Actions must reference a component") + return fmt.Errorf("Exec commands must reference a component") } // must specify a command if &command.Exec.CommandLine == nil || command.Exec.CommandLine == "" { - return fmt.Errorf("Actions must have a command") + return fmt.Errorf("Exec commands must have a command") } // must map to a supported component @@ -78,30 +97,31 @@ func validateCommand(data data.DevfileData, command common.DevfileCommand) (err // 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) + return getCommand(data, devfileInitCmd, common.InitCommandGroupType, 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, "", common.InitCommandGroupType, false) } // 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) + return getCommand(data, devfileBuildCmd, common.BuildCommandGroupType, 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, "", common.BuildCommandGroupType, false) } // 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, devfileRunCmd, common.RunCommandGroupType, true) } - return getCommand(data, string(DefaultDevfileRunCommand), true) + return getCommand(data, "", common.RunCommandGroupType, true) } // ValidateAndGetPushDevfileCommands validates the build and the run command, From 0825fcccd40ea82e8128abe381f8f5e354621c4e Mon Sep 17 00:00:00 2001 From: Mrinal Das Date: Mon, 18 May 2020 18:14:40 +0530 Subject: [PATCH 05/23] Updates create logic to v2 (#3200) --- pkg/odo/cli/component/create.go | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/pkg/odo/cli/component/create.go b/pkg/odo/cli/component/create.go index a900eded7a0..f90902ea15a 100644 --- a/pkg/odo/cli/component/create.go +++ b/pkg/odo/cli/component/create.go @@ -770,29 +770,30 @@ func (co *CreateOptions) downloadProject(projectPassed string) error { return err } - var zipUrl string - switch project.Source.Type { - case "git": - if strings.Contains(project.Source.Location, "github.com") { - zipUrl, err = util.GetGitHubZipURL(project.Source.Location) + var url string + switch project.SourceType { + case common.GitProjectSourceType: + if strings.Contains(project.Git.Location, "github.com") { + url, err = util.GetGitHubZipURL(project.Git.Location) if err != nil { return err } } else { return errors.Errorf("Project type git with non github url not supported") } - case "github": - zipUrl, err = util.GetGitHubZipURL(project.Source.Location) + case common.GitHubProjectSourceType: + url, err = util.GetGitHubZipURL(project.Github.Location) if err != nil { return err } - case "zip": - zipUrl = project.Source.Location + case common.ZipProjectSourceType: + url = project.Zip.Location default: return errors.Errorf("Project type not supported") } - err = checkoutProject(project, zipUrl, path) + err = checkoutProject(project, url, path) + if err != nil { return err } @@ -905,8 +906,15 @@ func ensureAndLogProperResourceUsage(resource, resourceMin, resourceMax, resourc } func checkoutProject(project common.DevfileProject, zipURL, path string) error { - if project.Source.SparseCheckoutDir != nil && *project.Source.SparseCheckoutDir != "" { - sparseCheckoutDir := *project.Source.SparseCheckoutDir + var sparseCheckoutDir string + if project.Git.SparseCheckoutDir != "" { + sparseCheckoutDir = project.Git.SparseCheckoutDir + } else if project.Github.SparseCheckoutDir != "" { + sparseCheckoutDir = project.Github.SparseCheckoutDir + } else if project.Zip.SparseCheckoutDir != "" { + sparseCheckoutDir = project.Zip.SparseCheckoutDir + } + if sparseCheckoutDir != "" { err := util.GetAndExtractZip(zipURL, path, sparseCheckoutDir) if err != nil { return errors.Wrap(err, "failed to download and extract project zip folder") From 8147f768bdaf93cd37683e3cc41ba34a9cff83c3 Mon Sep 17 00:00:00 2001 From: Mrinal Das Date: Tue, 19 May 2020 12:25:45 +0530 Subject: [PATCH 06/23] Fixes utils of docker docker adapter (#3198) Signed-off-by: mik-dass --- pkg/devfile/adapters/common/utils.go | 4 +- .../adapters/docker/component/utils.go | 116 +++++++++--------- pkg/devfile/adapters/docker/utils/utils.go | 67 +++++----- 3 files changed, 89 insertions(+), 98 deletions(-) diff --git a/pkg/devfile/adapters/common/utils.go b/pkg/devfile/adapters/common/utils.go index db487b78b55..c286c459f1f 100644 --- a/pkg/devfile/adapters/common/utils.go +++ b/pkg/devfile/adapters/common/utils.go @@ -127,7 +127,7 @@ func GetVolumes(devfileObj devfileParser.DevfileObj) map[string][]DevfileVolume } // IsEnvPresent checks if the env variable is present in an array of env variables -func IsEnvPresent(envVars []common.Container, envVarName string) bool { +func IsEnvPresent(envVars []*common.Env, envVarName string) bool { for _, envVar := range envVars { if envVar.Name == envVarName { return true @@ -138,7 +138,7 @@ func IsEnvPresent(envVars []common.Container, envVarName string) bool { } // IsPortPresent checks if the port is present in the endpoints array -func IsPortPresent(endpoints []common.Endpoint, port int) bool { +func IsPortPresent(endpoints []*common.Endpoint, port int) bool { for _, endpoint := range endpoints { if endpoint.TargetPort == int32(port) { return true diff --git a/pkg/devfile/adapters/docker/component/utils.go b/pkg/devfile/adapters/docker/component/utils.go index f250ec3b564..d32594b4b6e 100644 --- a/pkg/devfile/adapters/docker/component/utils.go +++ b/pkg/devfile/adapters/docker/component/utils.go @@ -72,7 +72,7 @@ func (a Adapter) createComponent() (err error) { // Loop over each component and start a container for it for _, comp := range supportedComponents { var dockerVolumeMounts []mount.Mount - for _, vol := range a.componentAliasToVolumes[*comp.Alias] { + for _, vol := range a.componentAliasToVolumes[comp.Container.Name] { volMount := mount.Mount{ Type: mount.TypeVolume, Source: a.volumeNameToDockerVolName[*vol.Name], @@ -82,7 +82,7 @@ func (a Adapter) createComponent() (err error) { } err = a.pullAndStartContainer(dockerVolumeMounts, projectVolumeName, comp) if err != nil { - return errors.Wrapf(err, "unable to pull and start container %s for component %s", *comp.Alias, componentName) + return errors.Wrapf(err, "unable to pull and start container %s for component %s", comp.Container.Name, componentName) } } klog.V(3).Infof("Successfully created all containers for component %s", componentName) @@ -120,13 +120,13 @@ func (a Adapter) updateComponent() (componentExists bool, err error) { for _, comp := range supportedComponents { // Check to see if this component is already running and if so, update it // If component isn't running, re-create it, as it either may be new, or crashed. - containers, err := a.Client.GetContainersByComponentAndAlias(componentName, *comp.Alias) + containers, err := a.Client.GetContainersByComponentAndAlias(componentName, comp.Container.Name) if err != nil { return false, errors.Wrapf(err, "unable to list containers for component %s", componentName) } var dockerVolumeMounts []mount.Mount - for _, vol := range a.componentAliasToVolumes[*comp.Alias] { + for _, vol := range a.componentAliasToVolumes[comp.Container.Name] { volMount := mount.Mount{ Type: mount.TypeVolume, Source: a.volumeNameToDockerVolName[*vol.Name], @@ -140,7 +140,7 @@ func (a Adapter) updateComponent() (componentExists bool, err error) { // Container doesn't exist, so need to pull its image (to be safe) and start a new container err = a.pullAndStartContainer(dockerVolumeMounts, projectVolumeName, comp) if err != nil { - return false, errors.Wrapf(err, "unable to pull and start container %s for component %s", *comp.Alias, componentName) + return false, errors.Wrapf(err, "unable to pull and start container %s for component %s", comp.Container.Name, componentName) } // Update componentExists so that we re-sync project and initialize supervisord if required @@ -155,7 +155,7 @@ func (a Adapter) updateComponent() (componentExists bool, err error) { return componentExists, errors.Wrapf(err, "unable to get the container config for component %s", componentName) } - portMap, namePortMapping, err := getPortMap(a.Context, comp.Endpoints, false) + portMap, namePortMapping, err := getPortMap(a.Context, comp.Container.Endpoints, false) if err != nil { return componentExists, errors.Wrapf(err, "unable to get the port map from env.yaml file for component %s", componentName) } @@ -167,22 +167,22 @@ func (a Adapter) updateComponent() (componentExists bool, err error) { if utils.DoesContainerNeedUpdating(comp, containerConfig, hostConfig, dockerVolumeMounts, mounts, portMap) { log.Infof("\nCreating Docker resources for component %s", a.ComponentName) - s := log.SpinnerNoSpin("Updating the component " + *comp.Alias) + s := log.SpinnerNoSpin("Updating the component " + comp.Container.Name) defer s.End(false) // Remove the container err := a.Client.RemoveContainer(containerID) if err != nil { - return componentExists, errors.Wrapf(err, "Unable to remove container %s for component %s", containerID, *comp.Alias) + return componentExists, errors.Wrapf(err, "Unable to remove container %s for component %s", containerID, comp.Container.Name) } // Start the container err = a.startComponent(dockerVolumeMounts, projectVolumeName, comp) if err != nil { - return false, errors.Wrapf(err, "Unable to start container for devfile component %s", *comp.Alias) + return false, errors.Wrapf(err, "Unable to start container for devfile component %s", comp.Container.Name) } - klog.V(3).Infof("Successfully created container %s for component %s", *comp.Image, componentName) + klog.V(3).Infof("Successfully created container %s for component %s", comp.Container.Image, componentName) s.End(true) // Update componentExists so that we re-sync project and initialize supervisord if required @@ -191,7 +191,7 @@ func (a Adapter) updateComponent() (componentExists bool, err error) { } else { // Multiple containers were returned with the specified label (which should be unique) // Error out, as this isn't expected - return true, fmt.Errorf("Found multiple running containers for devfile component %s and cannot push changes", *comp.Alias) + return true, fmt.Errorf("Found multiple running containers for devfile component %s and cannot push changes", comp.Container.Name) } } @@ -200,27 +200,27 @@ func (a Adapter) updateComponent() (componentExists bool, err error) { func (a Adapter) pullAndStartContainer(mounts []mount.Mount, projectVolumeName string, comp versionsCommon.DevfileComponent) error { // Container doesn't exist, so need to pull its image (to be safe) and start a new container - s := log.Spinnerf("Pulling image %s", *comp.Image) + s := log.Spinnerf("Pulling image %s", comp.Container.Image) - err := a.Client.PullImage(*comp.Image) + err := a.Client.PullImage(comp.Container.Image) if err != nil { s.End(false) - return errors.Wrapf(err, "Unable to pull %s image", *comp.Image) + return errors.Wrapf(err, "Unable to pull %s image", comp.Container.Image) } s.End(true) // Start the component container err = a.startComponent(mounts, projectVolumeName, comp) if err != nil { - return errors.Wrapf(err, "Unable to start container for devfile component %s", *comp.Alias) + return errors.Wrapf(err, "Unable to start container for devfile component %s", comp.Container.Name) } - klog.V(3).Infof("Successfully created container %s for component %s", *comp.Image, a.ComponentName) + klog.V(3).Infof("Successfully created container %s for component %s", comp.Container.Image, a.ComponentName) return nil } func (a Adapter) startComponent(mounts []mount.Mount, projectVolumeName string, comp versionsCommon.DevfileComponent) error { - hostConfig, namePortMapping, err := a.generateAndGetHostConfig(comp.Endpoints) + hostConfig, namePortMapping, err := a.generateAndGetHostConfig(comp.Container.Endpoints) hostConfig.Mounts = mounts if err != nil { return err @@ -234,15 +234,15 @@ func (a Adapter) startComponent(mounts []mount.Mount, projectVolumeName string, utils.UpdateComponentWithSupervisord(&comp, runCommand, a.supervisordVolumeName, &hostConfig) // If the component set `mountSources` to true, add the source volume and env CHE_PROJECTS_ROOT to it - if comp.MountSources { + if comp.Container.MountSources { utils.AddVolumeToContainer(projectVolumeName, lclient.OdoSourceVolumeMount, &hostConfig) - if !common.IsEnvPresent(comp.Env, common.EnvCheProjectsRoot) { + if !common.IsEnvPresent(comp.Container.Env, common.EnvCheProjectsRoot) { envName := common.EnvCheProjectsRoot envValue := lclient.OdoSourceVolumeMount - comp.Env = append(comp.Env, versionsCommon.DockerimageEnv{ - Name: &envName, - Value: &envValue, + comp.Container.Env = append(comp.Container.Env, &versionsCommon.Env{ + Name: envName, + Value: envValue, }) } } @@ -254,7 +254,7 @@ func (a Adapter) startComponent(mounts []mount.Mount, projectVolumeName string, } // Create the docker container - s := log.Spinner("Starting container for " + *comp.Image) + s := log.Spinner("Starting container for " + comp.Container.Image) defer s.End(false) err = a.Client.StartContainer(&containerConfig, &hostConfig, nil) if err != nil { @@ -267,16 +267,16 @@ func (a Adapter) startComponent(mounts []mount.Mount, projectVolumeName string, func (a Adapter) generateAndGetContainerConfig(componentName string, comp versionsCommon.DevfileComponent) container.Config { // Convert the env vars in the Devfile to the format expected by Docker - envVars := utils.ConvertEnvs(comp.Env) - ports := utils.ConvertPorts(comp.Endpoints) - containerLabels := utils.GetContainerLabels(componentName, *comp.Alias) + envVars := utils.ConvertEnvs(comp.Container.Env) + ports := utils.ConvertPorts(comp.Container.Endpoints) + containerLabels := utils.GetContainerLabels(componentName, comp.Container.Name) - containerConfig := a.Client.GenerateContainerConfig(*comp.Image, comp.Command, comp.Args, envVars, containerLabels, ports) + containerConfig := a.Client.GenerateContainerConfig(comp.Container.Image, []string{}, []string{}, envVars, containerLabels, ports) return containerConfig } -func (a Adapter) generateAndGetHostConfig(endpoints []versionsCommon.DockerimageEndpoint) (container.HostConfig, map[nat.Port]string, error) { +func (a Adapter) generateAndGetHostConfig(endpoints []*versionsCommon.Endpoint) (container.HostConfig, map[nat.Port]string, error) { // Convert the port bindings from env.yaml and generate docker host config portMap, namePortMapping, err := getPortMap(a.Context, endpoints, true) if err != nil { @@ -291,7 +291,7 @@ func (a Adapter) generateAndGetHostConfig(endpoints []versionsCommon.Dockerimage return hostConfig, namePortMapping, nil } -func getPortMap(context string, endpoints []versionsCommon.DockerimageEndpoint, show bool) (nat.PortMap, map[nat.Port]string, error) { +func getPortMap(context string, endpoints []*versionsCommon.Endpoint, show bool) (nat.PortMap, map[nat.Port]string, error) { // Convert the exposed and internal port pairs saved in env.yaml file to PortMap // Todo: Use context to get the approraite envinfo after context is supported in experimental mode portmap := nat.PortMap{} @@ -368,57 +368,51 @@ func (a Adapter) execDevfile(pushDevfileCommands []versionsCommon.DevfileCommand // Loop through each of the command given from the devfile for _, command := range pushDevfileCommands { // If the current command from the devfile is the currently expected command from the devfile - if command.Name == currentCommand.DefaultName || command.Name == currentCommand.AdapterName { + if command.Exec.Id == currentCommand.DefaultName || command.Exec.Id == currentCommand.AdapterName { // If the current command is not the last command in the slice // it is not expected to be the run command if i < len(commandOrder)-1 { // Any exec command such as "Init" and "Build" - for _, action := range command.Actions { - containerID := utils.GetContainerIDForAlias(containers, *action.Component) - compInfo := common.ComponentInfo{ - ContainerName: containerID, - } + containerID := utils.GetContainerIDForAlias(containers, command.Exec.Component) + compInfo := common.ComponentInfo{ + ContainerName: containerID, + } - err = exec.ExecuteDevfileBuildAction(&a.Client, action, command.Name, compInfo, show) - if err != nil { - return err - } + err = exec.ExecuteDevfileBuildAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) + if err != nil { + return err } // If the current command is the last command in the slice // it is expected to be the run command } else { // Last command is "Run" - klog.V(4).Infof("Executing devfile command %v", command.Name) + klog.V(4).Infof("Executing devfile command %v", command.Exec.Id) - for _, action := range command.Actions { - - // Check if the devfile run component containers have supervisord as the entrypoint. - // Start the supervisord if the odo component does not exist - if !componentExists { - err = a.InitRunContainerSupervisord(*action.Component, containers) - if err != nil { - return - } - } - - containerID := utils.GetContainerIDForAlias(containers, *action.Component) - compInfo := common.ComponentInfo{ - ContainerName: containerID, - } - - if componentExists && !common.IsRestartRequired(command) { - klog.V(4).Info("restart:false, Not restarting DevRun Command") - err = exec.ExecuteDevfileRunActionWithoutRestart(&a.Client, action, command.Name, compInfo, show) + // Check if the devfile run component containers have supervisord as the entrypoint. + // Start the supervisord if the odo component does not exist + if !componentExists { + err = a.InitRunContainerSupervisord(command.Exec.Component, containers) + if err != nil { return } + } - err = exec.ExecuteDevfileRunAction(&a.Client, action, command.Name, compInfo, show) + containerID := utils.GetContainerIDForAlias(containers, command.Exec.Component) + compInfo := common.ComponentInfo{ + ContainerName: containerID, + } + if componentExists && !common.IsRestartRequired(command) { + klog.V(4).Info("restart:false, Not restarting DevRun Command") + err = exec.ExecuteDevfileRunActionWithoutRestart(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) + return } - } + err = exec.ExecuteDevfileRunAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) + + } } } } diff --git a/pkg/devfile/adapters/docker/utils/utils.go b/pkg/devfile/adapters/docker/utils/utils.go index 6824e56a188..bccb551a2c2 100644 --- a/pkg/devfile/adapters/docker/utils/utils.go +++ b/pkg/devfile/adapters/docker/utils/utils.go @@ -18,7 +18,6 @@ import ( "github.com/openshift/odo/pkg/util" "github.com/pkg/errors" - "k8s.io/klog" ) const ( @@ -61,20 +60,20 @@ func GetContainerIDForAlias(containers []types.Container, alias string) string { } // ConvertEnvs converts environment variables from the devfile structure to an array of strings, as expected by Docker -func ConvertEnvs(vars []common.DockerimageEnv) []string { +func ConvertEnvs(vars []*common.Env) []string { dockerVars := []string{} for _, env := range vars { - envString := fmt.Sprintf("%s=%s", *env.Name, *env.Value) + envString := fmt.Sprintf("%s=%s", env.Name, env.Value) dockerVars = append(dockerVars, envString) } return dockerVars } // ConvertPorts converts endpoints from the devfile structure to PortSet, which is expected by Docker -func ConvertPorts(endpoints []common.DockerimageEndpoint) nat.PortSet { +func ConvertPorts(endpoints []*common.Endpoint) nat.PortSet { portSet := nat.PortSet{} for _, endpoint := range endpoints { - port := nat.Port(strconv.Itoa(int(*endpoint.Port)) + "/tcp") + port := nat.Port(strconv.Itoa(int(endpoint.TargetPort)) + "/tcp") portSet[port] = struct{}{} } return portSet @@ -87,7 +86,7 @@ func ConvertPorts(endpoints []common.DockerimageEndpoint) nat.PortSet { // so this function is necessary to prevent having to restart the container on every odo pushs func DoesContainerNeedUpdating(component common.DevfileComponent, containerConfig *container.Config, hostConfig *container.HostConfig, devfileMounts []mount.Mount, containerMounts []types.MountPoint, portMap nat.PortMap) bool { // If the image was changed in the devfile, the container needs to be updated - if *component.Image != containerConfig.Image { + if component.Container.Image != containerConfig.Image { return true } @@ -100,14 +99,14 @@ func DoesContainerNeedUpdating(component common.DevfileComponent, containerConfi // Update the container if the env vars were updated in the devfile // Need to convert the devfile envvars to the format expected by Docker - devfileEnvVars := ConvertEnvs(component.Env) + devfileEnvVars := ConvertEnvs(component.Container.Env) for _, envVar := range devfileEnvVars { if !containerHasEnvVar(envVar, containerConfig.Env) { return true } } - devfilePorts := ConvertPorts(component.Endpoints) + devfilePorts := ConvertPorts(component.Container.Endpoints) for port := range devfilePorts { if !containerHasPort(port, containerConfig.ExposedPorts) { return true @@ -209,33 +208,31 @@ func containerHasPort(devfilePort nat.Port, exposedPorts nat.PortSet) bool { func UpdateComponentWithSupervisord(comp *common.DevfileComponent, runCommand common.DevfileCommand, supervisordVolumeName string, hostConfig *container.HostConfig) { // Mount the supervisord volume for the run command container - for _, action := range runCommand.Actions { - if *action.Component == *comp.Alias { - AddVolumeToContainer(supervisordVolumeName, adaptersCommon.SupervisordMountPath, hostConfig) - - if len(comp.Command) == 0 && len(comp.Args) == 0 { - klog.V(4).Infof("Updating container %v entrypoint with supervisord", *comp.Alias) - comp.Command = append(comp.Command, adaptersCommon.SupervisordBinaryPath) - comp.Args = append(comp.Args, "-c", adaptersCommon.SupervisordConfFile) - } - - if !adaptersCommon.IsEnvPresent(comp.Env, adaptersCommon.EnvOdoCommandRun) { - envName := adaptersCommon.EnvOdoCommandRun - envValue := *action.Command - comp.Env = append(comp.Env, common.DockerimageEnv{ - Name: &envName, - Value: &envValue, - }) - } - - if !adaptersCommon.IsEnvPresent(comp.Env, adaptersCommon.EnvOdoCommandRunWorkingDir) && action.Workdir != nil { - envName := adaptersCommon.EnvOdoCommandRunWorkingDir - envValue := *action.Workdir - comp.Env = append(comp.Env, common.DockerimageEnv{ - Name: &envName, - Value: &envValue, - }) - } + if runCommand.Exec.Component == comp.Container.Name { + AddVolumeToContainer(supervisordVolumeName, adaptersCommon.SupervisordMountPath, hostConfig) + + //if len(comp.Command) == 0 && len(comp.Args) == 0 { + // klog.V(4).Infof("Updating container %v entrypoint with supervisord", *comp.Alias) + // comp.Command = append(comp.Command, adaptersCommon.SupervisordBinaryPath) + // comp.Args = append(comp.Args, "-c", adaptersCommon.SupervisordConfFile) + //} + + if !adaptersCommon.IsEnvPresent(comp.Container.Env, adaptersCommon.EnvOdoCommandRun) { + envName := adaptersCommon.EnvOdoCommandRun + envValue := runCommand.Exec.CommandLine + comp.Container.Env = append(comp.Container.Env, &common.Env{ + Name: envName, + Value: envValue, + }) + } + + if !adaptersCommon.IsEnvPresent(comp.Container.Env, adaptersCommon.EnvOdoCommandRunWorkingDir) && runCommand.Exec.WorkingDir != nil { + envName := adaptersCommon.EnvOdoCommandRunWorkingDir + envValue := runCommand.Exec.WorkingDir + comp.Container.Env = append(comp.Container.Env, &common.Env{ + Name: envName, + Value: *envValue, + }) } } } From d2553f5a7259f4214d19bdc0c20920d1897869c2 Mon Sep 17 00:00:00 2001 From: Aditi Sharma Date: Tue, 19 May 2020 14:18:57 +0530 Subject: [PATCH 07/23] Update Docker and Kubernetes adapter to use groups (#3206) * Update Docker and Kubernetes adapter to use groups * Update Some Validation Incorporate some review comments for commands Map Update Some Validation logic --- pkg/devfile/adapters/common/command.go | 35 ++++-- pkg/devfile/adapters/common/types.go | 9 ++ pkg/devfile/adapters/common/utils.go | 7 +- .../adapters/docker/component/utils.go | 104 +++++++---------- .../adapters/kubernetes/component/adapter.go | 108 ++++++++---------- 5 files changed, 133 insertions(+), 130 deletions(-) diff --git a/pkg/devfile/adapters/common/command.go b/pkg/devfile/adapters/common/command.go index 18f27786790..7e0c5980f8d 100644 --- a/pkg/devfile/adapters/common/command.go +++ b/pkg/devfile/adapters/common/command.go @@ -26,6 +26,23 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf if commandName != "" { if command.Exec.Id == commandName { + + if supportedCommand.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 } @@ -65,7 +82,7 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf func validateCommand(data data.DevfileData, command common.DevfileCommand) (err error) { // type must be exec - if command.Type != common.ExecCommandType { + if command.Exec == nil { return fmt.Errorf("Command must be of type \"exec\"") } @@ -84,7 +101,7 @@ func validateCommand(data data.DevfileData, command common.DevfileCommand) (err isActionValid := false for _, component := range components { - if command.Exec.Component == component.Container.Name && isComponentSupported(component) { + if command.Exec.Component == component.Container.Name { isActionValid = true } } @@ -127,8 +144,10 @@ func GetRunCommand(data data.DevfileData, devfileRunCmd string) (runCommand comm // 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) @@ -140,7 +159,7 @@ func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, de klog.V(3).Infof("No init command was provided") } else if !isInitCmdEmpty && initCmdErr == nil { isInitCommandValid = true - pushDevfileCommands = append(pushDevfileCommands, initCommand) + commandMap[common.InitCommandGroupType] = initCommand klog.V(3).Infof("Init command: %v", initCommand.Exec.Id) } @@ -153,14 +172,14 @@ func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, de klog.V(3).Infof("No build command was provided") } else if !reflect.DeepEqual(emptyCommand, buildCommand) && buildCmdErr == nil { isBuildCommandValid = true - pushDevfileCommands = append(pushDevfileCommands, buildCommand) + commandMap[common.BuildCommandGroupType] = buildCommand klog.V(3).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 + commandMap[common.RunCommandGroupType] = runCommand klog.V(3).Infof("Run command: %v", runCommand.Exec.Id) } @@ -176,8 +195,8 @@ 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 } diff --git a/pkg/devfile/adapters/common/types.go b/pkg/devfile/adapters/common/types.go index 3a4cb3882fb..9b03333f643 100644 --- a/pkg/devfile/adapters/common/types.go +++ b/pkg/devfile/adapters/common/types.go @@ -2,6 +2,7 @@ package common import ( devfileParser "github.com/openshift/odo/pkg/devfile/parser" + "github.com/openshift/odo/pkg/devfile/parser/data/common" "github.com/openshift/odo/pkg/envinfo" ) @@ -52,3 +53,11 @@ type ComponentInfo struct { PodName string ContainerName string } + +// PushCommandsMap stores the commands to be executed as per there types. +type PushCommandsMap map[common.DevfileCommandGroupType]common.DevfileCommand + +// NewPushCommandMap returns the instance of PushCommandsMap +func NewPushCommandMap() PushCommandsMap { + return make(map[common.DevfileCommandGroupType]common.DevfileCommand) +} diff --git a/pkg/devfile/adapters/common/utils.go b/pkg/devfile/adapters/common/utils.go index c286c459f1f..f570e25fc33 100644 --- a/pkg/devfile/adapters/common/utils.go +++ b/pkg/devfile/adapters/common/utils.go @@ -81,8 +81,11 @@ type CommandNames struct { } func isComponentSupported(component common.DevfileComponent) bool { - // Currently odo only uses devfile components of type dockerimage, since most of the Che registry devfiles use it - return component.Type == common.ContainerComponentType + // Currently odo only uses devfile components of type container, since most of the Che registry devfiles use it + if component.Container != nil { + return true + } + return false } // GetBootstrapperImage returns the odo-init bootstrapper image diff --git a/pkg/devfile/adapters/docker/component/utils.go b/pkg/devfile/adapters/docker/component/utils.go index d32594b4b6e..5e1974edda1 100644 --- a/pkg/devfile/adapters/docker/component/utils.go +++ b/pkg/devfile/adapters/docker/component/utils.go @@ -344,77 +344,61 @@ func getPortMap(context string, endpoints []*versionsCommon.Endpoint, show bool) // Executes all the commands from the devfile in order: init and build - which are both optional, and a compulsary run. // Init only runs once when the component is created. -func (a Adapter) execDevfile(pushDevfileCommands []versionsCommon.DevfileCommand, componentExists, show bool, containers []types.Container) (err error) { +func (a Adapter) execDevfile(commandsMap common.PushCommandsMap, componentExists, show bool, containers []types.Container) (err error) { // If nothing has been passed, then the devfile is missing the required run command - if len(pushDevfileCommands) == 0 { + if len(commandsMap) == 0 { return errors.New(fmt.Sprint("error executing devfile commands - there should be at least 1 command")) } - commandOrder := []common.CommandNames{} - // Only add runinit to the expected commands if the component doesn't already exist // This would be the case when first running the container if !componentExists { - commandOrder = append(commandOrder, common.CommandNames{DefaultName: string(common.DefaultDevfileInitCommand), AdapterName: a.devfileInitCmd}) + // Get Init Command + command, ok := commandsMap[versionsCommon.InitCommandGroupType] + if ok { + + containerID := utils.GetContainerIDForAlias(containers, command.Exec.Component) + compInfo := common.ComponentInfo{ContainerName: containerID} + err = exec.ExecuteDevfileBuildAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) + if err != nil { + return err + } + } } - commandOrder = append( - commandOrder, - common.CommandNames{DefaultName: string(common.DefaultDevfileBuildCommand), AdapterName: a.devfileBuildCmd}, - common.CommandNames{DefaultName: string(common.DefaultDevfileRunCommand), AdapterName: a.devfileRunCmd}, - ) - - // Loop through each of the expected commands in the devfile - for i, currentCommand := range commandOrder { - // Loop through each of the command given from the devfile - for _, command := range pushDevfileCommands { - // If the current command from the devfile is the currently expected command from the devfile - if command.Exec.Id == currentCommand.DefaultName || command.Exec.Id == currentCommand.AdapterName { - // If the current command is not the last command in the slice - // it is not expected to be the run command - if i < len(commandOrder)-1 { - // Any exec command such as "Init" and "Build" - - containerID := utils.GetContainerIDForAlias(containers, command.Exec.Component) - compInfo := common.ComponentInfo{ - ContainerName: containerID, - } - - err = exec.ExecuteDevfileBuildAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) - if err != nil { - return err - } - - // If the current command is the last command in the slice - // it is expected to be the run command - } else { - // Last command is "Run" - klog.V(4).Infof("Executing devfile command %v", command.Exec.Id) - - // Check if the devfile run component containers have supervisord as the entrypoint. - // Start the supervisord if the odo component does not exist - if !componentExists { - err = a.InitRunContainerSupervisord(command.Exec.Component, containers) - if err != nil { - return - } - } - - containerID := utils.GetContainerIDForAlias(containers, command.Exec.Component) - compInfo := common.ComponentInfo{ - ContainerName: containerID, - } - - if componentExists && !common.IsRestartRequired(command) { - klog.V(4).Info("restart:false, Not restarting DevRun Command") - err = exec.ExecuteDevfileRunActionWithoutRestart(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) - return - } - - err = exec.ExecuteDevfileRunAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) - } + // Get Build Command + command, ok := commandsMap[versionsCommon.BuildCommandGroupType] + if ok { + containerID := utils.GetContainerIDForAlias(containers, command.Exec.Component) + compInfo := common.ComponentInfo{ContainerName: containerID} + err = exec.ExecuteDevfileBuildAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) + if err != nil { + return err + } + } + + // Get Run command + command, ok = commandsMap[versionsCommon.RunCommandGroupType] + if ok { + klog.V(4).Infof("Executing devfile command %v", command.Exec.Id) + + // Check if the devfile run component containers have supervisord as the entrypoint. + // Start the supervisord if the odo component does not exist + if !componentExists { + err = a.InitRunContainerSupervisord(command.Exec.Component, containers) + if err != nil { + return } } + + containerID := utils.GetContainerIDForAlias(containers, command.Exec.Component) + compInfo := common.ComponentInfo{ContainerName: containerID} + if componentExists && !common.IsRestartRequired(command) { + klog.V(4).Info("restart:false, Not restarting DevRun Command") + err = exec.ExecuteDevfileRunActionWithoutRestart(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) + return + } + err = exec.ExecuteDevfileRunAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) } return diff --git a/pkg/devfile/adapters/kubernetes/component/adapter.go b/pkg/devfile/adapters/kubernetes/component/adapter.go index 4515ee6d322..8835e4d2665 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter.go @@ -305,76 +305,64 @@ func (a Adapter) waitAndGetComponentPod(hideSpinner bool) (*corev1.Pod, error) { // Executes all the commands from the devfile in order: init and build - which are both optional, and a compulsary run. // Init only runs once when the component is created. -func (a Adapter) execDevfile(pushDevfileCommands []versionsCommon.DevfileCommand, componentExists, show bool, podName string, containers []corev1.Container) (err error) { +func (a Adapter) execDevfile(commandsMap common.PushCommandsMap, componentExists, show bool, podName string, containers []corev1.Container) (err error) { // If nothing has been passed, then the devfile is missing the required run command - if len(pushDevfileCommands) == 0 { + if len(commandsMap) == 0 { return errors.New(fmt.Sprint("error executing devfile commands - there should be at least 1 command")) } - commandOrder := []common.CommandNames{} + compInfo := common.ComponentInfo{ + PodName: podName, + } // Only add runinit to the expected commands if the component doesn't already exist // This would be the case when first running the container if !componentExists { - commandOrder = append(commandOrder, common.CommandNames{DefaultName: string(common.DefaultDevfileInitCommand), AdapterName: a.devfileInitCmd}) - } - commandOrder = append( - commandOrder, - common.CommandNames{DefaultName: string(common.DefaultDevfileBuildCommand), AdapterName: a.devfileBuildCmd}, - common.CommandNames{DefaultName: string(common.DefaultDevfileRunCommand), AdapterName: a.devfileRunCmd}, - ) - - // Loop through each of the expected commands in the devfile - for i, currentCommand := range commandOrder { - // Loop through each of the command given from the devfile - for _, command := range pushDevfileCommands { - // If the current command from the devfile is the currently expected command from the devfile - if command.Exec.Id == currentCommand.DefaultName || command.Exec.Id == currentCommand.AdapterName { - // If the current command is not the last command in the slice - // it is not expected to be the run command - if i < len(commandOrder)-1 { - // Any exec command such as "Init" and "Build" - - compInfo := common.ComponentInfo{ - ContainerName: command.Exec.Component, - PodName: podName, - } - - err = exec.ExecuteDevfileBuildAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) - if err != nil { - return err - } - - // If the current command is the last command in the slice - // it is expected to be the run command - } else { - // Last command is "Run" - klog.V(4).Infof("Executing devfile command %v", command.Exec.Id) - - // Check if the devfile run component containers have supervisord as the entrypoint. - // Start the supervisord if the odo component does not exist - if !componentExists { - err = a.InitRunContainerSupervisord(command.Exec.Component, podName, containers) - if err != nil { - return - } - } - - compInfo := common.ComponentInfo{ - ContainerName: command.Exec.Component, - PodName: podName, - } - - if componentExists && !common.IsRestartRequired(command) { - klog.V(4).Infof("restart:false, Not restarting DevRun Command") - err = exec.ExecuteDevfileRunActionWithoutRestart(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) - return - } - - err = exec.ExecuteDevfileRunAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) - } + // Get Init Command + command, ok := commandsMap[versionsCommon.InitCommandGroupType] + if ok { + compInfo.ContainerName = command.Exec.Component + err = exec.ExecuteDevfileBuildAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) + if err != nil { + return err } + } + + } + + // Get Build Command + command, ok := commandsMap[versionsCommon.BuildCommandGroupType] + if ok { + compInfo.ContainerName = command.Exec.Component + err = exec.ExecuteDevfileBuildAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) + if err != nil { + return err + } + } + + // Get Run Command + command, ok = commandsMap[versionsCommon.RunCommandGroupType] + if ok { + klog.V(4).Infof("Executing devfile command %v", command.Exec.Id) + compInfo.ContainerName = command.Exec.Component + + // Check if the devfile run component containers have supervisord as the entrypoint. + // Start the supervisord if the odo component does not exist + if !componentExists { + err = a.InitRunContainerSupervisord(command.Exec.Component, podName, containers) + if err != nil { + return + } + } + + if componentExists && !common.IsRestartRequired(command) { + klog.V(4).Infof("restart:false, Not restarting DevRun Command") + err = exec.ExecuteDevfileRunActionWithoutRestart(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) + return + } + err = exec.ExecuteDevfileRunAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, show) + } return From 82b979dab3b9203cfe3f2cdc8782f976d3e291fd Mon Sep 17 00:00:00 2001 From: Aditi Sharma Date: Tue, 19 May 2020 16:16:09 +0530 Subject: [PATCH 08/23] Updated Logs for V2 (#3208) Fixed interface implementation for v2 Updated logs refactored commands.go --- pkg/devfile/adapters/common/command.go | 41 +++++++++------------ pkg/devfile/adapters/common/utils.go | 2 +- pkg/devfile/parser/data/2.0.0/components.go | 4 +- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/pkg/devfile/adapters/common/command.go b/pkg/devfile/adapters/common/command.go index 7e0c5980f8d..b77a511be3f 100644 --- a/pkg/devfile/adapters/common/command.go +++ b/pkg/devfile/adapters/common/command.go @@ -10,7 +10,7 @@ import ( ) // GetCommand iterates through the devfile commands and returns the associated devfile command -func getCommand(data data.DevfileData, commandName string, groupType common.DevfileCommandGroupType, required bool) (supportedCommand common.DevfileCommand, err error) { +func getCommand(data data.DevfileData, commandName string, groupType common.DevfileCommandGroupType) (supportedCommand common.DevfileCommand, err error) { for _, command := range data.GetCommands() { // validate command @@ -62,14 +62,18 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf } } - // 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) + // 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 { - // Not found and optional, so just log it - klog.V(3).Info(msg) + 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(3).Info(msg) + + } } return @@ -115,30 +119,19 @@ func validateCommand(data data.DevfileData, command common.DevfileCommand) (err // 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, common.InitCommandGroupType, true) - } - // a init command was not specified so if it is not found then it is not an error - return getCommand(data, "", common.InitCommandGroupType, false) + return getCommand(data, devfileInitCmd, common.InitCommandGroupType) } // 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, common.BuildCommandGroupType, true) - } - // a build command was not specified so if it is not found then it is not an error - return getCommand(data, "", common.BuildCommandGroupType, 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, common.RunCommandGroupType, true) - } - return getCommand(data, "", common.RunCommandGroupType, true) + + return getCommand(data, devfileRunCmd, common.RunCommandGroupType) } // ValidateAndGetPushDevfileCommands validates the build and the run command, diff --git a/pkg/devfile/adapters/common/utils.go b/pkg/devfile/adapters/common/utils.go index f570e25fc33..56e5d230fb1 100644 --- a/pkg/devfile/adapters/common/utils.go +++ b/pkg/devfile/adapters/common/utils.go @@ -83,6 +83,7 @@ type CommandNames struct { func isComponentSupported(component common.DevfileComponent) bool { // Currently odo only uses devfile components of type container, since most of the Che registry devfiles use it if component.Container != nil { + klog.V(3).Infof("Found component \"%v\" with name \"%v\"\n", common.ContainerComponentType, component.Container.Name) return true } return false @@ -102,7 +103,6 @@ func GetSupportedComponents(data data.DevfileData) []common.DevfileComponent { // Only components with aliases are considered because without an alias commands cannot reference them for _, comp := range data.GetAliasedComponents() { if isComponentSupported(comp) { - klog.V(3).Infof("Found component \"%v\" with alias \"%v\"\n", comp.Type, comp.Container.Name) components = append(components, comp) } } diff --git a/pkg/devfile/parser/data/2.0.0/components.go b/pkg/devfile/parser/data/2.0.0/components.go index 986eae361e2..127499cd485 100644 --- a/pkg/devfile/parser/data/2.0.0/components.go +++ b/pkg/devfile/parser/data/2.0.0/components.go @@ -19,8 +19,8 @@ func (d *Devfile200) GetParent() common.DevfileParent { return d.Parent } -// GetProject returns the DevfileProject Object parsed from devfile -func (d *Devfile200) GetProject() []common.DevfileProject { +// GetProjects returns the DevfileProject Object parsed from devfile +func (d *Devfile200) GetProjects() []common.DevfileProject { return d.Projects } From cd8d466b4597cc112317e6074308f4e3260f86ea Mon Sep 17 00:00:00 2001 From: Aditi Sharma Date: Tue, 19 May 2020 18:22:31 +0530 Subject: [PATCH 09/23] Avoid String Pointers (#3209) While converting v1 to v2 types, string pointers are prone to cause null pointer error. This PR updates struct fields from string pointers to string --- pkg/devfile/adapters/docker/utils/utils.go | 4 +- .../adapters/kubernetes/utils/utils.go | 4 +- pkg/devfile/parser/data/1.0.0/components.go | 32 ++++---- pkg/devfile/parser/data/1.0.0/types.go | 82 +++++++++---------- pkg/devfile/parser/data/common/types.go | 4 +- pkg/exec/devfile.go | 4 +- pkg/odo/cli/component/create.go | 4 +- pkg/sync/adapter.go | 8 +- 8 files changed, 71 insertions(+), 71 deletions(-) diff --git a/pkg/devfile/adapters/docker/utils/utils.go b/pkg/devfile/adapters/docker/utils/utils.go index bccb551a2c2..bde669f7658 100644 --- a/pkg/devfile/adapters/docker/utils/utils.go +++ b/pkg/devfile/adapters/docker/utils/utils.go @@ -226,12 +226,12 @@ func UpdateComponentWithSupervisord(comp *common.DevfileComponent, runCommand co }) } - if !adaptersCommon.IsEnvPresent(comp.Container.Env, adaptersCommon.EnvOdoCommandRunWorkingDir) && runCommand.Exec.WorkingDir != nil { + if !adaptersCommon.IsEnvPresent(comp.Container.Env, adaptersCommon.EnvOdoCommandRunWorkingDir) && runCommand.Exec.WorkingDir != "" { envName := adaptersCommon.EnvOdoCommandRunWorkingDir envValue := runCommand.Exec.WorkingDir comp.Container.Env = append(comp.Container.Env, &common.Env{ Name: envName, - Value: *envValue, + Value: envValue, }) } } diff --git a/pkg/devfile/adapters/kubernetes/utils/utils.go b/pkg/devfile/adapters/kubernetes/utils/utils.go index aa393980894..f414388e6ca 100644 --- a/pkg/devfile/adapters/kubernetes/utils/utils.go +++ b/pkg/devfile/adapters/kubernetes/utils/utils.go @@ -145,12 +145,12 @@ func UpdateContainersWithSupervisord(devfileObj devfileParser.DevfileObj, contai }) } - if !isEnvPresent(container.Env, adaptersCommon.EnvOdoCommandRunWorkingDir) && runCommand.Exec.WorkingDir != nil { + if !isEnvPresent(container.Env, adaptersCommon.EnvOdoCommandRunWorkingDir) && runCommand.Exec.WorkingDir != "" { klog.V(3).Infof("Updating container %v env with run command's workdir", container.Name) container.Env = append(container.Env, corev1.EnvVar{ Name: adaptersCommon.EnvOdoCommandRunWorkingDir, - Value: *runCommand.Exec.WorkingDir, + Value: runCommand.Exec.WorkingDir, }) } diff --git a/pkg/devfile/parser/data/1.0.0/components.go b/pkg/devfile/parser/data/1.0.0/components.go index b27b5c1afdd..a951a839412 100644 --- a/pkg/devfile/parser/data/1.0.0/components.go +++ b/pkg/devfile/parser/data/1.0.0/components.go @@ -7,7 +7,7 @@ import ( func (d *Devfile100) GetMetadata() common.DevfileMetadata { // No GenerateName field in V2 return common.DevfileMetadata{ - Name: *d.Metadata.Name, + Name: d.Metadata.Name, //Version: No field in V1 } } @@ -80,11 +80,11 @@ func convertV1CommandToCommon(c Command) (d common.DevfileCommand) { for _, action := range c.Actions { - if *action.Type == DevfileCommandTypeExec { + if action.Type == DevfileCommandTypeExec { exec = common.Exec{ Attributes: c.Attributes, - CommandLine: *action.Command, - Component: *action.Component, + CommandLine: action.Command, + Component: action.Component, Group: getGroup(c.Name), Id: c.Name, WorkingDir: action.Workdir, @@ -120,11 +120,11 @@ func convertV1ComponentToCommon(c Component) (d common.DevfileComponent) { } container := common.Container{ - Name: *c.Alias, + Name: c.Alias, Endpoints: endpoints, Env: envs, - Image: *c.ComponentDockerimage.Image, - MemoryLimit: *c.ComponentDockerimage.MemoryLimit, + Image: c.ComponentDockerimage.Image, + MemoryLimit: c.ComponentDockerimage.MemoryLimit, MountSources: c.MountSources, VolumeMounts: volumes, // SourceMapping: Not present in V1 @@ -139,32 +139,32 @@ func convertV1EndpointsToCommon(e DockerimageEndpoint) *common.Endpoint { return &common.Endpoint{ // Attributes: // Configuration: - Name: *e.Name, - TargetPort: *e.Port, + Name: e.Name, + TargetPort: e.Port, } } func convertV1EnvToCommon(e DockerimageEnv) *common.Env { return &common.Env{ - Name: *e.Name, - Value: *e.Value, + Name: e.Name, + Value: e.Value, } } func convertV1VolumeToCommon(v DockerimageVolume) *common.VolumeMount { return &common.VolumeMount{ - Name: *v.Name, - Path: *v.ContainerPath, + Name: v.Name, + Path: v.ContainerPath, } } func convertV1ProjectToCommon(p Project) common.DevfileProject { git := common.Git{ - Branch: *p.Source.Branch, + Branch: p.Source.Branch, Location: p.Source.Location, - SparseCheckoutDir: *p.Source.SparseCheckoutDir, - StartPoint: *p.Source.StartPoint, + SparseCheckoutDir: p.Source.SparseCheckoutDir, + StartPoint: p.Source.StartPoint, } return common.DevfileProject{ diff --git a/pkg/devfile/parser/data/1.0.0/types.go b/pkg/devfile/parser/data/1.0.0/types.go index af9774f81ca..5f59003ab6e 100644 --- a/pkg/devfile/parser/data/1.0.0/types.go +++ b/pkg/devfile/parser/data/1.0.0/types.go @@ -63,18 +63,18 @@ type Metadata struct { // Workspaces created from devfile, will use it as base and append random suffix. // It's used when name is not defined. - GenerateName *string `yaml:"generateName,omitempty" json:"generateName,omitempty"` + GenerateName string `yaml:"generateName,omitempty" json:"generateName,omitempty"` // The name of the devfile. Workspaces created from devfile, will inherit this // name - Name *string `yaml:"name,omitempty" json:"name,omitempty"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` } // Description of the projects, containing names and sources locations type Project struct { // The path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name." - ClonePath *string `yaml:"clonePath,omitempty" json:"clonePath,omitempty"` + ClonePath string `yaml:"clonePath,omitempty" json:"clonePath,omitempty"` // The Project Name Name string `yaml:"name" json:"name"` @@ -92,21 +92,21 @@ type ProjectSource struct { // The name of the of the branch to check out after obtaining the source from the location. // The branch has to already exist in the source otherwise the default branch is used. // In case of git, this is also the name of the remote branch to push to. - Branch *string `yaml:"branch,omitempty" json:"branch,omitempty"` + Branch string `yaml:"branch,omitempty" json:"branch,omitempty"` // The id of the commit to reset the checked out branch to. // Note that this is equivalent to 'startPoint' and provided for convenience. - CommitId *string `yaml:"commitId,omitempty" json:"commitId,omitempty"` + CommitId string `yaml:"commitId,omitempty" json:"commitId,omitempty"` // Part of project to populate in the working directory. - SparseCheckoutDir *string `yaml:"sparseCheckoutDir,omitempty" json:"sparseCheckoutDir,omitempty"` + SparseCheckoutDir string `yaml:"sparseCheckoutDir,omitempty" json:"sparseCheckoutDir,omitempty"` // The tag or commit id to reset the checked out branch to. - StartPoint *string `yaml:"startPoint,omitempty" json:"startPoint,omitempty"` + StartPoint string `yaml:"startPoint,omitempty" json:"startPoint,omitempty"` // The name of the tag to reset the checked out branch to. // Note that this is equivalent to 'startPoint' and provided for convenience. - Tag *string `yaml:"tag,omitempty" json:"tag,omitempty"` + Tag string `yaml:"tag,omitempty" json:"tag,omitempty"` } type Command struct { @@ -127,31 +127,31 @@ type Command struct { } type CommandPreviewUrl struct { - Port *int32 `yaml:"port,omitempty" json:"port,omitempty"` - Path *string `yaml:"path,omitempty" json:"path,omitempty"` + Port int32 `yaml:"port,omitempty" json:"port,omitempty"` + Path string `yaml:"path,omitempty" json:"path,omitempty"` } type CommandAction struct { // The actual action command-line string - Command *string `yaml:"command,omitempty" json:"command,omitempty"` + Command string `yaml:"command,omitempty" json:"command,omitempty"` // Describes component to which given action relates - Component *string `yaml:"component,omitempty" json:"component,omitempty"` + Component string `yaml:"component,omitempty" json:"component,omitempty"` // the path relative to the location of the devfile to the configuration file // defining one or more actions in the editor-specific format - Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` + Reference string `yaml:"reference,omitempty" json:"reference,omitempty"` // The content of the referenced configuration file that defines one or more // actions in the editor-specific format - ReferenceContent *string `yaml:"referenceContent,omitempty" json:"referenceContent,omitempty"` + ReferenceContent string `yaml:"referenceContent,omitempty" json:"referenceContent,omitempty"` // Describes action type - Type *CommandType `yaml:"type,omitempty" json:"type,omitempty"` + Type CommandType `yaml:"type,omitempty" json:"type,omitempty"` // Working directory where the command should be executed - Workdir *string `yaml:"workdir,omitempty" json:"workdir,omitempty"` + Workdir string `yaml:"workdir,omitempty" json:"workdir,omitempty"` } type Component struct { @@ -159,7 +159,7 @@ type Component struct { // The name using which other places of this devfile (like commands) can refer to // this component. This attribute is optional but must be unique in the devfile if // specified. - Alias *string `yaml:"alias,omitempty" json:"alias,omitempty"` + Alias string `yaml:"alias,omitempty" json:"alias,omitempty"` // Describes whether projects sources should be mount to the component. // `CHE_PROJECTS_ROOT` environment variable should contains a path where projects @@ -178,36 +178,36 @@ type Component struct { } type ComponentChePlugin struct { - Id *string `yaml:"id,omitempty" json:"id,omitempty"` - Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` - RegistryUrl *string `yaml:"registryUrl,omitempty" json:"registryUrl,omitempty"` + Id string `yaml:"id,omitempty" json:"id,omitempty"` + Reference string `yaml:"reference,omitempty" json:"reference,omitempty"` + RegistryUrl string `yaml:"registryUrl,omitempty" json:"registryUrl,omitempty"` } type ComponentCheEditor struct { - Id *string `yaml:"id,omitempty" json:"id,omitempty"` - Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` - RegistryUrl *string `yaml:"registryUrl,omitempty" json:"registryUrl,omitempty"` + Id string `yaml:"id,omitempty" json:"id,omitempty"` + Reference string `yaml:"reference,omitempty" json:"reference,omitempty"` + RegistryUrl string `yaml:"registryUrl,omitempty" json:"registryUrl,omitempty"` } type ComponentOpenshift struct { - Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` - ReferenceContent *string `yaml:"referenceContent,omitempty" json:"referenceContent,omitempty"` - Selector *string `yaml:"selector,omitempty" json:"selector,omitempty"` - EntryPoints *string `yaml:"entryPoints,omitempty" json:"entryPoints,omitempty"` - MemoryLimit *string `yaml:"memoryLimit,omitempty" json:"memoryLimit,omitempty"` + Reference string `yaml:"reference,omitempty" json:"reference,omitempty"` + ReferenceContent string `yaml:"referenceContent,omitempty" json:"referenceContent,omitempty"` + Selector string `yaml:"selector,omitempty" json:"selector,omitempty"` + EntryPoints string `yaml:"entryPoints,omitempty" json:"entryPoints,omitempty"` + MemoryLimit string `yaml:"memoryLimit,omitempty" json:"memoryLimit,omitempty"` } type ComponentKubernetes struct { - Reference *string `yaml:"reference,omitempty" json:"reference,omitempty"` - ReferenceContent *string `yaml:"referenceContent,omitempty" json:"referenceContent,omitempty"` - Selector *string `yaml:"selector,omitempty" json:"selector,omitempty"` - EntryPoints *string `yaml:"entryPoints,omitempty" json:"entryPoints,omitempty"` - MemoryLimit *string `yaml:"memoryLimit,omitempty" json:"memoryLimit,omitempty"` + Reference string `yaml:"reference,omitempty" json:"reference,omitempty"` + ReferenceContent string `yaml:"referenceContent,omitempty" json:"referenceContent,omitempty"` + Selector string `yaml:"selector,omitempty" json:"selector,omitempty"` + EntryPoints string `yaml:"entryPoints,omitempty" json:"entryPoints,omitempty"` + MemoryLimit string `yaml:"memoryLimit,omitempty" json:"memoryLimit,omitempty"` } type ComponentDockerimage struct { - Image *string `yaml:"image,omitempty" json:"image,omitempty"` - MemoryLimit *string `yaml:"memoryLimit,omitempty" json:"memoryLimit,omitempty"` + Image string `yaml:"image,omitempty" json:"image,omitempty"` + MemoryLimit string `yaml:"memoryLimit,omitempty" json:"memoryLimit,omitempty"` Command []string `yaml:"command,omitempty" json:"command,omitempty"` Args []string `yaml:"args,omitempty" json:"args,omitempty"` Volumes []DockerimageVolume `yaml:"volumes,omitempty" json:"volumes,omitempty"` @@ -216,16 +216,16 @@ type ComponentDockerimage struct { } type DockerimageVolume struct { - Name *string `yaml:"name,omitempty" json:"name,omitempty"` - ContainerPath *string `yaml:"containerPath,omitempty" json:"containerPath,omitempty"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` + ContainerPath string `yaml:"containerPath,omitempty" json:"containerPath,omitempty"` } type DockerimageEnv struct { - Name *string `yaml:"name,omitempty" json:"name,omitempty"` - Value *string `yaml:"value,omitempty" json:"value,omitempty"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Value string `yaml:"value,omitempty" json:"value,omitempty"` } type DockerimageEndpoint struct { - Name *string `yaml:"name,omitempty" json:"name,omitempty"` - Port *int32 `yaml:"port,omitempty" json:"port,omitempty"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Port int32 `yaml:"port,omitempty" json:"port,omitempty"` } diff --git a/pkg/devfile/parser/data/common/types.go b/pkg/devfile/parser/data/common/types.go index 6e0b5064eff..bfaa132996d 100644 --- a/pkg/devfile/parser/data/common/types.go +++ b/pkg/devfile/parser/data/common/types.go @@ -97,7 +97,7 @@ type DevfileComponent struct { type DevfileProject struct { // Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name. - ClonePath *string `json:"clonePath,omitempty"` + ClonePath string `json:"clonePath,omitempty"` // Project's Custom source Custom *Custom `json:"custom,omitempty"` @@ -272,7 +272,7 @@ type Exec struct { Label string `json:"label,omitempty"` // Working directory where the command should be executed - WorkingDir *string `json:"workingDir,omitempty"` + WorkingDir string `json:"workingDir,omitempty"` } // Git Project's Git source diff --git a/pkg/exec/devfile.go b/pkg/exec/devfile.go index ce639ab422b..1485bc8743e 100644 --- a/pkg/exec/devfile.go +++ b/pkg/exec/devfile.go @@ -15,9 +15,9 @@ func ExecuteDevfileBuildAction(client ExecClient, exec common.Exec, commandName // Change to the workdir and execute the command var cmdArr []string - if exec.WorkingDir != nil { + if exec.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{adaptersCommon.ShellExecutable, "-c", "cd " + *exec.WorkingDir + " && " + exec.CommandLine} + cmdArr = []string{adaptersCommon.ShellExecutable, "-c", "cd " + exec.WorkingDir + " && " + exec.CommandLine} } else { cmdArr = []string{adaptersCommon.ShellExecutable, "-c", exec.CommandLine} } diff --git a/pkg/odo/cli/component/create.go b/pkg/odo/cli/component/create.go index f90902ea15a..182860dd2de 100644 --- a/pkg/odo/cli/component/create.go +++ b/pkg/odo/cli/component/create.go @@ -750,8 +750,8 @@ func (co *CreateOptions) downloadProject(projectPassed string) error { return errors.Wrapf(err, "Could not get the current working directory.") } - if project.ClonePath != nil && *project.ClonePath != "" { - clonePath := *project.ClonePath + if project.ClonePath != "" { + clonePath := project.ClonePath if runtime.GOOS == "windows" { clonePath = strings.Replace(clonePath, "\\", "/", -1) } diff --git a/pkg/sync/adapter.go b/pkg/sync/adapter.go index cb6d47f3557..57a27002269 100644 --- a/pkg/sync/adapter.go +++ b/pkg/sync/adapter.go @@ -197,14 +197,14 @@ func getSyncFolder(projects []versionsCommon.DevfileProject) (string, error) { project := projects[0] // If the clonepath is set to a value, set it to be the sync folder // As some devfiles rely on the code being synced to the folder in the clonepath - if project.ClonePath != nil { - if strings.HasPrefix(*project.ClonePath, "/") { + if project.ClonePath != "" { + if strings.HasPrefix(project.ClonePath, "/") { return "", fmt.Errorf("the clonePath in the devfile must be a relative path") } - if strings.Contains(*project.ClonePath, "..") { + if strings.Contains(project.ClonePath, "..") { return "", fmt.Errorf("the clonePath in the devfile cannot escape the projects root. Don't use .. to try and do that") } - return filepath.ToSlash(filepath.Join(kclient.OdoSourceVolumeMount, *project.ClonePath)), nil + return filepath.ToSlash(filepath.Join(kclient.OdoSourceVolumeMount, project.ClonePath)), nil } return filepath.ToSlash(filepath.Join(kclient.OdoSourceVolumeMount, projects[0].Name)), nil } From 65841d90d970581856c1be3fc7be62abb37b1595 Mon Sep 17 00:00:00 2001 From: Aditi Sharma Date: Tue, 19 May 2020 18:43:35 +0530 Subject: [PATCH 10/23] Update commands check --- pkg/devfile/adapters/common/command.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/devfile/adapters/common/command.go b/pkg/devfile/adapters/common/command.go index b77a511be3f..b21ca9a2de9 100644 --- a/pkg/devfile/adapters/common/command.go +++ b/pkg/devfile/adapters/common/command.go @@ -91,12 +91,12 @@ func validateCommand(data data.DevfileData, command common.DevfileCommand) (err } // component must be specified - if &command.Exec.Component == nil || command.Exec.Component == "" { + if command.Exec.Component == "" { return fmt.Errorf("Exec commands must reference a component") } // must specify a command - if &command.Exec.CommandLine == nil || command.Exec.CommandLine == "" { + if command.Exec.CommandLine == "" { return fmt.Errorf("Exec commands must have a command") } From b33a34849196d57432d9659fc4d2e6d310f47589 Mon Sep 17 00:00:00 2001 From: Mrinal Das Date: Wed, 20 May 2020 15:24:39 +0530 Subject: [PATCH 11/23] Fixes lower and upper case for commands (#3219) --- pkg/devfile/parser/data/1.0.0/components.go | 27 ++++++++++----------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/pkg/devfile/parser/data/1.0.0/components.go b/pkg/devfile/parser/data/1.0.0/components.go index a951a839412..2f30b39022f 100644 --- a/pkg/devfile/parser/data/1.0.0/components.go +++ b/pkg/devfile/parser/data/1.0.0/components.go @@ -2,6 +2,7 @@ package version100 import ( "github.com/openshift/odo/pkg/devfile/parser/data/common" + "strings" ) func (d *Devfile100) GetMetadata() common.DevfileMetadata { @@ -176,21 +177,19 @@ func convertV1ProjectToCommon(p Project) common.DevfileProject { } func getGroup(name string) *common.Group { - var kind common.DevfileCommandGroupType + group := common.Group{} - switch name { - case "devRun": - kind = common.RunCommandGroupType - case "devBuild": - kind = common.BuildCommandGroupType - case "devInit": - kind = common.InitCommandGroupType - default: - kind = "" + switch strings.ToLower(name) { + case "devrun": + group.Kind = common.RunCommandGroupType + group.IsDefault = true + case "devbuild": + group.Kind = common.BuildCommandGroupType + group.IsDefault = true + case "devinit": + group.Kind = common.InitCommandGroupType + group.IsDefault = true } - return &common.Group{ - // TODO(adi): IsDefault: - Kind: kind, - } + return &group } From 9132a1243df7e802f530b169391217f63264650a Mon Sep 17 00:00:00 2001 From: Mrinal Das Date: Thu, 21 May 2020 14:28:41 +0530 Subject: [PATCH 12/23] Fixes type of project and components for devfile v1 (#3228) --- pkg/devfile/parser/data/1.0.0/components.go | 13 +++++++------ pkg/devfile/parser/data/2.0.0/types.go | 2 +- pkg/devfile/validate/components.go | 2 +- pkg/odo/cli/component/create.go | 9 ++++----- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pkg/devfile/parser/data/1.0.0/components.go b/pkg/devfile/parser/data/1.0.0/components.go index 2f30b39022f..567c7fa7609 100644 --- a/pkg/devfile/parser/data/1.0.0/components.go +++ b/pkg/devfile/parser/data/1.0.0/components.go @@ -103,7 +103,7 @@ func convertV1CommandToCommon(c Command) (d common.DevfileCommand) { } } -func convertV1ComponentToCommon(c Component) (d common.DevfileComponent) { +func convertV1ComponentToCommon(c Component) (component common.DevfileComponent) { var endpoints []*common.Endpoint for _, v := range c.ComponentDockerimage.Endpoints { @@ -131,9 +131,9 @@ func convertV1ComponentToCommon(c Component) (d common.DevfileComponent) { // SourceMapping: Not present in V1 } - d = common.DevfileComponent{Container: &container} + component = common.DevfileComponent{Container: &container} - return d + return component } func convertV1EndpointsToCommon(e DockerimageEndpoint) *common.Endpoint { @@ -169,9 +169,10 @@ func convertV1ProjectToCommon(p Project) common.DevfileProject { } return common.DevfileProject{ - ClonePath: p.ClonePath, - Name: p.Name, - Git: &git, + ClonePath: p.ClonePath, + Git: &git, + Name: p.Name, + SourceType: common.GitProjectSourceType, } } diff --git a/pkg/devfile/parser/data/2.0.0/types.go b/pkg/devfile/parser/data/2.0.0/types.go index 5c801cf9d51..1b884fcf044 100644 --- a/pkg/devfile/parser/data/2.0.0/types.go +++ b/pkg/devfile/parser/data/2.0.0/types.go @@ -126,7 +126,7 @@ type Component struct { Openshift *Openshift `json:"openshift,omitempty"` // Type of project source - Type CommandType `json:"type,omitempty"` + Type ComponentType `json:"type,omitempty"` // Volume component Volume *Volume `json:"volume,omitempty"` diff --git a/pkg/devfile/validate/components.go b/pkg/devfile/validate/components.go index 30168bfa954..8e8a362664d 100644 --- a/pkg/devfile/validate/components.go +++ b/pkg/devfile/validate/components.go @@ -23,7 +23,7 @@ func ValidateComponents(components []common.DevfileComponent) error { // Check weather component of type container is present isContainerComponentPresent := false for _, component := range components { - if component.Type == common.ContainerComponentType { + if component.Container != nil { isContainerComponentPresent = true break } diff --git a/pkg/odo/cli/component/create.go b/pkg/odo/cli/component/create.go index 182860dd2de..49011ba921b 100644 --- a/pkg/odo/cli/component/create.go +++ b/pkg/odo/cli/component/create.go @@ -771,8 +771,7 @@ func (co *CreateOptions) downloadProject(projectPassed string) error { } var url string - switch project.SourceType { - case common.GitProjectSourceType: + if project.Git != nil { if strings.Contains(project.Git.Location, "github.com") { url, err = util.GetGitHubZipURL(project.Git.Location) if err != nil { @@ -781,14 +780,14 @@ func (co *CreateOptions) downloadProject(projectPassed string) error { } else { return errors.Errorf("Project type git with non github url not supported") } - case common.GitHubProjectSourceType: + } else if project.Github != nil { url, err = util.GetGitHubZipURL(project.Github.Location) if err != nil { return err } - case common.ZipProjectSourceType: + } else if project.Zip != nil { url = project.Zip.Location - default: + } else { return errors.Errorf("Project type not supported") } From 9d8eac592c5c062faa8f88fe16959aaf7b3c0e6e Mon Sep 17 00:00:00 2001 From: Aditi Sharma Date: Thu, 21 May 2020 17:13:42 +0530 Subject: [PATCH 13/23] Update testing utils (#3220) * Update command tests Updated Command tests to v2 Removed some cases like command type validation, that will be validated by schema only. * Fix common adapters tests All devfile.adapters.common unit tests are fixed * Add tests for Mismatched type Fix devfile.adapters.Kubernetes.Component unit tests * Add TestCase for default command --- pkg/devfile/adapters/common/command.go | 21 +- pkg/devfile/adapters/common/command_test.go | 631 ++++++++++-------- pkg/devfile/adapters/common/utils.go | 4 +- pkg/devfile/adapters/common/utils_test.go | 71 +- .../adapters/docker/component/utils.go | 6 +- pkg/devfile/adapters/docker/utils/utils.go | 8 +- .../kubernetes/component/adapter_test.go | 58 +- .../adapters/kubernetes/utils/utils.go | 4 +- pkg/devfile/parser/data/1.0.0/components.go | 24 +- pkg/devfile/parser/data/common/types.go | 8 +- pkg/devfile/parser/writer_test.go | 13 +- pkg/devfile/validate/components_test.go | 8 +- pkg/testingutil/devfile.go | 156 ++--- vendor/k8s.io/api/go.sum | 1 + 14 files changed, 525 insertions(+), 488 deletions(-) diff --git a/pkg/devfile/adapters/common/command.go b/pkg/devfile/adapters/common/command.go index b21ca9a2de9..30515ebda0c 100644 --- a/pkg/devfile/adapters/common/command.go +++ b/pkg/devfile/adapters/common/command.go @@ -13,6 +13,7 @@ import ( func getCommand(data data.DevfileData, commandName string, groupType common.DevfileCommandGroupType) (supportedCommand common.DevfileCommand, err error) { for _, command := range data.GetCommands() { + // validate command err = validateCommand(data, command) @@ -27,7 +28,7 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf if command.Exec.Id == commandName { - if supportedCommand.Exec.Group.Kind == "" { + if command.Exec.Group.Kind == "" { // Devfile V1 for commands passed from flags // Group type is not updated during conversion command.Exec.Group.Kind = groupType @@ -50,15 +51,21 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf } // if not command specified via flag, default command has the highest priority + // We need to scan all the commands to find default command if command.Exec.Group.Kind == groupType && command.Exec.Group.IsDefault { supportedCommand = command return supportedCommand, nil } + } + + if commandName == "" { + // if default command is not found return the first command found for the matching type. + for _, command := range data.GetCommands() { + if command.Exec.Group.Kind == groupType { + supportedCommand = command + return supportedCommand, nil + } - // return the first command found for the matching type. - if command.Exec.Group.Kind == groupType { - supportedCommand = command - return supportedCommand, nil } } @@ -100,6 +107,10 @@ func validateCommand(data data.DevfileData, command common.DevfileCommand) (err return fmt.Errorf("Exec commands must have a command") } + if command.Exec.Group == nil { + return fmt.Errorf("Exec commands must have group") + } + // must map to a supported component components := GetSupportedComponents(data) diff --git a/pkg/devfile/adapters/common/command_test.go b/pkg/devfile/adapters/common/command_test.go index 262e40f01f4..0a0cbefc1b0 100644 --- a/pkg/devfile/adapters/common/command_test.go +++ b/pkg/devfile/adapters/common/command_test.go @@ -10,214 +10,166 @@ import ( "github.com/openshift/odo/pkg/testingutil" ) +var buildGroup = common.BuildCommandGroupType +var runGroup = common.RunCommandGroupType +var initGroup = common.InitCommandGroupType + func TestGetCommand(t *testing.T) { commands := [...]string{"ls -la", "pwd"} components := [...]string{"alias1", "alias2"} invalidComponent := "garbagealias" workDir := [...]string{"/", "/root"} - validCommandType := common.DevfileCommandTypeExec - invalidCommandType := common.DevfileCommandType("garbage") + emptyString := "" tests := []struct { - name string - requestedCommands []string - commandActions []common.DevfileCommandAction - isCommandRequired []bool - wantErr bool + name string + requestedType []common.DevfileCommandGroupType + execCommands []common.Exec + groupType []common.DevfileCommandGroupType + reqCommandName string + retCommandName string + wantErr bool }{ { - name: "Case 1: Valid devfile", - requestedCommands: []string{"devbuild", "devrun"}, - commandActions: []common.DevfileCommandAction{ - { - Command: &commands[0], - Component: &components[0], - Workdir: &workDir[0], - Type: &validCommandType, - }, - }, - isCommandRequired: []bool{false, false, true}, - wantErr: false, - }, - { - name: "Case 2: Valid devfile with devinit and devbuild", - requestedCommands: []string{"devinit", "devbuild", "devrun"}, - commandActions: []versionsCommon.DevfileCommandAction{ - { - Command: &commands[0], - Component: &components[0], - Workdir: &workDir[0], - Type: &validCommandType, - }, - }, - isCommandRequired: []bool{false, false, true}, - wantErr: false, - }, - { - name: "Case 3: Valid devfile with devinit and devrun", - requestedCommands: []string{"devinit", "devrun"}, - commandActions: []versionsCommon.DevfileCommandAction{ - { - Command: &commands[0], - Component: &components[0], - Workdir: &workDir[0], - Type: &validCommandType, - }, + name: "Case 1: Valid devfile", + execCommands: []versionsCommon.Exec{ + getExecCommand("", buildGroup), + getExecCommand("", runGroup), }, - isCommandRequired: []bool{false, false, true}, - wantErr: false, + requestedType: []common.DevfileCommandGroupType{buildGroup, runGroup}, + wantErr: false, }, { - name: "Case 4: Wrong command requested", - requestedCommands: []string{"garbage1"}, - commandActions: []common.DevfileCommandAction{ - { - Command: &commands[0], - Component: &components[0], - Workdir: &workDir[0], - Type: &validCommandType, - }, + name: "Case 2: Valid devfile with devinit and devbuild", + execCommands: []versionsCommon.Exec{ + getExecCommand("", buildGroup), + getExecCommand("", runGroup), }, - isCommandRequired: []bool{true}, - wantErr: true, + requestedType: []common.DevfileCommandGroupType{initGroup, buildGroup, runGroup}, + wantErr: false, }, { - name: "Case 5: Invalid devfile with wrong devinit command type", - requestedCommands: []string{"devinit"}, - commandActions: []versionsCommon.DevfileCommandAction{ - { - Command: &commands[0], - Component: &components[0], - Workdir: &workDir[0], - Type: &invalidCommandType, - }, + name: "Case 3: Valid devfile with devinit and devrun", + execCommands: []versionsCommon.Exec{ + getExecCommand("", initGroup), + getExecCommand("", runGroup), }, - isCommandRequired: []bool{true}, - wantErr: true, + requestedType: []common.DevfileCommandGroupType{initGroup, runGroup}, + wantErr: false, }, { - name: "Case 6: Invalid devfile with empty devinit component", - requestedCommands: []string{"devinit"}, - commandActions: []versionsCommon.DevfileCommandAction{ + name: "Case 4: Invalid devfile with empty component", + execCommands: []versionsCommon.Exec{ { - Command: &commands[0], - Component: &emptyString, - Workdir: &workDir[0], - Type: &validCommandType, + CommandLine: commands[0], + Component: emptyString, + WorkingDir: workDir[0], + Group: &versionsCommon.Group{Kind: initGroup}, }, }, - isCommandRequired: []bool{false}, - wantErr: true, + requestedType: []common.DevfileCommandGroupType{initGroup}, + wantErr: true, }, { - name: "Case 7: Invalid devfile with empty devinit command", - requestedCommands: []string{"devinit"}, - commandActions: []versionsCommon.DevfileCommandAction{ + name: "Case 5: Invalid devfile with empty devinit command", + execCommands: []versionsCommon.Exec{ { - Command: &emptyString, - Component: &components[0], - Workdir: &workDir[0], - Type: &validCommandType, + CommandLine: emptyString, + Component: components[0], + WorkingDir: workDir[0], + Group: &versionsCommon.Group{Kind: initGroup}, }, }, - isCommandRequired: []bool{false}, - wantErr: true, + requestedType: []common.DevfileCommandGroupType{initGroup}, + wantErr: true, }, { - name: "Case 8: Invalid devfile with wrong devbuild command type", - requestedCommands: []string{"devbuild"}, - commandActions: []common.DevfileCommandAction{ + name: "Case 6: Valid devfile with empty workdir", + execCommands: []common.Exec{ { - Command: &commands[0], - Component: &components[0], - Workdir: &workDir[0], - Type: &invalidCommandType, + CommandLine: commands[0], + Component: components[0], + Group: &versionsCommon.Group{Kind: runGroup}, }, }, - isCommandRequired: []bool{true}, - wantErr: true, + requestedType: []common.DevfileCommandGroupType{runGroup}, + wantErr: false, }, { - name: "Case 9: Invalid devfile with empty devbuild component", - requestedCommands: []string{"devbuild"}, - commandActions: []common.DevfileCommandAction{ + name: "Case 7: Invalid command referencing an absent component", + execCommands: []common.Exec{ { - Command: &commands[0], - Component: &emptyString, - Workdir: &workDir[0], - Type: &validCommandType, + CommandLine: commands[0], + Component: invalidComponent, + Group: &versionsCommon.Group{Kind: runGroup}, }, }, - isCommandRequired: []bool{false}, - wantErr: true, + requestedType: []common.DevfileCommandGroupType{runGroup}, + wantErr: true, }, { - name: "Case 10: Invalid devfile with empty devbuild command", - requestedCommands: []string{"devbuild"}, - commandActions: []common.DevfileCommandAction{ + name: "Case 8: Mismatched command type", + execCommands: []common.Exec{ { - Command: &emptyString, - Component: &components[0], - Workdir: &workDir[0], - Type: &validCommandType, + Id: "build command", + CommandLine: commands[0], + Component: components[0], + Group: &versionsCommon.Group{Kind: runGroup}, }, }, - isCommandRequired: []bool{false}, - wantErr: true, + reqCommandName: "build command", + requestedType: []common.DevfileCommandGroupType{buildGroup}, + wantErr: true, }, { - name: "Case 11: Valid devfile with empty workdir", - requestedCommands: []string{"devrun"}, - commandActions: []common.DevfileCommandAction{ + name: "Case 9: Default command is returned", + execCommands: []common.Exec{ { - Command: &commands[0], - Component: &components[0], - Type: &validCommandType, + Id: "defaultRunCommand", + CommandLine: commands[0], + Component: components[0], + Group: &versionsCommon.Group{Kind: runGroup, IsDefault: true}, }, - }, - isCommandRequired: []bool{true}, - wantErr: false, - }, - { - name: "Case 12: Invalid command referencing an absent component", - requestedCommands: []string{"devrun"}, - commandActions: []common.DevfileCommandAction{ { - Command: &commands[0], - Component: &invalidComponent, - Type: &validCommandType, + Id: "runCommand", + CommandLine: commands[0], + Component: components[0], + Group: &versionsCommon.Group{Kind: runGroup}, }, }, - isCommandRequired: []bool{true}, - wantErr: true, + retCommandName: "defaultRunCommand", + requestedType: []common.DevfileCommandGroupType{runGroup}, + wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + components := []common.DevfileComponent{testingutil.GetFakeComponent(tt.execCommands[0].Component)} + if tt.execCommands[0].Component == invalidComponent { + components = []common.DevfileComponent{testingutil.GetFakeComponent("randomComponent")} + } devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - CommandActions: tt.commandActions, - ComponentType: common.DevfileComponentTypeDockerimage, + ExecCommands: tt.execCommands, + Components: components, }, } - for i, commandName := range tt.requestedCommands { - command, err := getCommand(devObj.Data, commandName, tt.isCommandRequired[i]) + for _, gtype := range tt.requestedType { + cmd, err := getCommand(devObj.Data, tt.reqCommandName, gtype) if !tt.wantErr == (err != nil) { - t.Errorf("TestGetCommand unexpected error for command: %v wantErr: %v err: %v", commandName, tt.wantErr, err) + t.Errorf("TestGetCommand unexpected error for command: %v wantErr: %v err: %v", gtype, tt.wantErr, err) return } else if tt.wantErr { return } - if command.Name != commandName { - t.Errorf("TestGetCommand error: command names do not match expected: %v actual: %v", commandName, command.Name) - } - - if len(command.Actions) != 1 { - t.Errorf("TestGetCommand error: command %v do not have the correct number of actions actual: %v", commandName, len(command.Actions)) + if cmd.Exec != nil { + if cmd.Exec.Id != tt.retCommandName { + t.Errorf("TestGetCommand error: command names do not match expected: %v actual: %v", tt.retCommandName, cmd.Exec.Id) + } } } }) @@ -230,51 +182,49 @@ func TestValidateAction(t *testing.T) { command := "ls -la" component := "alias1" workDir := "/" - validCommandType := common.DevfileCommandTypeExec - invalidCommandType := common.DevfileCommandType("garbage") + emptyString := "" tests := []struct { name string - action common.DevfileCommandAction + exec common.Exec wantErr bool }{ { - name: "Case: Valid Command Action", - action: common.DevfileCommandAction{ - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + name: "Case: Valid Exec Command", + exec: common.Exec{ + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: runGroup}, }, wantErr: false, }, { - name: "Case: Invalid Command Action with empty command", - action: common.DevfileCommandAction{ - Command: &emptyString, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + name: "Case: Invalid Exec Command with empty command", + exec: common.Exec{ + CommandLine: emptyString, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: runGroup}, }, wantErr: true, }, { - name: "Case: Invalid Command Action with missing component", - action: common.DevfileCommandAction{ - Command: &command, - Workdir: &workDir, - Type: &validCommandType, + name: "Case: Invalid Exec Command with missing component", + exec: common.Exec{ + CommandLine: command, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: runGroup}, }, wantErr: true, }, { - name: "Case: Invalid Command Action with wrong type", - action: common.DevfileCommandAction{ - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &invalidCommandType, + name: "Case: Invalid Exec Command with Group nil", + exec: common.Exec{ + CommandLine: command, + Component: component, + WorkingDir: workDir, }, wantErr: true, }, @@ -282,18 +232,13 @@ func TestValidateAction(t *testing.T) { for _, tt := range tests { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - CommandActions: []common.DevfileCommandAction{ - { - Command: &command, - Component: &component, - Type: &validCommandType, - }, - }, - ComponentType: common.DevfileComponentTypeDockerimage, + ExecCommands: []common.Exec{tt.exec}, + Components: []common.DevfileComponent{testingutil.GetFakeComponent(component)}, }, } t.Run(tt.name, func(t *testing.T) { - err := validateCommand(devObj.Data, tt.action) + cmd := common.DevfileCommand{Exec: &tt.exec} + err := validateCommand(devObj.Data, cmd) if !tt.wantErr == (err != nil) { t.Errorf("TestValidateAction unexpected error: %v", err) return @@ -308,39 +253,46 @@ func TestGetInitCommand(t *testing.T) { command := "ls -la" component := "alias1" workDir := "/" - validCommandType := common.ExecCommandType emptyString := "" var emptyCommand common.DevfileCommand tests := []struct { - name string - commandName string - commandActions []common.DevfileCommandAction - wantErr bool + name string + commandName string + execCommands []common.Exec + wantErr bool }{ { name: "Case: Default Init Command", commandName: emptyString, - commandActions: []versionsCommon.DevfileCommandAction{ + execCommands: []versionsCommon.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: initGroup, IsDefault: true}, }, }, wantErr: false, }, { - name: "Case: Custom Init Command", - commandName: "customcommand", - commandActions: []versionsCommon.DevfileCommandAction{ + name: "Case: Init Command passed through odo flag", + commandName: "flagcommand", + execCommands: []versionsCommon.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + Id: "flagcommand", + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: initGroup}, + }, + { + Id: "init command", + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: initGroup}, }, }, wantErr: false, @@ -348,12 +300,12 @@ func TestGetInitCommand(t *testing.T) { { name: "Case: Missing Init Command", commandName: "customcommand123", - commandActions: []versionsCommon.DevfileCommandAction{ + execCommands: []versionsCommon.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: initGroup}, }, }, wantErr: true, @@ -364,8 +316,8 @@ func TestGetInitCommand(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - CommandActions: tt.commandActions, - ComponentType: versionsCommon.DevfileComponentTypeDockerimage, + ExecCommands: tt.execCommands, + Components: []common.DevfileComponent{testingutil.GetFakeComponent(component)}, }, } @@ -387,39 +339,46 @@ func TestGetBuildCommand(t *testing.T) { command := "ls -la" component := "alias1" workDir := "/" - validCommandType := common.DevfileCommandTypeExec emptyString := "" var emptyCommand common.DevfileCommand tests := []struct { - name string - commandName string - commandActions []common.DevfileCommandAction - wantErr bool + name string + commandName string + execCommands []common.Exec + wantErr bool }{ { name: "Case: Default Build Command", commandName: emptyString, - commandActions: []common.DevfileCommandAction{ + execCommands: []common.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: buildGroup, IsDefault: true}, }, }, wantErr: false, }, { - name: "Case: Custom Build Command", - commandName: "customcommand", - commandActions: []common.DevfileCommandAction{ + name: "Case: Build Command passed through the odo flag", + commandName: "flagcommand", + execCommands: []common.Exec{ + { + Id: "flagcommand", + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: buildGroup}, + }, { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + Id: "build command", + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: buildGroup}, }, }, wantErr: false, @@ -427,12 +386,13 @@ func TestGetBuildCommand(t *testing.T) { { name: "Case: Missing Build Command", commandName: "customcommand123", - commandActions: []common.DevfileCommandAction{ + execCommands: []common.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + Id: "build command", + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: buildGroup}, }, }, wantErr: true, @@ -443,8 +403,8 @@ func TestGetBuildCommand(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - CommandActions: tt.commandActions, - ComponentType: common.DevfileComponentTypeDockerimage, + ExecCommands: tt.execCommands, + Components: []common.DevfileComponent{testingutil.GetFakeComponent(component)}, }, } @@ -466,52 +426,59 @@ func TestGetRunCommand(t *testing.T) { command := "ls -la" component := "alias1" workDir := "/" - validCommandType := common.DevfileCommandTypeExec emptyString := "" var emptyCommand common.DevfileCommand tests := []struct { - name string - commandName string - commandActions []common.DevfileCommandAction - wantErr bool + name string + commandName string + execCommands []common.Exec + wantErr bool }{ { name: "Case: Default Run Command", commandName: emptyString, - commandActions: []common.DevfileCommandAction{ + execCommands: []common.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: runGroup, IsDefault: true}, }, }, wantErr: false, }, { - name: "Case: Custom Run Command", - commandName: "customcommand", - commandActions: []common.DevfileCommandAction{ + name: "Case: Run Command passed through odo flag", + commandName: "flagcommand", + execCommands: []common.Exec{ + { + Id: "flagcommand", + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: runGroup}, + }, { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + Id: "run command", + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: runGroup}, }, }, wantErr: false, }, { name: "Case: Missing Run Command", - commandName: "customcommand123", - commandActions: []common.DevfileCommandAction{ + commandName: "", + execCommands: []common.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: initGroup}, }, }, wantErr: true, @@ -522,8 +489,8 @@ func TestGetRunCommand(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - CommandActions: tt.commandActions, - ComponentType: common.DevfileComponentTypeDockerimage, + ExecCommands: tt.execCommands, + Components: []common.DevfileComponent{testingutil.GetFakeComponent(component)}, }, } @@ -544,16 +511,48 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { command := "ls -la" component := "alias1" workDir := "/" - validCommandType := common.DevfileCommandTypeExec emptyString := "" - actions := []common.DevfileCommandAction{ + execCommands := []common.Exec{ + { + Id: "run command", + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: runGroup}, + }, + + { + Id: "build command", + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: buildGroup}, + }, + { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + Id: "init command", + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: initGroup}, }, + { + Id: "customcommand", + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: runGroup}, + }, + } + + wrongCompTypeCmd := common.Exec{ + + Id: "run command", + CommandLine: command, + Component: "", + WorkingDir: workDir, + Group: &versionsCommon.Group{Kind: runGroup}, } tests := []struct { @@ -561,8 +560,8 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { initCommand string buildCommand string runCommand string + execCommands []common.Exec numberOfCommands int - componentType common.DevfileComponentType missingInitCommand bool missingBuildCommand bool wantErr bool @@ -572,8 +571,8 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { initCommand: emptyString, buildCommand: emptyString, runCommand: emptyString, + execCommands: execCommands, numberOfCommands: 3, - componentType: common.DevfileComponentTypeDockerimage, wantErr: false, }, { @@ -581,17 +580,17 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { initCommand: emptyString, buildCommand: emptyString, runCommand: "customcommand", + execCommands: execCommands, numberOfCommands: 3, - componentType: common.DevfileComponentTypeDockerimage, wantErr: false, }, { - name: "Case: No Dockerimage Component", + name: "Case: Empty Component", initCommand: emptyString, buildCommand: "customcommand", runCommand: "customcommand", + execCommands: append(execCommands, wrongCompTypeCmd), numberOfCommands: 0, - componentType: "", wantErr: true, }, { @@ -599,8 +598,8 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { initCommand: emptyString, buildCommand: "customcommand123", runCommand: "customcommand", + execCommands: execCommands, numberOfCommands: 1, - componentType: common.DevfileComponentTypeDockerimage, wantErr: true, }, { @@ -608,48 +607,78 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { initCommand: "customcommand123", buildCommand: emptyString, runCommand: "customcommand", + execCommands: execCommands, numberOfCommands: 1, - componentType: versionsCommon.DevfileComponentTypeDockerimage, wantErr: true, }, { - name: "Case: Missing Init and Build Command, and Provided Run Command", - initCommand: emptyString, - buildCommand: emptyString, - runCommand: "customcommand", - numberOfCommands: 1, - componentType: common.DevfileComponentTypeDockerimage, - missingInitCommand: true, - missingBuildCommand: true, - wantErr: false, + name: "Case: Missing Init and Build Command, and Provided Run Command", + initCommand: emptyString, + buildCommand: emptyString, + runCommand: "customcommand", + execCommands: []common.Exec{ + { + Id: "customcommand", + Group: &common.Group{Kind: runGroup}, + Component: component, + CommandLine: command, + }, + }, + numberOfCommands: 1, + wantErr: false, }, { - name: "Case: Missing Init Command with provided Build and Run Command", - initCommand: emptyString, - buildCommand: "customcommand", - runCommand: "customcommand", + name: "Case: Missing Init Command with provided Build and Run Command", + initCommand: emptyString, + buildCommand: "build command", + runCommand: "run command", + execCommands: []common.Exec{ + { + Id: "build command", + Group: &common.Group{Kind: buildGroup}, + Component: component, + CommandLine: command, + }, + { + Id: "run command", + Group: &common.Group{Kind: runGroup}, + Component: component, + CommandLine: command, + }, + }, numberOfCommands: 2, - componentType: versionsCommon.DevfileComponentTypeDockerimage, missingInitCommand: true, wantErr: false, }, { - name: "Case: Missing Build Command with provided Init and Run Command", - initCommand: "customcommand", - buildCommand: emptyString, - runCommand: "customcommand", - numberOfCommands: 2, - componentType: versionsCommon.DevfileComponentTypeDockerimage, - missingBuildCommand: true, - wantErr: false, + name: "Case: Missing Build Command with provided Init and Run Command", + initCommand: "init command", + buildCommand: emptyString, + runCommand: "run command", + execCommands: []common.Exec{ + { + Id: "init command", + Group: &common.Group{Kind: initGroup}, + Component: component, + CommandLine: command, + }, + { + Id: "run command", + Group: &common.Group{Kind: runGroup}, + Component: component, + CommandLine: command, + }, + }, + numberOfCommands: 2, + wantErr: false, }, { name: "Case: Optional Init Command with provided Build and Run Command", - initCommand: "customcommand", - buildCommand: "customcommand", - runCommand: "customcommand", + initCommand: "init command", + buildCommand: "build command", + runCommand: "run command", + execCommands: execCommands, numberOfCommands: 3, - componentType: versionsCommon.DevfileComponentTypeDockerimage, wantErr: false, }, } @@ -658,10 +687,8 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - CommandActions: actions, - ComponentType: tt.componentType, - MissingInitCommand: tt.missingInitCommand, - MissingBuildCommand: tt.missingBuildCommand, + ExecCommands: tt.execCommands, + Components: []common.DevfileComponent{testingutil.GetFakeComponent(component)}, }, } @@ -679,3 +706,19 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { } } + +func getExecCommand(id string, group common.DevfileCommandGroupType) versionsCommon.Exec { + + commands := [...]string{"ls -la", "pwd"} + components := [...]string{"alias1", "alias2"} + workDir := [...]string{"/", "/root"} + + return versionsCommon.Exec{ + Id: id, + CommandLine: commands[0], + Component: components[0], + WorkingDir: workDir[0], + Group: &common.Group{Kind: group}, + } + +} diff --git a/pkg/devfile/adapters/common/utils.go b/pkg/devfile/adapters/common/utils.go index 56e5d230fb1..4bddc3c05a1 100644 --- a/pkg/devfile/adapters/common/utils.go +++ b/pkg/devfile/adapters/common/utils.go @@ -130,7 +130,7 @@ func GetVolumes(devfileObj devfileParser.DevfileObj) map[string][]DevfileVolume } // IsEnvPresent checks if the env variable is present in an array of env variables -func IsEnvPresent(envVars []*common.Env, envVarName string) bool { +func IsEnvPresent(envVars []common.Env, envVarName string) bool { for _, envVar := range envVars { if envVar.Name == envVarName { return true @@ -141,7 +141,7 @@ func IsEnvPresent(envVars []*common.Env, envVarName string) bool { } // IsPortPresent checks if the port is present in the endpoints array -func IsPortPresent(endpoints []*common.Endpoint, port int) bool { +func IsPortPresent(endpoints []common.Endpoint, port int) bool { for _, endpoint := range endpoints { if endpoint.TargetPort == int32(port) { return true diff --git a/pkg/devfile/adapters/common/utils_test.go b/pkg/devfile/adapters/common/utils_test.go index 3b8bd6ef632..55fbbd27202 100644 --- a/pkg/devfile/adapters/common/utils_test.go +++ b/pkg/devfile/adapters/common/utils_test.go @@ -8,75 +8,56 @@ import ( "github.com/openshift/odo/pkg/devfile/parser/data/common" versionsCommon "github.com/openshift/odo/pkg/devfile/parser/data/common" "github.com/openshift/odo/pkg/testingutil" - "github.com/openshift/odo/pkg/util" ) func TestGetSupportedComponents(t *testing.T) { tests := []struct { name string - componentType versionsCommon.DevfileComponentType + component []versionsCommon.DevfileComponent alias []string expectedMatchesCount int }{ { name: "Case: Invalid devfile", - componentType: "", + component: []versionsCommon.DevfileComponent{}, expectedMatchesCount: 0, }, { - name: "Case: Valid devfile with wrong component type (CheEditor)", - componentType: versionsCommon.DevfileComponentTypeCheEditor, - alias: []string{"alias1", "alias2"}, - expectedMatchesCount: 0, - }, - { - name: "Case: Valid devfile with wrong component type (ChePlugin)", - componentType: versionsCommon.DevfileComponentTypeChePlugin, - alias: []string{"alias1", "alias2"}, + name: "Case: Valid devfile with wrong component type (Openshift)", + component: []versionsCommon.DevfileComponent{{Openshift: &versionsCommon.Openshift{}}}, expectedMatchesCount: 0, }, { name: "Case: Valid devfile with wrong component type (Kubernetes)", - componentType: versionsCommon.DevfileComponentTypeKubernetes, - alias: []string{"alias1", "alias2"}, + component: []versionsCommon.DevfileComponent{{Kubernetes: &versionsCommon.Kubernetes{}}}, expectedMatchesCount: 0, }, + { - name: "Case: Valid devfile with wrong component type (Openshift)", - componentType: versionsCommon.DevfileComponentTypeOpenshift, - alias: []string{"alias1", "alias2"}, - expectedMatchesCount: 0, + name: "Case: Valid devfile with correct component type (Container)", + component: []versionsCommon.DevfileComponent{testingutil.GetFakeComponent("comp1"), testingutil.GetFakeComponent("comp2")}, + expectedMatchesCount: 2, }, + { - name: "Case: Valid devfile with correct component type (Dockerimage)", - componentType: versionsCommon.DevfileComponentTypeDockerimage, - alias: []string{"alias1", "alias2"}, - expectedMatchesCount: 2, + name: "Case: Valid devfile with correct component type (Container) without name", + component: []versionsCommon.DevfileComponent{testingutil.GetFakeComponent("comp1"), testingutil.GetFakeComponent("")}, + expectedMatchesCount: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: tt.componentType, + Components: tt.component, }, } devfileComponents := GetSupportedComponents(devObj.Data) - componentsMatched := 0 - for _, component := range devfileComponents { - if component.Type != versionsCommon.DevfileComponentTypeDockerimage { - t.Errorf("TestGetSupportedComponents error: wrong component type expected %v, actual %v", versionsCommon.DevfileComponentTypeDockerimage, component.Type) - } - if util.In(tt.alias, *component.Alias) { - componentsMatched++ - } - } - - if componentsMatched != tt.expectedMatchesCount { - t.Errorf("TestGetSupportedComponents error: wrong number of components matched: expected %v, actual %v", tt.expectedMatchesCount, componentsMatched) + if len(devfileComponents) != tt.expectedMatchesCount { + t.Errorf("TestGetSupportedComponents error: wrong number of components matched: expected %v, actual %v", tt.expectedMatchesCount, len(devfileComponents)) } }) } @@ -88,10 +69,10 @@ func TestIsEnvPresent(t *testing.T) { envName := "myenv" envValue := "myenvvalue" - envVars := []common.DockerimageEnv{ + envVars := []common.Env{ { - Name: &envName, - Value: &envValue, + Name: envName, + Value: envValue, }, } @@ -127,10 +108,10 @@ func TestIsPortPresent(t *testing.T) { endpointName := "8080/tcp" var endpointPort int32 = 8080 - endpoints := []common.DockerimageEndpoint{ + endpoints := []common.Endpoint{ { - Name: &endpointName, - Port: &endpointPort, + Name: endpointName, + TargetPort: endpointPort, }, } @@ -204,16 +185,14 @@ func TestIsComponentSupported(t *testing.T) { wantIsSupported bool }{ { - name: "Case 1: Supported component", - component: common.DevfileComponent{ - Type: versionsCommon.DevfileComponentTypeDockerimage, - }, + name: "Case 1: Supported component", + component: testingutil.GetFakeComponent("comp1"), wantIsSupported: true, }, { name: "Case 2: Unsupported component", component: common.DevfileComponent{ - Type: versionsCommon.DevfileComponentTypeCheEditor, + Openshift: &versionsCommon.Openshift{}, }, wantIsSupported: false, }, diff --git a/pkg/devfile/adapters/docker/component/utils.go b/pkg/devfile/adapters/docker/component/utils.go index 5e1974edda1..569cf1fba09 100644 --- a/pkg/devfile/adapters/docker/component/utils.go +++ b/pkg/devfile/adapters/docker/component/utils.go @@ -240,7 +240,7 @@ func (a Adapter) startComponent(mounts []mount.Mount, projectVolumeName string, if !common.IsEnvPresent(comp.Container.Env, common.EnvCheProjectsRoot) { envName := common.EnvCheProjectsRoot envValue := lclient.OdoSourceVolumeMount - comp.Container.Env = append(comp.Container.Env, &versionsCommon.Env{ + comp.Container.Env = append(comp.Container.Env, versionsCommon.Env{ Name: envName, Value: envValue, }) @@ -276,7 +276,7 @@ func (a Adapter) generateAndGetContainerConfig(componentName string, comp versio return containerConfig } -func (a Adapter) generateAndGetHostConfig(endpoints []*versionsCommon.Endpoint) (container.HostConfig, map[nat.Port]string, error) { +func (a Adapter) generateAndGetHostConfig(endpoints []versionsCommon.Endpoint) (container.HostConfig, map[nat.Port]string, error) { // Convert the port bindings from env.yaml and generate docker host config portMap, namePortMapping, err := getPortMap(a.Context, endpoints, true) if err != nil { @@ -291,7 +291,7 @@ func (a Adapter) generateAndGetHostConfig(endpoints []*versionsCommon.Endpoint) return hostConfig, namePortMapping, nil } -func getPortMap(context string, endpoints []*versionsCommon.Endpoint, show bool) (nat.PortMap, map[nat.Port]string, error) { +func getPortMap(context string, endpoints []versionsCommon.Endpoint, show bool) (nat.PortMap, map[nat.Port]string, error) { // Convert the exposed and internal port pairs saved in env.yaml file to PortMap // Todo: Use context to get the approraite envinfo after context is supported in experimental mode portmap := nat.PortMap{} diff --git a/pkg/devfile/adapters/docker/utils/utils.go b/pkg/devfile/adapters/docker/utils/utils.go index bde669f7658..ca00e61427b 100644 --- a/pkg/devfile/adapters/docker/utils/utils.go +++ b/pkg/devfile/adapters/docker/utils/utils.go @@ -60,7 +60,7 @@ func GetContainerIDForAlias(containers []types.Container, alias string) string { } // ConvertEnvs converts environment variables from the devfile structure to an array of strings, as expected by Docker -func ConvertEnvs(vars []*common.Env) []string { +func ConvertEnvs(vars []common.Env) []string { dockerVars := []string{} for _, env := range vars { envString := fmt.Sprintf("%s=%s", env.Name, env.Value) @@ -70,7 +70,7 @@ func ConvertEnvs(vars []*common.Env) []string { } // ConvertPorts converts endpoints from the devfile structure to PortSet, which is expected by Docker -func ConvertPorts(endpoints []*common.Endpoint) nat.PortSet { +func ConvertPorts(endpoints []common.Endpoint) nat.PortSet { portSet := nat.PortSet{} for _, endpoint := range endpoints { port := nat.Port(strconv.Itoa(int(endpoint.TargetPort)) + "/tcp") @@ -220,7 +220,7 @@ func UpdateComponentWithSupervisord(comp *common.DevfileComponent, runCommand co if !adaptersCommon.IsEnvPresent(comp.Container.Env, adaptersCommon.EnvOdoCommandRun) { envName := adaptersCommon.EnvOdoCommandRun envValue := runCommand.Exec.CommandLine - comp.Container.Env = append(comp.Container.Env, &common.Env{ + comp.Container.Env = append(comp.Container.Env, common.Env{ Name: envName, Value: envValue, }) @@ -229,7 +229,7 @@ func UpdateComponentWithSupervisord(comp *common.DevfileComponent, runCommand co if !adaptersCommon.IsEnvPresent(comp.Container.Env, adaptersCommon.EnvOdoCommandRunWorkingDir) && runCommand.Exec.WorkingDir != "" { envName := adaptersCommon.EnvOdoCommandRunWorkingDir envValue := runCommand.Exec.WorkingDir - comp.Container.Env = append(comp.Container.Env, &common.Env{ + comp.Container.Env = append(comp.Container.Env, common.Env{ Name: envName, Value: envValue, }) diff --git a/pkg/devfile/adapters/kubernetes/component/adapter_test.go b/pkg/devfile/adapters/kubernetes/component/adapter_test.go index a683a5298d5..f5755c13e65 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter_test.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter_test.go @@ -8,6 +8,7 @@ import ( adaptersCommon "github.com/openshift/odo/pkg/devfile/adapters/common" devfileParser "github.com/openshift/odo/pkg/devfile/parser" + "github.com/openshift/odo/pkg/devfile/parser/data/common" versionsCommon "github.com/openshift/odo/pkg/devfile/parser/data/common" "github.com/openshift/odo/pkg/kclient" "github.com/openshift/odo/pkg/testingutil" @@ -37,7 +38,7 @@ func TestCreateOrUpdateComponent(t *testing.T) { }, { name: "Case: Valid devfile", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + componentType: versionsCommon.ContainerComponentType, running: false, wantErr: false, }, @@ -49,16 +50,21 @@ func TestCreateOrUpdateComponent(t *testing.T) { }, { name: "Case: Valid devfile, already running component", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + componentType: versionsCommon.ContainerComponentType, running: true, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + var comp versionsCommon.DevfileComponent + if tt.componentType != "" { + comp = testingutil.GetFakeComponent("component") + } devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: tt.componentType, + Components: []versionsCommon.DevfileComponent{comp}, + ExecCommands: []versionsCommon.Exec{getExecCommand("run", versionsCommon.RunCommandGroupType)}, }, } @@ -218,14 +224,12 @@ func TestDoesComponentExist(t *testing.T) { }{ { name: "Case 1: Valid component name", - componentType: versionsCommon.DevfileComponentTypeDockerimage, componentName: "test-name", getComponentName: "test-name", want: true, }, { name: "Case 2: Non-existent component name", - componentType: versionsCommon.DevfileComponentTypeDockerimage, componentName: "test-name", getComponentName: "fake-component", want: false, @@ -235,7 +239,8 @@ func TestDoesComponentExist(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: tt.componentType, + Components: []versionsCommon.DevfileComponent{testingutil.GetFakeComponent("component")}, + ExecCommands: []versionsCommon.Exec{getExecCommand("run", versionsCommon.RunCommandGroupType)}, }, } @@ -282,29 +287,26 @@ func TestWaitAndGetComponentPod(t *testing.T) { wantErr bool }{ { - name: "Case 1: Running", - componentType: versionsCommon.DevfileComponentTypeDockerimage, - status: corev1.PodRunning, - wantErr: false, + name: "Case 1: Running", + status: corev1.PodRunning, + wantErr: false, }, { - name: "Case 2: Failed pod", - componentType: versionsCommon.DevfileComponentTypeDockerimage, - status: corev1.PodFailed, - wantErr: true, + name: "Case 2: Failed pod", + status: corev1.PodFailed, + wantErr: true, }, { - name: "Case 3: Unknown pod", - componentType: versionsCommon.DevfileComponentTypeDockerimage, - status: corev1.PodUnknown, - wantErr: true, + name: "Case 3: Unknown pod", + status: corev1.PodUnknown, + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: tt.componentType, + Components: []versionsCommon.DevfileComponent{testingutil.GetFakeComponent("component")}, }, } @@ -382,7 +384,7 @@ func TestAdapterDelete(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: "nodejs", + // ComponentType: "nodejs", }, } @@ -422,3 +424,19 @@ func TestAdapterDelete(t *testing.T) { }) } } + +func getExecCommand(id string, group common.DevfileCommandGroupType) versionsCommon.Exec { + + commands := [...]string{"ls -la", "pwd"} + component := "component" + workDir := [...]string{"/", "/root"} + + return versionsCommon.Exec{ + Id: id, + CommandLine: commands[0], + Component: component, + WorkingDir: workDir[0], + Group: &common.Group{Kind: group}, + } + +} diff --git a/pkg/devfile/adapters/kubernetes/utils/utils.go b/pkg/devfile/adapters/kubernetes/utils/utils.go index f414388e6ca..c766761b18a 100644 --- a/pkg/devfile/adapters/kubernetes/utils/utils.go +++ b/pkg/devfile/adapters/kubernetes/utils/utils.go @@ -22,7 +22,7 @@ func ComponentExists(client kclient.Client, name string) bool { } // ConvertEnvs converts environment variables from the devfile structure to kubernetes structure -func ConvertEnvs(vars []*common.Env) []corev1.EnvVar { +func ConvertEnvs(vars []common.Env) []corev1.EnvVar { kVars := []corev1.EnvVar{} for _, env := range vars { kVars = append(kVars, corev1.EnvVar{ @@ -34,7 +34,7 @@ func ConvertEnvs(vars []*common.Env) []corev1.EnvVar { } // ConvertPorts converts endpoint variables from the devfile structure to kubernetes ContainerPort -func ConvertPorts(endpoints []*common.Endpoint) ([]corev1.ContainerPort, error) { +func ConvertPorts(endpoints []common.Endpoint) ([]corev1.ContainerPort, error) { containerPorts := []corev1.ContainerPort{} for _, endpoint := range endpoints { name := strings.TrimSpace(util.GetDNS1123Name(strings.ToLower(endpoint.Name))) diff --git a/pkg/devfile/parser/data/1.0.0/components.go b/pkg/devfile/parser/data/1.0.0/components.go index 567c7fa7609..03b68038c6e 100644 --- a/pkg/devfile/parser/data/1.0.0/components.go +++ b/pkg/devfile/parser/data/1.0.0/components.go @@ -32,8 +32,10 @@ func (d *Devfile100) GetAliasedComponents() []common.DevfileComponent { var aliasedComponents = []common.DevfileComponent{} for _, comp := range comps { - if comp.Container.Name != "" { - aliasedComponents = append(aliasedComponents, comp) + if comp.Container != nil { + if comp.Container.Name != "" { + aliasedComponents = append(aliasedComponents, comp) + } } } return aliasedComponents @@ -105,17 +107,17 @@ func convertV1CommandToCommon(c Command) (d common.DevfileCommand) { func convertV1ComponentToCommon(c Component) (component common.DevfileComponent) { - var endpoints []*common.Endpoint + var endpoints []common.Endpoint for _, v := range c.ComponentDockerimage.Endpoints { endpoints = append(endpoints, convertV1EndpointsToCommon(v)) } - var envs []*common.Env + var envs []common.Env for _, v := range c.ComponentDockerimage.Env { envs = append(envs, convertV1EnvToCommon(v)) } - var volumes []*common.VolumeMount + var volumes []common.VolumeMount for _, v := range c.ComponentDockerimage.Volumes { volumes = append(volumes, convertV1VolumeToCommon(v)) } @@ -136,8 +138,8 @@ func convertV1ComponentToCommon(c Component) (component common.DevfileComponent) return component } -func convertV1EndpointsToCommon(e DockerimageEndpoint) *common.Endpoint { - return &common.Endpoint{ +func convertV1EndpointsToCommon(e DockerimageEndpoint) common.Endpoint { + return common.Endpoint{ // Attributes: // Configuration: Name: e.Name, @@ -145,15 +147,15 @@ func convertV1EndpointsToCommon(e DockerimageEndpoint) *common.Endpoint { } } -func convertV1EnvToCommon(e DockerimageEnv) *common.Env { - return &common.Env{ +func convertV1EnvToCommon(e DockerimageEnv) common.Env { + return common.Env{ Name: e.Name, Value: e.Value, } } -func convertV1VolumeToCommon(v DockerimageVolume) *common.VolumeMount { - return &common.VolumeMount{ +func convertV1VolumeToCommon(v DockerimageVolume) common.VolumeMount { + return common.VolumeMount{ Name: v.Name, Path: v.ContainerPath, } diff --git a/pkg/devfile/parser/data/common/types.go b/pkg/devfile/parser/data/common/types.go index bfaa132996d..efa7af7d559 100644 --- a/pkg/devfile/parser/data/common/types.go +++ b/pkg/devfile/parser/data/common/types.go @@ -190,10 +190,10 @@ type Configuration struct { // Container Container component type Container struct { - Endpoints []*Endpoint `json:"endpoints,omitempty"` + Endpoints []Endpoint `json:"endpoints,omitempty"` // Environment variables used in this container - Env []*Env `json:"env,omitempty"` + Env []Env `json:"env,omitempty"` Image string `json:"image"` MemoryLimit string `json:"memoryLimit,omitempty"` MountSources bool `json:"mountSources,omitempty"` @@ -203,7 +203,7 @@ type Container struct { SourceMapping string `json:"sourceMapping,omitempty"` // List of volumes mounts that should be mounted is this container. - VolumeMounts []*VolumeMount `json:"volumeMounts,omitempty"` + VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` } // Custom Custom component @@ -260,7 +260,7 @@ type Exec struct { Component string `json:"component,omitempty"` // Optional list of environment variables that have to be set before running the command - Env []*Env `json:"env,omitempty"` + Env []Env `json:"env,omitempty"` // Defines the group this command is part of Group *Group `json:"group,omitempty"` diff --git a/pkg/devfile/parser/writer_test.go b/pkg/devfile/parser/writer_test.go index 5368660a3ef..31165ae415d 100644 --- a/pkg/devfile/parser/writer_test.go +++ b/pkg/devfile/parser/writer_test.go @@ -5,7 +5,6 @@ import ( devfileCtx "github.com/openshift/odo/pkg/devfile/parser/context" v100 "github.com/openshift/odo/pkg/devfile/parser/data/1.0.0" - "github.com/openshift/odo/pkg/devfile/parser/data/common" "github.com/openshift/odo/pkg/testingutil/filesystem" ) @@ -23,9 +22,9 @@ func TestWriteJsonDevfile(t *testing.T) { devfileObj := DevfileObj{ Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), Data: &v100.Devfile100{ - ApiVersion: common.ApiVersion(apiVersion), - Metadata: common.DevfileMetadata{ - Name: &testName, + ApiVersion: v100.ApiVersion(apiVersion), + Metadata: v100.Metadata{ + Name: testName, }, }, } @@ -51,9 +50,9 @@ func TestWriteJsonDevfile(t *testing.T) { devfileObj := DevfileObj{ Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), Data: &v100.Devfile100{ - ApiVersion: common.ApiVersion(apiVersion), - Metadata: common.DevfileMetadata{ - Name: &testName, + ApiVersion: v100.ApiVersion(apiVersion), + Metadata: v100.Metadata{ + Name: testName, }, }, } diff --git a/pkg/devfile/validate/components_test.go b/pkg/devfile/validate/components_test.go index 9e48ea58042..7da9b9d79bd 100644 --- a/pkg/devfile/validate/components_test.go +++ b/pkg/devfile/validate/components_test.go @@ -27,7 +27,7 @@ func TestValidateComponents(t *testing.T) { components := []common.DevfileComponent{ { - Type: common.DevfileComponentTypeDockerimage, + Type: common.ContainerComponentType, }, } @@ -42,15 +42,15 @@ func TestValidateComponents(t *testing.T) { components := []common.DevfileComponent{ { - Type: common.DevfileComponentTypeCheEditor, + Type: common.PluginComponentType, }, { - Type: common.DevfileComponentTypeChePlugin, + Type: common.KubernetesComponentType, }, } got := ValidateComponents(components) - want := fmt.Errorf(ErrorNoDockerImageComponent) + want := fmt.Errorf(ErrorNoContainerComponent) if !reflect.DeepEqual(got, want) { t.Errorf("Incorrect error; want: '%v', got: '%v'", want, got) diff --git a/pkg/testingutil/devfile.go b/pkg/testingutil/devfile.go index 303d8b4c3a3..e874e883fdc 100644 --- a/pkg/testingutil/devfile.go +++ b/pkg/testingutil/devfile.go @@ -1,15 +1,14 @@ package testingutil import ( + "github.com/openshift/odo/pkg/devfile/parser/data/common" versionsCommon "github.com/openshift/odo/pkg/devfile/parser/data/common" ) // TestDevfileData is a convenience data type used to mock up a devfile configuration type TestDevfileData struct { - ComponentType versionsCommon.DevfileComponentType - CommandActions []versionsCommon.DevfileCommandAction - MissingInitCommand bool - MissingBuildCommand bool + Components []versionsCommon.DevfileComponent + ExecCommands []versionsCommon.Exec } // GetComponents is a mock function to get the components from a devfile @@ -17,54 +16,34 @@ func (d TestDevfileData) GetComponents() []versionsCommon.DevfileComponent { return d.GetAliasedComponents() } +// GetEvents is a mock function to get events from devfile +func (d TestDevfileData) GetEvents() versionsCommon.DevfileEvents { + return d.GetEvents() +} + +// GetMetadata is a mock function to get metadata from devfile +func (d TestDevfileData) GetMetadata() versionsCommon.DevfileMetadata { + return d.GetMetadata() +} + +// GetParent is a mock function to get parent from devfile +func (d TestDevfileData) GetParent() versionsCommon.DevfileParent { + return d.GetParent() +} + // GetAliasedComponents is a mock function to get the components that have an alias from a devfile func (d TestDevfileData) GetAliasedComponents() []versionsCommon.DevfileComponent { - alias := [...]string{"alias1", "alias2"} - image := [...]string{"docker.io/maven:latest", "docker.io/hello-world:latest"} - memoryLimit := "128Mi" - volumeName := [...]string{"myvolume1", "myvolume2"} - volumePath := [...]string{"/my/volume/mount/path1", "/my/volume/mount/path2"} - return []versionsCommon.DevfileComponent{ - { - Alias: &alias[0], - DevfileComponentDockerimage: versionsCommon.DevfileComponentDockerimage{ - Image: &image[0], - Command: []string{}, - Args: []string{}, - Env: []versionsCommon.DockerimageEnv{}, - MemoryLimit: &memoryLimit, - Volumes: []versionsCommon.DockerimageVolume{ - { - Name: &volumeName[0], - ContainerPath: &volumePath[0], - }, - }, - }, - Type: d.ComponentType, - MountSources: true, - }, - { - Alias: &alias[1], - DevfileComponentDockerimage: versionsCommon.DevfileComponentDockerimage{ - Image: &image[1], - Command: []string{}, - Args: []string{}, - Env: []versionsCommon.DockerimageEnv{}, - MemoryLimit: &memoryLimit, - Volumes: []versionsCommon.DockerimageVolume{ - { - Name: &volumeName[0], - ContainerPath: &volumePath[0], - }, - { - Name: &volumeName[1], - ContainerPath: &volumePath[1], - }, - }, - }, - Type: d.ComponentType, - }, + var aliasedComponents = []common.DevfileComponent{} + + for _, comp := range d.Components { + if comp.Container != nil { + if comp.Container.Name != "" { + aliasedComponents = append(aliasedComponents, comp) + } + } } + return aliasedComponents + } // GetProjects is a mock function to get the components that have an alias from a devfile @@ -72,57 +51,62 @@ func (d TestDevfileData) GetProjects() []versionsCommon.DevfileProject { projectName := [...]string{"test-project", "anotherproject"} clonePath := [...]string{"/test-project", "/anotherproject"} sourceLocation := [...]string{"https://github.com/someproject/test-project.git", "https://github.com/another/project.git"} - return []versionsCommon.DevfileProject{ - { - ClonePath: &clonePath[0], - Name: projectName[0], - Source: versionsCommon.DevfileProjectSource{ - Type: versionsCommon.DevfileProjectTypeGit, - Location: sourceLocation[0], - }, + + project1 := versionsCommon.DevfileProject{ + ClonePath: clonePath[0], + Name: projectName[0], + Git: &versionsCommon.Git{ + Location: sourceLocation[0], }, - { - ClonePath: &clonePath[1], - Name: projectName[1], - Source: versionsCommon.DevfileProjectSource{ - Type: versionsCommon.DevfileProjectTypeGit, - Location: sourceLocation[1], - }, + } + + project2 := versionsCommon.DevfileProject{ + ClonePath: clonePath[1], + Name: projectName[1], + Git: &versionsCommon.Git{ + Location: sourceLocation[1], }, } + return []versionsCommon.DevfileProject{project1, project2} + } // GetCommands is a mock function to get the commands from a devfile func (d TestDevfileData) GetCommands() []versionsCommon.DevfileCommand { - commandName := [...]string{"devinit", "devbuild", "devrun", "customcommand"} - commands := []versionsCommon.DevfileCommand{ - { - Name: commandName[2], - Actions: d.CommandActions, - }, - { - Name: commandName[3], - Actions: d.CommandActions, - }, - } - if !d.MissingInitCommand { - commands = append(commands, versionsCommon.DevfileCommand{ - Name: commandName[0], - Actions: d.CommandActions, - }) - } - if !d.MissingBuildCommand { - commands = append(commands, versionsCommon.DevfileCommand{ - Name: commandName[1], - Actions: d.CommandActions, - }) + var commands []versionsCommon.DevfileCommand + + for i := range d.ExecCommands { + commands = append(commands, versionsCommon.DevfileCommand{Exec: &d.ExecCommands[i]}) } return commands + } // Validate is a mock validation that always validates without error func (d TestDevfileData) Validate() error { return nil } + +// GetFakeComponent returns fake component for testing +func GetFakeComponent(name string) versionsCommon.DevfileComponent { + image := "docker.io/maven:latest" + memoryLimit := "128Mi" + volumeName := "myvolume1" + volumePath := "/my/volume/mount/path1" + + return versionsCommon.DevfileComponent{ + Container: &versionsCommon.Container{ + Name: name, + Image: image, + Env: []versionsCommon.Env{}, + MemoryLimit: memoryLimit, + VolumeMounts: []versionsCommon.VolumeMount{{ + Name: volumeName, + Path: volumePath, + }}, + MountSources: true, + }} + +} diff --git a/vendor/k8s.io/api/go.sum b/vendor/k8s.io/api/go.sum index e9de18764bd..3a9fde2aa96 100644 --- a/vendor/k8s.io/api/go.sum +++ b/vendor/k8s.io/api/go.sum @@ -97,6 +97,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +k8s.io/apimachinery v0.17.1 h1:zUjS3szTxoUjTDYNvdFkYt2uMEXLcthcbp+7uZvWhYM= k8s.io/apimachinery v0.17.1/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= From 01d80e14c26d614206665b895394def03f8268da Mon Sep 17 00:00:00 2001 From: Jonathan West Date: Tue, 26 May 2020 02:33:43 -0400 Subject: [PATCH 14/23] Design proposal: Event notification support for build and application status for IDE integration for devfile scenarios (#2550) (#3177) * Add event notification proposal [skip ci] * Update event-notification.md * Update event-notification.md * Update event-notification.md * Update event-notification.md * Update event-notification.md * Update event-notification.md * Update event-notification.md * Update event-notification.md --- docs/proposals/event-notification.md | 211 +++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 docs/proposals/event-notification.md diff --git a/docs/proposals/event-notification.md b/docs/proposals/event-notification.md new file mode 100644 index 00000000000..28f428a54aa --- /dev/null +++ b/docs/proposals/event-notification.md @@ -0,0 +1,211 @@ +# ODO build and application status notification + +## Summary + +With this proposal and [it's related issue](https://github.com/openshift/odo/issues/2550) we examine how to consume build/run event output from odo in order to allow external tools (such as IDEs) to determine the application/build status of an odo-managed application. + +In short, the proposal is: +- New flag for push command, `odo push -o json`: Outputs JSON events that correspond to what devfile actions/commands are being executed by push. +- New odo command `odo component status -o json`: Calls `supervisord ctl status` to check the running container processes, checks the container/pod status every X seconds (and/or a watch, for Kubernetes case), and sends an HTTP/S request to the application URL. The result of those is output as JSON to be used by external tools to determine if the application is running. +- A standardized markup format for communicating detailed build status, something like: `#devfile-status# {"buildStatus":"Compiling application"}`, to be optionally included in devfile scripts that wish to provide detailed build status to consuming tools. + +## Terminology + +With this issue, we are looking at adding odo support for allowing external tools to gather build status and application status. We further divide both statuses into detailed and non-detailed, with detailed principally being related to statuses that can be determine by looking at container logs. + +**Build status**: A simple build status indicating whether the build is running (y/n), and whether the last build succeeded or failed. This can be determined based on whether odo is running a dev file action/command, and the process error code (0 = success, non-0 = failed) of that action/command. + +**Detailed build status**: An indication of which step in the build process the build is in. For example: are we 'Compiling application', or are we 'Running unit tests'? + +**App status**: Determine if an application is running, using container status (for both local and Kubernetes, various status: containercreating, started, restarting, etc), `supervisord ctl status`, or whether an HTTP/S request to the root application URL returns an HTTP response with any status code. + +**Detailed application status**: +- While app status works well for standalone application frameworks (Go, Node Express, Spring Boot), it works less well for full server runtimes such as Java EE application servers like OpenLiberty/WildFly that may begin responding to Web requests before the user's deployed WAR/EAR application has finished startup. +- Since these application servers are built to serve multiple concurrently-deployed applications, it is more difficult to determine the status of any specific application running on them. The lifecycle of the application server differs from the lifecycle of the application running inside the application server. +- Fortunately, in these cases the IDE/consuming tool can use the console logs (from `odo log`) from the runtime container to determine a more detailed application status. +- For example, OpenLiberty (as an example of an application-server-style container) prints a specific code when an application is starting `CWWKZ0018I: Starting application {0}.`, and another when it has started. `CWWKZ0001I: Application {0} started in {1} seconds.` +- Odo itself should NOT know anything about these specific application codes; knowing how these translate into a detailed application status would be the responsibility of the IDE/consuming tool. Odo's role here is only to provide the console output log. +- In the future, we could add these codes into the devfile to give Odo itself some agency over determining the detailed application status, but for this proposal responsibility is left with the consuming tool. + +**Devfile writer**: A devfile writer may be a runtime developer (for example, a Red-Hatter working on WildFly or an IBMer working on OpenLibery) creating a devfile for their organization's runtime (for example 'OpenLiberty w/ Maven' dev file), or an application developer creating/customizing a dev file for use with their own application. In either case, the devfile writer must be familiar with the semantics of both odo and the devfile. + +## JSON-based odo command behaviours to detect app and build status + +New odo commands and flags: +- `odo push -o json` +- `odo component status -o json` + +With these two additions, an IDE or similar external tool can detect build running/succeeded/failed, application starting/started/stopping/stopped, and (in many cases) get a 'detailed app status' and/or 'detailed build status'. + +### Build status notification via `odo push -o json` + +`odo push -o json` is like standard `odo push`, but instead it outputs JSON events (including action console output) instead of text. This allows the internal state of the odo push process to be more easily consumed by external tools. + +Several different types of JSON-formatted events would be output, which correspond to odo container command/action executions: +- Dev file command execution begun *(command name, start timestamp)* +- Dev file command execution completed *(error code, end timestamp)* +- Dev file action execution begun *(action name, parent command name, start timestamp)* +- Dev file action execution completed *(action name, error code, end timestamp)* +- Log/console stdout from the actions, one line at a time *(for example, `mvn build` output).* (timestamp) + +(Exact details for which fields are included with events are TBD) + +In addition, `odo push -o json` should return a non-zero error code if one of the actions returned a non-zero error code, otherwise zero is returned. + +### `odo push -o json` example output + +This is what an `odo push -o json` command invocation would look like: +``` +odo push -o json +{ "devFileCommandExecutionBegun": { "commandName" : "build", "timestamp" : "(UTC unix epoch seconds.microseconds)" } } +{ "devFileActionExecutionBegun" : { "commandName" : "build", "actionName" : "run-build-script", "timestamp" : "(...)" } } +{ "logText" : { "text:" "one line of text received\\n another line of text received", "timestamp" : "(...)" } } +{ "devFileActionExecutionComplete" : { "errorCode" : 1, ( same as above )} } +{ "logText" : { "text": (... ), "timestamp" : "(...)" } } # Log text is interleaved with events +{ "devFileCommandExecutionComplete": { "success" : false, (same as above) } } + +(Exact details on event name, and JSON format are TBD; feedback welcome!) +``` + +These events allow an external odo-consuming tool to determine the build status of an application (build succeeded, build failed, build not running). + +Note that unlike other machine-readable outputs used in odo, each individual line is a fully complete and parseable JSON document, allowing events to be streamed and processed by the consuming tool one-at-a-time, rather than waiting for all the events to be received before being parseable (which would be required if the entire console output was one single JSON document, as is the case for other odo machine-readable outputs.) + +### Detailed build status via JSON+custom markup + +For detailed build status, it is proposed that devfile writers may *optionally* include custom markup in their devfile actions which indicate a detailed build status: +- If a dev file writer wanted to communicate that the current command/action were compiling the application, they would insert a specific markup string (`#devfile-status#`) at the beginning of a console-outputted line, and then between those two fields would be a JSON object with a single field `buildStatus`: + - For example: `#devfile-status# {"buildStatus":"Compiling application"}` would then communicate that the detailed build status should be set to `Compiling application`. +- Since this line would be output as container stdout, it would be included as a `logText` JSON event, and the consuming tool can look for this markup string and parse the simple JSON to extract the detailed build status. +- Feedback welcome around exact markup text format. + +The build step (running as a bash script, for example, invoked via an action) of a devfile might then look like this: +``` +#!/bin/bash +(...) +echo "#devfile-status# {'buildStatus':'Compiling application'} +mvn compile +echo "#devfile-status# {'buildStatus':'Running unit tests'} +mvn test +``` + +This 'detailed build status' markup text is *entirely optional*: if this markup is not present, the odo tool can still determine build succeeded/failed and build running/not-running using the other `odo push -o json` JSON events. + +### App status notification via `odo component status -o json` and `odo log --follow` + +In general, within the execution context that odo operates, there are a few ways for us to determine the application status: +1) Will the application respond to an HTTP/S request sent to its exposed URL? +2) What state is the container in? (running/container creating/restarting/etc -- different statuses between local and Kube but same general idea) +3) Are the container processes running that are managed by supervisord? We check this by calling `supervisord ctl status`. +4) In the application log, specific hardcoded text strings can be searched for (for example, OpenLiberty outputs defined status codes to its log to indicate that an app started.) But, note that we definitely don't want to hardcode specific text strings into ODO: instead, this proposal leaves it up to the IDE to process the output from the `odo log` command. Since the `odo log` command output would contain the application text, IDEs can provide their own mechanism to determine status for supported devfiles (and in the future we may wish to add new devfile elements for these strings, to allow odo to do this as well). + +Ideally, we would like for odo to provide consuming tools with all 4 sets of data. Thus, as proposed: +- 1, 2 and 3 are handled by a new `odo component status -o json` command, described here. +- 4 is handled by the existing unmodified `odo log --follow` command. + +The new proposed `odo component status -o json` command will: +- Be a *long-running* command that will continue outputing status until it is aborted by the parent process. +- Every X seconds, send an HTTP/S request to the URLs/routes of the application as they existed when the command was first executed. Output the result as a JSON string. +- Every X seconds (or using a Kubernetes watch, where appropriate), check the container status for the application, based on the application data that was present when the command was first issued. Output the result as a JSON string. +- Every X seconds call `supervisord ctl status` within the container and report the status of supervisord's managed processes. + +**Note**: This command will NOT, by design, respond to route/application changes that occur during or after it is first invoked. It is up to consuming tools to ensure that the `odo component status` command is stopped/restarted as needed. + - For example, if the user tells the IDE to delete their application with the IDE UI, the IDE will call `odo delete (component)`; at the same time, the IDE should also abort any existing `odo component status` commands that are running (as these are no longer guaranteed to return a valid status now that the application itself no longer exists). `odo component status` will not automatically abort when the application is deleted (because it has no reliable way to detect this in all cases). + - Another example: if the IDE adds a new URL via `odo url create [...]`, any existing `odo component status` commands that are running should be aborted, as these commands would still only be checking the URLs that existed when the command was first invoked (eg there is intentionally no cross-process notification mechanism for created/updated/deleted URLs implemented as part of this command.) + - See discussion of this design descision in 'Other solutions considered' below + +This is an example an `odo component status -o json` command invocation look like: +``` +odo component status -o json + +{ "componentURLStatus" : { "url" : "https://(...)", "response" : "true", "responseCode" : 200, "timestamp" : (UTC unix epoch seconds.microseconds) } } +{ "componentURLStatus" : { "url" : "https://(...)", "response" : "false", error: "host unreachable", "timestamp" : (...) } } +{ "containerStatus" : { "status" : "containercreating", "timestamp" : (...)} } +{ "containerStatus" : { "status" : "running", "timestamp" : (...)} } +{ "supervisordCtlStatus" : { "name": "devrun", "status" : "STARTED", "timestamp" : (...)} } +(...) + +(Exact details on event name, and JSON format are TBD; feedback welcome!) +``` + +To keep from overwhelming the output, only state changes would be printed (after an initial state output), rather than every specific event. + +## Consumption of odo via external tools, such as IDEs + +Based on our existing knowledge from previously building similar application/build-status tracking systems in Eclipse Codewind, we believe the above described commands should allow any external tool to provide a detailed status for odo-managed applications. + +The proposed changes ensure that the the high-level logic around tracking application changes across time can be managed by external tools (such as IDEs) as desired, without the need to leak/"pollute" odo with any of these details. These changes give consuming tools all the data they need ensure fast, reliable, up-to-date and (where possible) detailed build/application status. + + +### What happens if the network connection is lost while executing these commands? + +One potential challenge is how to handle network connection instability when the push/log/status commands are actively running. Both odo, and any external consuming tools, should be able to ensure that the odo-managed application can be returned to a stable state once the connection is re-established. + +We can look at how each command should handle a temporary network disconnection: +- If network connection is dropped during *push*: consuming tool can restart the push command from scratch. Well-written dev files should be nuking any existing build processes (for example, when running a 'build' action, that build action should look for any old maven processes and kill them, if there are any that are already running; or said another way, it is up to the build action of a devfile to ensure that container state is consistent before starting a new build) +- If connection is dropped during *logs*: start a new tail, and then do a full 'get logs' to make sure we didn't miss anything; match up the two (the full log and the tail) as best as possible, to prevent duplicates. (The Kubernetes API may already have a better way of handling this; this is the "naive" algorithm) +- If connection is dropped during *status*: no special behaviour is needed here. + +## Other solutions considered + +Fundamentally, this proposal needs to find a solution to this scenario: + +1) IDE creates a URL (calls `odo url create`) and pushes the user's code (calls `odo push`) +2) To get the status of that app, the IDE runs `odo component status -o json` to start the long-running odo process. The status command then helpfully reports the pod/url/supervisord container status, which allows the IDE to determine when the app process is up. +3) *[some time passes]* +4) IDE creates a new URL (or performs some other action that invalidates the existing `odo status` state, such as `odo component delete`) by calling `odo url create`. +5) The long-running `odo status` process is still running, but somehow needs to know about the new URL from step 4 (or other events). + +Thus, in some way, that existing long-running `odo status` process needs to be informed of the new event (a new url event, a component deletion event, etc). Since these events are generated across independent OS processes, this requires some form of [IPC](https://en.wikipedia.org/wiki/Inter-process_communication). + +### Some options in how to communicate these data across independent odo processes (in ascending order of complexity) + +#### 1) Get the IDE/consuming tool to manage the lifeycle of `odo component status` + +This is the solution proposed in this proposal, and is included for contrast. + +Since the IDE has a lifecycle that is greater than each of the individual calls to `odo`, and the IDE is directly and solely responsible for calling odo (when the user is interacting with the IDE), it is a good fit to ensure the state of `odo component status` is up-to-date and consistent. + +But this option is by no means a perfect solution: +- This does introduce complexity on the IDE side, as the IDE needs to keep track of which `odo` processes are running for each component, and it needs to know when/how to respond to actions (delete/url create/etc). But since the IDE is a monolithic process, this is at least straightforward (I mocked up the algorithm that the IDE will use in each case, which I can share if useful.) +- This introduces complexity for EVERY new IDE/consuming tool that uses this mechanism; rather than solving it once in ODO, it needs to be solved X times for X IDEs. +- Requires multiple concurrent long-running odo processes per odo-managed component + +#### 2) `odo component status` could monitor files under the `.odo` directory in order to detect changes; for example, if a new URL is added to `.odo/env/env.yaml`, `odo component status` would detect that and update the URLs it is checking + +This sounds simple, but is surprisingly difficult: +- No way to detect a delete operation just by watching `.odo` directory: at present, `odo delete` does not delete/change any of the files under `.odo` +- Partial file writes/atomicity/file locking: How to ensure that when `odo component status` reads a file that it has been fully written by the producing process? One way is to use file locks, but that means using/testing each supported platform's file locking mechanisms. Then need to implement a cross-process polling mechanism. +- Or, need to implement a cross-platform [filewatching mechanism](https://github.com/fsnotify/fsnotify): We need a way to watch the `.odo` directory and respond to I/O events to the files, either by modification. +- Windows: Unlike other supported platforms, Windows has a number of quirky file-system behaviours that need to be individually handled. The most relevant one here is that Windows will not let you delete/modify a file in one process if another process is holding it open (we have been bitten by this a number of times in Codewind) +- Need to support all filesystems: some filesystems have different file write/locking/atomicity guarantees for various operations. + + +#### 3) Convert odo into a multi-process client-server architecture + +Fundamentally this problem is about how to share state between odo processes; if odo instead used a client-server architure, odo state could be centrally/consistently managed via a single server process, and communicated piecemeal to odo clients. + +As one example of this, we could create a new odo process/command (`odo status --daemon`?) that would listen on some IPC mechanism (TCP/IP sockets/named pipes/etc) for events from individual odo commands: +1) IDE runs `odo status --daemon --port=32272` as a long-running process; the daemon listens on localhost:32272 for events. The daemon will output component/build status to stdout, which the IDE can provide back to the user. +2) IDE calls `odo url create` to create a new URL, but includes the daemon information in the request: `odo url create --port=32272 (...)` +3) The `odo url create` process updates the `env.yaml` to include the URL, then connects to the daemon on `localhost:32272` and informs the daemon of the new url. +4) The daemon receives the new URL event, and reconciles it with its existing state for the application, and begins watching the new URL. +(This would be need to be implemented for every odo change event) + + +Drawbacks: +- Odo's code currently assumes that commands are short-lived, mostly single-threaded, and compartmentalized; switching to a server would fundamentally alter this presumption of existing code +- Much more complex to implement versus other options: requires changing the architecture of the odo tool into a multithreaded client-server model, meaning many more moving parts, and [the perils of distributed computing](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing). +- Most be cross-platform; IPC mechanisms/behaviour are VERY platform-specific, so we probably need to use TCP/IP sockets. +- But, if using HTTP/S over TCP/IP socket, we need to secure endpoints; just listening on localhost [is not necessarily enough to ensure local-only access](https://bugs.chromium.org/p/project-zero/issues/detail?id=1524). +- Plus some corporate developer environments may use strict firewall rules that prevent server sockets, even on localhost ports. + +Variants on this idea: 1) a new odo daemon/LSP-style server process that was responsible for running ALL odo commands; calls to the `odo` CLI would just initiate a request to the server, and the server would be responsible for performing the action and monitoring the status + +### Proposed option vs options 2/3 + +Hopefully the inherent complexity of options 2-3 is fully conveyed above, but if you all have another fourth option, let me know. + +Ultimately, this proposal (option 1) cleanly solves the problem, puts the complexity in the right place (the IDE), is straight-forward to implement, is not time consuming to implement, and does not fundamentally alter the odo architecture. + +And this option definitely does not in any way tie our hands in implementing a more complex solution in the future if/when we our requirements demand it. From 74d0ebc4854952c397cd217d2d77bb92009b7cb3 Mon Sep 17 00:00:00 2001 From: Aditi Sharma Date: Tue, 26 May 2020 14:56:16 +0530 Subject: [PATCH 15/23] Fix Integration tests for devfile v2 (#3236) * Fix Integration tests Correct volume structure Fix supervisord binary insertion for docker Insert command and args in build container fr docker * Fix Integration tests Revert commands, args removal Add commands, args in v2 common structs Fix duplicate volume error --- pkg/devfile/adapters/common/command.go | 14 ++++++++++++++ pkg/devfile/adapters/common/types.go | 6 +++--- pkg/devfile/adapters/common/utils.go | 8 ++++---- pkg/devfile/adapters/docker/component/utils.go | 12 ++++++------ pkg/devfile/adapters/docker/storage/utils.go | 16 ++++++++-------- pkg/devfile/adapters/docker/utils/utils.go | 11 ++++++----- .../adapters/kubernetes/component/adapter.go | 14 +++++++------- pkg/devfile/adapters/kubernetes/storage/utils.go | 4 ++-- pkg/devfile/adapters/kubernetes/utils/utils.go | 2 +- pkg/devfile/parser/data/1.0.0/components.go | 14 +++++++++----- pkg/devfile/parser/data/common/types.go | 3 +++ pkg/kclient/generators.go | 4 +++- pkg/kclient/volumes.go | 4 ++-- tests/integration/devfile/utils/utils.go | 4 ++-- 14 files changed, 70 insertions(+), 46 deletions(-) diff --git a/pkg/devfile/adapters/common/command.go b/pkg/devfile/adapters/common/command.go index 30515ebda0c..a6020be98fa 100644 --- a/pkg/devfile/adapters/common/command.go +++ b/pkg/devfile/adapters/common/command.go @@ -14,6 +14,8 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf for _, command := range data.GetCommands() { + command = updateGroupforCustomCommand(commandName, groupType, command) + // validate command err = validateCommand(data, command) @@ -204,3 +206,15 @@ func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, de return commandMap, nil } + +// Need to update group on custom commands specified by odo flags +func updateGroupforCustomCommand(commandName string, groupType common.DevfileCommandGroupType, command common.DevfileCommand) common.DevfileCommand { + // Update Group only for exec commands + // Update Group only custom commands (specified by odo flags) + // Update Group only when Group is not nil, devfile v2 might contain group for custom commands. + if command.Exec != nil && commandName != "" && command.Exec.Group == nil { + command.Exec.Group = &common.Group{Kind: groupType} + return command + } + return command +} diff --git a/pkg/devfile/adapters/common/types.go b/pkg/devfile/adapters/common/types.go index 9b03333f643..3e4a86138b6 100644 --- a/pkg/devfile/adapters/common/types.go +++ b/pkg/devfile/adapters/common/types.go @@ -15,9 +15,9 @@ type AdapterContext struct { // DevfileVolume is a struct for Devfile volume that is common to all the adapters type DevfileVolume struct { - Name *string - ContainerPath *string - Size *string + Name string + ContainerPath string + Size string } // Storage is a struct that is common to all the adapters diff --git a/pkg/devfile/adapters/common/utils.go b/pkg/devfile/adapters/common/utils.go index 4bddc3c05a1..509796e1e9e 100644 --- a/pkg/devfile/adapters/common/utils.go +++ b/pkg/devfile/adapters/common/utils.go @@ -115,12 +115,12 @@ func GetVolumes(devfileObj devfileParser.DevfileObj) map[string][]DevfileVolume componentAliasToVolumes := make(map[string][]DevfileVolume) size := volumeSize for _, comp := range GetSupportedComponents(devfileObj.Data) { - if comp.Volume != nil { + if len(comp.Container.VolumeMounts) != 0 { for _, volume := range comp.Container.VolumeMounts { vol := DevfileVolume{ - Name: &volume.Name, - ContainerPath: &volume.Path, - Size: &size, + Name: volume.Name, + ContainerPath: volume.Path, + Size: size, } componentAliasToVolumes[comp.Container.Name] = append(componentAliasToVolumes[comp.Container.Name], vol) } diff --git a/pkg/devfile/adapters/docker/component/utils.go b/pkg/devfile/adapters/docker/component/utils.go index 569cf1fba09..4b76ab01534 100644 --- a/pkg/devfile/adapters/docker/component/utils.go +++ b/pkg/devfile/adapters/docker/component/utils.go @@ -73,10 +73,11 @@ func (a Adapter) createComponent() (err error) { for _, comp := range supportedComponents { var dockerVolumeMounts []mount.Mount for _, vol := range a.componentAliasToVolumes[comp.Container.Name] { + volMount := mount.Mount{ Type: mount.TypeVolume, - Source: a.volumeNameToDockerVolName[*vol.Name], - Target: *vol.ContainerPath, + Source: a.volumeNameToDockerVolName[vol.Name], + Target: vol.ContainerPath, } dockerVolumeMounts = append(dockerVolumeMounts, volMount) } @@ -129,8 +130,8 @@ func (a Adapter) updateComponent() (componentExists bool, err error) { for _, vol := range a.componentAliasToVolumes[comp.Container.Name] { volMount := mount.Mount{ Type: mount.TypeVolume, - Source: a.volumeNameToDockerVolName[*vol.Name], - Target: *vol.ContainerPath, + Source: a.volumeNameToDockerVolName[vol.Name], + Target: vol.ContainerPath, } dockerVolumeMounts = append(dockerVolumeMounts, volMount) } @@ -270,8 +271,7 @@ func (a Adapter) generateAndGetContainerConfig(componentName string, comp versio envVars := utils.ConvertEnvs(comp.Container.Env) ports := utils.ConvertPorts(comp.Container.Endpoints) containerLabels := utils.GetContainerLabels(componentName, comp.Container.Name) - - containerConfig := a.Client.GenerateContainerConfig(comp.Container.Image, []string{}, []string{}, envVars, containerLabels, ports) + containerConfig := a.Client.GenerateContainerConfig(comp.Container.Image, comp.Container.Command, comp.Container.Args, envVars, containerLabels, ports) return containerConfig } diff --git a/pkg/devfile/adapters/docker/storage/utils.go b/pkg/devfile/adapters/docker/storage/utils.go index 60bf74d5c8a..736cd793ae2 100644 --- a/pkg/devfile/adapters/docker/storage/utils.go +++ b/pkg/devfile/adapters/docker/storage/utils.go @@ -18,7 +18,7 @@ const volNameMaxLength = 45 func CreateComponentStorage(Client *lclient.Client, storages []common.Storage, componentName string) (err error) { for _, storage := range storages { - volumeName := *storage.Volume.Name + volumeName := storage.Volume.Name dockerVolName := storage.Name existingDockerVolName, err := GetExistingVolume(Client, volumeName, componentName) @@ -109,23 +109,23 @@ func ProcessVolumes(client *lclient.Client, componentName string, componentAlias // Get a list of all the unique volume names and generate their Docker volume names for _, volumes := range componentAliasToVolumes { for _, vol := range volumes { - if _, ok := processedVolumes[*vol.Name]; !ok { - processedVolumes[*vol.Name] = true + if _, ok := processedVolumes[vol.Name]; !ok { + processedVolumes[vol.Name] = true // Generate the volume Names - klog.V(3).Infof("Generating Docker volumes name for %v", *vol.Name) - generatedDockerVolName, err := GenerateVolName(*vol.Name, componentName) + klog.V(3).Infof("Generating Docker volumes name for %v", vol.Name) + generatedDockerVolName, err := GenerateVolName(vol.Name, componentName) if err != nil { return nil, nil, err } // Check if we have an existing volume with the labels, overwrite the generated name with the existing name if present - existingVolName, err := GetExistingVolume(client, *vol.Name, componentName) + existingVolName, err := GetExistingVolume(client, vol.Name, componentName) if err != nil { return nil, nil, err } if len(existingVolName) > 0 { - klog.V(3).Infof("Found an existing Docker volume for %v, volume %v will be re-used", *vol.Name, existingVolName) + klog.V(3).Infof("Found an existing Docker volume for %v, volume %v will be re-used", vol.Name, existingVolName) generatedDockerVolName = existingVolName } @@ -134,7 +134,7 @@ func ProcessVolumes(client *lclient.Client, componentName string, componentAlias Volume: vol, } uniqueStorages = append(uniqueStorages, dockerVol) - volumeNameToDockerVolName[*vol.Name] = generatedDockerVolName + volumeNameToDockerVolName[vol.Name] = generatedDockerVolName } } } diff --git a/pkg/devfile/adapters/docker/utils/utils.go b/pkg/devfile/adapters/docker/utils/utils.go index ca00e61427b..370b8096cc8 100644 --- a/pkg/devfile/adapters/docker/utils/utils.go +++ b/pkg/devfile/adapters/docker/utils/utils.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/docker/go-connections/nat" + "k8s.io/klog" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" @@ -211,11 +212,11 @@ func UpdateComponentWithSupervisord(comp *common.DevfileComponent, runCommand co if runCommand.Exec.Component == comp.Container.Name { AddVolumeToContainer(supervisordVolumeName, adaptersCommon.SupervisordMountPath, hostConfig) - //if len(comp.Command) == 0 && len(comp.Args) == 0 { - // klog.V(4).Infof("Updating container %v entrypoint with supervisord", *comp.Alias) - // comp.Command = append(comp.Command, adaptersCommon.SupervisordBinaryPath) - // comp.Args = append(comp.Args, "-c", adaptersCommon.SupervisordConfFile) - //} + if len(comp.Container.Command) == 0 && len(comp.Container.Args) == 0 { + klog.V(4).Infof("Updating container %v entrypoint with supervisord", comp.Container.Name) + comp.Container.Command = append(comp.Container.Command, adaptersCommon.SupervisordBinaryPath) + comp.Container.Args = append(comp.Container.Args, "-c", adaptersCommon.SupervisordConfFile) + } if !adaptersCommon.IsEnvPresent(comp.Container.Env, adaptersCommon.EnvOdoCommandRun) { envName := adaptersCommon.EnvOdoCommandRun diff --git a/pkg/devfile/adapters/kubernetes/component/adapter.go b/pkg/devfile/adapters/kubernetes/component/adapter.go index 8835e4d2665..d06f69cbab5 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter.go @@ -175,23 +175,23 @@ func (a Adapter) createOrUpdateComponent(componentExists bool) (err error) { // Get a list of all the unique volume names and generate their PVC names for _, volumes := range componentAliasToVolumes { for _, vol := range volumes { - if _, ok := processedVolumes[*vol.Name]; !ok { - processedVolumes[*vol.Name] = true + if _, ok := processedVolumes[vol.Name]; !ok { + processedVolumes[vol.Name] = true // Generate the PVC Names - klog.V(3).Infof("Generating PVC name for %v", *vol.Name) - generatedPVCName, err := storage.GeneratePVCNameFromDevfileVol(*vol.Name, componentName) + klog.V(3).Infof("Generating PVC name for %v", vol.Name) + generatedPVCName, err := storage.GeneratePVCNameFromDevfileVol(vol.Name, componentName) if err != nil { return err } // Check if we have an existing PVC with the labels, overwrite the generated name with the existing name if present - existingPVCName, err := storage.GetExistingPVC(&a.Client, *vol.Name, componentName) + existingPVCName, err := storage.GetExistingPVC(&a.Client, vol.Name, componentName) if err != nil { return err } if len(existingPVCName) > 0 { - klog.V(3).Infof("Found an existing PVC for %v, PVC %v will be re-used", *vol.Name, existingPVCName) + klog.V(3).Infof("Found an existing PVC for %v, PVC %v will be re-used", vol.Name, existingPVCName) generatedPVCName = existingPVCName } @@ -200,7 +200,7 @@ func (a Adapter) createOrUpdateComponent(componentExists bool) (err error) { Volume: vol, } uniqueStorages = append(uniqueStorages, pvc) - volumeNameToPVCName[*vol.Name] = generatedPVCName + volumeNameToPVCName[vol.Name] = generatedPVCName } } } diff --git a/pkg/devfile/adapters/kubernetes/storage/utils.go b/pkg/devfile/adapters/kubernetes/storage/utils.go index f15656fbcd3..0b7667300bb 100644 --- a/pkg/devfile/adapters/kubernetes/storage/utils.go +++ b/pkg/devfile/adapters/kubernetes/storage/utils.go @@ -20,8 +20,8 @@ const pvcNameMaxLen = 45 func CreateComponentStorage(Client *kclient.Client, storages []common.Storage, componentName string) (err error) { for _, storage := range storages { - volumeName := *storage.Volume.Name - volumeSize := *storage.Volume.Size + volumeName := storage.Volume.Name + volumeSize := storage.Volume.Size pvcName := storage.Name existingPVCName, err := GetExistingPVC(Client, volumeName, componentName) diff --git a/pkg/devfile/adapters/kubernetes/utils/utils.go b/pkg/devfile/adapters/kubernetes/utils/utils.go index c766761b18a..583a8307ada 100644 --- a/pkg/devfile/adapters/kubernetes/utils/utils.go +++ b/pkg/devfile/adapters/kubernetes/utils/utils.go @@ -62,7 +62,7 @@ func GetContainers(devfileObj devfileParser.DevfileObj) ([]corev1.Container, err if err != nil { return nil, err } - container := kclient.GenerateContainer(comp.Container.Name, comp.Container.Image, false, envVars, resourceReqs, ports) + container := kclient.GenerateContainer(comp.Container.Name, comp.Container.Image, false, comp.Container.Command, comp.Container.Args, envVars, resourceReqs, ports) for _, c := range containers { for _, containerPort := range c.Ports { for _, curPort := range container.Ports { diff --git a/pkg/devfile/parser/data/1.0.0/components.go b/pkg/devfile/parser/data/1.0.0/components.go index 03b68038c6e..2dac81c3218 100644 --- a/pkg/devfile/parser/data/1.0.0/components.go +++ b/pkg/devfile/parser/data/1.0.0/components.go @@ -1,8 +1,9 @@ package version100 import ( - "github.com/openshift/odo/pkg/devfile/parser/data/common" "strings" + + "github.com/openshift/odo/pkg/devfile/parser/data/common" ) func (d *Devfile100) GetMetadata() common.DevfileMetadata { @@ -81,6 +82,8 @@ func (d *Devfile100) GetEvents() common.DevfileEvents { func convertV1CommandToCommon(c Command) (d common.DevfileCommand) { var exec common.Exec + name := strings.ToLower(c.Name) + for _, action := range c.Actions { if action.Type == DevfileCommandTypeExec { @@ -88,8 +91,8 @@ func convertV1CommandToCommon(c Command) (d common.DevfileCommand) { Attributes: c.Attributes, CommandLine: action.Command, Component: action.Component, - Group: getGroup(c.Name), - Id: c.Name, + Group: getGroup(name), + Id: name, WorkingDir: action.Workdir, // Env: // Label: @@ -130,7 +133,8 @@ func convertV1ComponentToCommon(c Component) (component common.DevfileComponent) MemoryLimit: c.ComponentDockerimage.MemoryLimit, MountSources: c.MountSources, VolumeMounts: volumes, - // SourceMapping: Not present in V1 + Command: c.Command, + Args: c.Args, } component = common.DevfileComponent{Container: &container} @@ -182,7 +186,7 @@ func convertV1ProjectToCommon(p Project) common.DevfileProject { func getGroup(name string) *common.Group { group := common.Group{} - switch strings.ToLower(name) { + switch name { case "devrun": group.Kind = common.RunCommandGroupType group.IsDefault = true diff --git a/pkg/devfile/parser/data/common/types.go b/pkg/devfile/parser/data/common/types.go index efa7af7d559..3249f7c6e48 100644 --- a/pkg/devfile/parser/data/common/types.go +++ b/pkg/devfile/parser/data/common/types.go @@ -204,6 +204,9 @@ type Container struct { // List of volumes mounts that should be mounted is this container. VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` + + Command []string `json:"command,omitempty"` + Args []string `json:"args,omitempty"` } // Custom Custom component diff --git a/pkg/kclient/generators.go b/pkg/kclient/generators.go index 8bf9e99f05b..5d16e70a3b6 100644 --- a/pkg/kclient/generators.go +++ b/pkg/kclient/generators.go @@ -33,7 +33,7 @@ const ( ) // GenerateContainer creates a container spec that can be used when creating a pod -func GenerateContainer(name, image string, isPrivileged bool, envVars []corev1.EnvVar, resourceReqs corev1.ResourceRequirements, ports []corev1.ContainerPort) *corev1.Container { +func GenerateContainer(name, image string, isPrivileged bool, command, args []string, envVars []corev1.EnvVar, resourceReqs corev1.ResourceRequirements, ports []corev1.ContainerPort) *corev1.Container { container := &corev1.Container{ Name: name, Image: image, @@ -41,6 +41,8 @@ func GenerateContainer(name, image string, isPrivileged bool, envVars []corev1.E Resources: resourceReqs, Env: envVars, Ports: ports, + Command: command, + Args: args, } if isPrivileged { diff --git a/pkg/kclient/volumes.go b/pkg/kclient/volumes.go index 49897ffd559..13f168ddea4 100644 --- a/pkg/kclient/volumes.go +++ b/pkg/kclient/volumes.go @@ -88,8 +88,8 @@ func AddPVCAndVolumeMount(podTemplateSpec *corev1.PodTemplateSpec, volumeNameToP componentAliasToMountPaths := make(map[string][]string) for containerName, volumes := range componentAliasToVolumes { for _, volume := range volumes { - if volName == *volume.Name { - componentAliasToMountPaths[containerName] = append(componentAliasToMountPaths[containerName], *volume.ContainerPath) + if volName == volume.Name { + componentAliasToMountPaths[containerName] = append(componentAliasToMountPaths[containerName], volume.ContainerPath) } } } diff --git a/tests/integration/devfile/utils/utils.go b/tests/integration/devfile/utils/utils.go index 2a0ccfe3b97..47807e7fb0e 100644 --- a/tests/integration/devfile/utils/utils.go +++ b/tests/integration/devfile/utils/utils.go @@ -71,7 +71,7 @@ func ExecWithMissingRunCommand(projectDirPath, cmpName, namespace string) { args = useProjectIfAvailable(args, namespace) output := helper.CmdShouldFail("odo", args...) Expect(output).NotTo(ContainSubstring("Executing devrun command")) - Expect(output).To(ContainSubstring("The command \"devrun\" was not found in the devfile")) + Expect(output).To(ContainSubstring("The command type \"run\" is not found in the devfile")) } // ExecWithCustomCommand executes odo push with a custom command @@ -107,7 +107,7 @@ func ExecWithWrongCustomCommand(projectDirPath, cmpName, namespace string) { args = useProjectIfAvailable(args, namespace) output := helper.CmdShouldFail("odo", args...) Expect(output).NotTo(ContainSubstring("Executing buildgarbage command")) - Expect(output).To(ContainSubstring("The command \"%v\" was not found in the devfile", garbageCommand)) + Expect(output).To(ContainSubstring("The command \"%v\" is not found in the devfile", garbageCommand)) } // ExecPushToTestFileChanges executes odo push with and without a file change From 53401eab0042c6ec545c9be3ff4b3118e21e91a6 Mon Sep 17 00:00:00 2001 From: Mrinal Das Date: Wed, 27 May 2020 15:26:57 +0530 Subject: [PATCH 16/23] Fixes unit tests (#3240) Signed-off-by: mik-dass --- .../adapters/docker/component/adapter_test.go | 57 +++- .../adapters/docker/component/utils_test.go | 235 +++++++------- .../adapters/docker/storage/adapter_test.go | 22 +- .../adapters/docker/storage/utils_test.go | 130 ++++---- .../adapters/docker/utils/utils_test.go | 303 +++++++++--------- pkg/devfile/adapters/helper_test.go | 8 +- .../adapters/kubernetes/storage/utils_test.go | 36 +-- .../adapters/kubernetes/utils/utils_test.go | 82 ++--- pkg/devfile/validate/components_test.go | 7 +- pkg/kclient/volumes_test.go | 32 +- pkg/sync/adapter_test.go | 51 +-- pkg/testingutil/devfile.go | 19 +- 12 files changed, 527 insertions(+), 455 deletions(-) diff --git a/pkg/devfile/adapters/docker/component/adapter_test.go b/pkg/devfile/adapters/docker/component/adapter_test.go index 30a502e1ff5..1e22ef2ccda 100644 --- a/pkg/devfile/adapters/docker/component/adapter_test.go +++ b/pkg/devfile/adapters/docker/component/adapter_test.go @@ -11,7 +11,6 @@ import ( "github.com/golang/mock/gomock" adaptersCommon "github.com/openshift/odo/pkg/devfile/adapters/common" devfileParser "github.com/openshift/odo/pkg/devfile/parser" - "github.com/openshift/odo/pkg/devfile/parser/data/common" versionsCommon "github.com/openshift/odo/pkg/devfile/parser/data/common" "github.com/openshift/odo/pkg/lclient" "github.com/openshift/odo/pkg/testingutil" @@ -26,7 +25,6 @@ func TestPush(t *testing.T) { command := "ls -la" component := "alias1" workDir := "/root" - validCommandType := common.DevfileCommandTypeExec // create a temp dir for the file indexer directory, err := ioutil.TempDir("", "") @@ -42,17 +40,27 @@ func TestPush(t *testing.T) { ForceBuild: false, } - commandActions := []versionsCommon.DevfileCommandAction{ + execCommands := []versionsCommon.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + Group: &versionsCommon.Group{ + Kind: versionsCommon.RunCommandGroupType, + }, + WorkingDir: workDir, + }, + } + validComponents := []versionsCommon.DevfileComponent{ + { + Container: &versionsCommon.Container{ + Name: component, + }, }, } tests := []struct { name string + components []versionsCommon.DevfileComponent componentType versionsCommon.DevfileComponentType client *lclient.Client wantErr bool @@ -60,18 +68,21 @@ func TestPush(t *testing.T) { { name: "Case 1: Invalid devfile", componentType: "", + components: []versionsCommon.DevfileComponent{}, client: fakeClient, wantErr: true, }, { name: "Case 2: Valid devfile", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + components: validComponents, + componentType: versionsCommon.ContainerComponentType, client: fakeClient, wantErr: false, }, { name: "Case 3: Valid devfile, docker client error", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + components: validComponents, + componentType: versionsCommon.ContainerComponentType, client: fakeErrorClient, wantErr: true, }, @@ -80,8 +91,8 @@ func TestPush(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: tt.componentType, - CommandActions: commandActions, + Components: tt.components, + ExecCommands: execCommands, }, } @@ -124,14 +135,14 @@ func TestDoesComponentExist(t *testing.T) { { name: "Case 1: Valid component name", client: fakeClient, - componentType: versionsCommon.DevfileComponentTypeDockerimage, + componentType: versionsCommon.ContainerComponentType, componentName: "golang", getComponentName: "golang", want: true, }, { name: "Case 2: Non-existent component name", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + componentType: versionsCommon.ContainerComponentType, client: fakeClient, componentName: "test-name", getComponentName: "fake-component", @@ -139,7 +150,7 @@ func TestDoesComponentExist(t *testing.T) { }, { name: "Case 3: Docker client error", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + componentType: versionsCommon.ContainerComponentType, client: fakeErrorClient, componentName: "test-name", getComponentName: "fake-component", @@ -150,7 +161,11 @@ func TestDoesComponentExist(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: tt.componentType, + Components: []versionsCommon.DevfileComponent{ + { + Type: tt.componentType, + }, + }, }, } @@ -222,7 +237,11 @@ func TestAdapterDelete(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: "nodejs", + Components: []versionsCommon.DevfileComponent{ + { + Type: versionsCommon.ContainerComponentType, + }, + }, }, } @@ -561,7 +580,11 @@ func TestAdapterDeleteVolumes(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: "nodejs", + Components: []versionsCommon.DevfileComponent{ + { + Type: versionsCommon.ContainerComponentType, + }, + }, }, } diff --git a/pkg/devfile/adapters/docker/component/utils_test.go b/pkg/devfile/adapters/docker/component/utils_test.go index ba69a418d7e..e60499a08da 100644 --- a/pkg/devfile/adapters/docker/component/utils_test.go +++ b/pkg/devfile/adapters/docker/component/utils_test.go @@ -22,35 +22,36 @@ func TestCreateComponent(t *testing.T) { fakeErrorClient := lclient.FakeErrorNew() tests := []struct { - name string - componentType versionsCommon.DevfileComponentType - client *lclient.Client - wantErr bool + name string + components []versionsCommon.DevfileComponent + client *lclient.Client + wantErr bool }{ { - name: "Case 1: Invalid devfile", - componentType: "", - client: fakeClient, - wantErr: true, + name: "Case 1: Invalid devfile", + components: []versionsCommon.DevfileComponent{}, + client: fakeClient, + wantErr: true, }, { - name: "Case 2: Valid devfile", - componentType: versionsCommon.DevfileComponentTypeDockerimage, - client: fakeClient, - wantErr: false, + name: "Case 2: Valid devfile", + components: []versionsCommon.DevfileComponent{testingutil.GetFakeComponent("alias1")}, + client: fakeClient, + wantErr: false, }, { - name: "Case 3: Valid devfile, docker client error", - componentType: versionsCommon.DevfileComponentTypeDockerimage, - client: fakeErrorClient, - wantErr: true, + name: "Case 3: Valid devfile, docker client error", + components: []versionsCommon.DevfileComponent{testingutil.GetFakeComponent("alias1")}, + client: fakeErrorClient, + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: tt.componentType, + ExecCommands: testingutil.GetFakeExecRunCommands(), + Components: tt.components, }, } @@ -78,35 +79,41 @@ func TestUpdateComponent(t *testing.T) { tests := []struct { name string - componentType versionsCommon.DevfileComponentType + components []versionsCommon.DevfileComponent componentName string client *lclient.Client wantErr bool }{ { name: "Case 1: Invalid devfile", - componentType: "", + components: []versionsCommon.DevfileComponent{}, componentName: "", client: fakeClient, wantErr: true, }, { name: "Case 2: Valid devfile", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + components: []versionsCommon.DevfileComponent{testingutil.GetFakeComponent("alias1")}, componentName: "test", client: fakeClient, wantErr: false, }, { name: "Case 3: Valid devfile, docker client error", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + components: []versionsCommon.DevfileComponent{testingutil.GetFakeComponent("alias1")}, componentName: "", client: fakeErrorClient, wantErr: true, }, { - name: "Case 3: Valid devfile, missing component", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + name: "Case 3: Valid devfile, missing component", + components: []versionsCommon.DevfileComponent{ + { + Container: &versionsCommon.Container{ + Name: "fakecomponent", + }, + }, + }, componentName: "fakecomponent", client: fakeClient, wantErr: true, @@ -116,7 +123,8 @@ func TestUpdateComponent(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: tt.componentType, + Components: tt.components, + ExecCommands: testingutil.GetFakeExecRunCommands(), }, } @@ -154,21 +162,21 @@ func TestPullAndStartContainer(t *testing.T) { }{ { name: "Case 1: Successfully start container, no mount", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + componentType: versionsCommon.ContainerComponentType, client: fakeClient, mounts: []mount.Mount{}, wantErr: false, }, { name: "Case 2: Docker client error", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + componentType: versionsCommon.ContainerComponentType, client: fakeErrorClient, mounts: []mount.Mount{}, wantErr: true, }, { name: "Case 3: Successfully start container, one mount", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + componentType: versionsCommon.ContainerComponentType, client: fakeClient, mounts: []mount.Mount{ { @@ -180,7 +188,7 @@ func TestPullAndStartContainer(t *testing.T) { }, { name: "Case 4: Successfully start container, multiple mounts", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + componentType: versionsCommon.ContainerComponentType, client: fakeClient, mounts: []mount.Mount{ { @@ -199,7 +207,10 @@ func TestPullAndStartContainer(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: tt.componentType, + Components: []versionsCommon.DevfileComponent{ + testingutil.GetFakeComponent("alias1"), + }, + ExecCommands: testingutil.GetFakeExecRunCommands(), }, } @@ -229,30 +240,26 @@ func TestStartContainer(t *testing.T) { fakeErrorClient := lclient.FakeErrorNew() tests := []struct { - name string - componentType versionsCommon.DevfileComponentType - client *lclient.Client - mounts []mount.Mount - wantErr bool + name string + client *lclient.Client + mounts []mount.Mount + wantErr bool }{ { - name: "Case 1: Successfully start container, no mount", - componentType: versionsCommon.DevfileComponentTypeDockerimage, - client: fakeClient, - mounts: []mount.Mount{}, - wantErr: false, + name: "Case 1: Successfully start container, no mount", + client: fakeClient, + mounts: []mount.Mount{}, + wantErr: false, }, { - name: "Case 2: Docker client error", - componentType: versionsCommon.DevfileComponentTypeDockerimage, - client: fakeErrorClient, - mounts: []mount.Mount{}, - wantErr: true, + name: "Case 2: Docker client error", + client: fakeErrorClient, + mounts: []mount.Mount{}, + wantErr: true, }, { - name: "Case 3: Successfully start container, one mount", - componentType: versionsCommon.DevfileComponentTypeDockerimage, - client: fakeClient, + name: "Case 3: Successfully start container, one mount", + client: fakeClient, mounts: []mount.Mount{ { Source: "test-vol", @@ -262,9 +269,8 @@ func TestStartContainer(t *testing.T) { wantErr: false, }, { - name: "Case 4: Successfully start container, multiple mount", - componentType: versionsCommon.DevfileComponentTypeDockerimage, - client: fakeClient, + name: "Case 4: Successfully start container, multiple mount", + client: fakeClient, mounts: []mount.Mount{ { Source: "test-vol", @@ -282,7 +288,10 @@ func TestStartContainer(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: tt.componentType, + Components: []versionsCommon.DevfileComponent{ + testingutil.GetFakeComponent("alias1"), + }, + ExecCommands: testingutil.GetFakeExecRunCommands(), }, } @@ -306,7 +315,7 @@ func TestStartContainer(t *testing.T) { func TestGenerateAndGetHostConfig(t *testing.T) { fakeClient := lclient.FakeNew() testComponentName := "test" - componentType := versionsCommon.DevfileComponentTypeDockerimage + componentType := versionsCommon.ContainerComponentType endpointName := []string{"8080/tcp", "9090/tcp", "9080/tcp"} var endpointPort = []int32{8080, 9090, 9080} @@ -321,14 +330,14 @@ func TestGenerateAndGetHostConfig(t *testing.T) { urlValue []envinfo.EnvInfoURL expectResult nat.PortMap client *lclient.Client - endpoints []versionsCommon.DockerimageEndpoint + endpoints []versionsCommon.Endpoint }{ { name: "Case 1: no port mappings", urlValue: []envinfo.EnvInfoURL{}, expectResult: nil, client: fakeClient, - endpoints: []versionsCommon.DockerimageEndpoint{}, + endpoints: []versionsCommon.Endpoint{}, }, { name: "Case 2: only one port mapping", @@ -344,10 +353,10 @@ func TestGenerateAndGetHostConfig(t *testing.T) { }, }, client: fakeClient, - endpoints: []versionsCommon.DockerimageEndpoint{ + endpoints: []versionsCommon.Endpoint{ { - Name: &endpointName[0], - Port: &endpointPort[0], + Name: endpointName[0], + TargetPort: endpointPort[0], }, }, }, @@ -379,18 +388,18 @@ func TestGenerateAndGetHostConfig(t *testing.T) { }, }, client: fakeClient, - endpoints: []versionsCommon.DockerimageEndpoint{ + endpoints: []versionsCommon.Endpoint{ { - Name: &endpointName[0], - Port: &endpointPort[0], + Name: endpointName[0], + TargetPort: endpointPort[0], }, { - Name: &endpointName[1], - Port: &endpointPort[1], + Name: endpointName[1], + TargetPort: endpointPort[1], }, { - Name: &endpointName[2], - Port: &endpointPort[2], + Name: endpointName[2], + TargetPort: endpointPort[2], }, }, }, @@ -400,7 +409,11 @@ func TestGenerateAndGetHostConfig(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: componentType, + Components: []versionsCommon.DevfileComponent{ + { + Type: componentType, + }, + }, }, } @@ -453,11 +466,11 @@ func TestGenerateAndGetHostConfig(t *testing.T) { func TestExecDevfile(t *testing.T) { testComponentName := "test" - componentType := versionsCommon.DevfileComponentTypeDockerimage + componentType := versionsCommon.ContainerComponentType command := "ls -la" workDir := "/tmp" component := "alias1" - var actionType versionsCommon.DevfileCommandType = versionsCommon.DevfileCommandTypeExec + var actionType versionsCommon.DevfileCommandType = versionsCommon.ExecCommandType containers := []types.Container{ { @@ -480,35 +493,35 @@ func TestExecDevfile(t *testing.T) { tests := []struct { name string client *lclient.Client - pushDevfileCommands []versionsCommon.DevfileCommand + pushDevfileCommands adaptersCommon.PushCommandsMap componentExists bool wantErr bool }{ { name: "Case 1: Successful devfile command exec of devbuild and devrun", client: fakeClient, - pushDevfileCommands: []versionsCommon.DevfileCommand{ - { - Name: "devrun", - Actions: []versionsCommon.DevfileCommandAction{ - { - Command: &command, - Workdir: &workDir, - Type: &actionType, - Component: &component, + pushDevfileCommands: adaptersCommon.PushCommandsMap{ + versionsCommon.RunCommandGroupType: versionsCommon.DevfileCommand{ + Exec: &versionsCommon.Exec{ + CommandLine: command, + WorkingDir: workDir, + Component: component, + Group: &versionsCommon.Group{ + Kind: versionsCommon.RunCommandGroupType, }, }, + Type: actionType, }, - { - Name: "devbuild", - Actions: []versionsCommon.DevfileCommandAction{ - { - Command: &command, - Workdir: &workDir, - Type: &actionType, - Component: &component, + versionsCommon.BuildCommandGroupType: versionsCommon.DevfileCommand{ + Exec: &versionsCommon.Exec{ + CommandLine: command, + WorkingDir: workDir, + Component: component, + Group: &versionsCommon.Group{ + Kind: versionsCommon.BuildCommandGroupType, }, }, + Type: actionType, }, }, componentExists: false, @@ -517,17 +530,17 @@ func TestExecDevfile(t *testing.T) { { name: "Case 2: Successful devfile command exec of devrun", client: fakeClient, - pushDevfileCommands: []versionsCommon.DevfileCommand{ - { - Name: "devrun", - Actions: []versionsCommon.DevfileCommandAction{ - { - Command: &command, - Workdir: &workDir, - Type: &actionType, - Component: &component, + pushDevfileCommands: adaptersCommon.PushCommandsMap{ + versionsCommon.RunCommandGroupType: versionsCommon.DevfileCommand{ + Exec: &versionsCommon.Exec{ + CommandLine: command, + WorkingDir: workDir, + Component: component, + Group: &versionsCommon.Group{ + Kind: versionsCommon.RunCommandGroupType, }, }, + Type: actionType, }, }, componentExists: true, @@ -536,24 +549,24 @@ func TestExecDevfile(t *testing.T) { { name: "Case 3: No devfile push commands should result in an err", client: fakeClient, - pushDevfileCommands: []versionsCommon.DevfileCommand{}, + pushDevfileCommands: adaptersCommon.PushCommandsMap{}, componentExists: false, wantErr: true, }, { name: "Case 4: Unsuccessful devfile command exec of devrun", client: fakeErrorClient, - pushDevfileCommands: []versionsCommon.DevfileCommand{ - { - Name: "devrun", - Actions: []versionsCommon.DevfileCommandAction{ - { - Command: &command, - Workdir: &workDir, - Type: &actionType, - Component: &component, + pushDevfileCommands: adaptersCommon.PushCommandsMap{ + versionsCommon.RunCommandGroupType: versionsCommon.DevfileCommand{ + Exec: &versionsCommon.Exec{ + CommandLine: command, + WorkingDir: workDir, + Component: component, + Group: &versionsCommon.Group{ + Kind: versionsCommon.RunCommandGroupType, }, }, + Type: actionType, }, }, componentExists: true, @@ -565,7 +578,11 @@ func TestExecDevfile(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: componentType, + Components: []versionsCommon.DevfileComponent{ + { + Type: componentType, + }, + }, }, } @@ -586,7 +603,7 @@ func TestExecDevfile(t *testing.T) { func TestInitRunContainerSupervisord(t *testing.T) { testComponentName := "test" - componentType := versionsCommon.DevfileComponentTypeDockerimage + componentType := versionsCommon.ContainerComponentType containers := []types.Container{ { @@ -636,7 +653,11 @@ func TestInitRunContainerSupervisord(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: componentType, + Components: []versionsCommon.DevfileComponent{ + { + Type: componentType, + }, + }, }, } diff --git a/pkg/devfile/adapters/docker/storage/adapter_test.go b/pkg/devfile/adapters/docker/storage/adapter_test.go index b6b4a8a3a9b..0db1640e140 100644 --- a/pkg/devfile/adapters/docker/storage/adapter_test.go +++ b/pkg/devfile/adapters/docker/storage/adapter_test.go @@ -38,15 +38,15 @@ func TestCreate(t *testing.T) { { Name: "vol1", Volume: common.DevfileVolume{ - Name: &volNames[0], - Size: &volSize, + Name: volNames[0], + Size: volSize, }, }, { Name: "vol2", Volume: common.DevfileVolume{ - Name: &volNames[1], - Size: &volSize, + Name: volNames[1], + Size: volSize, }, }, }, @@ -59,15 +59,15 @@ func TestCreate(t *testing.T) { { Name: "vol1", Volume: common.DevfileVolume{ - Name: &volNames[0], - Size: &volSize, + Name: volNames[0], + Size: volSize, }, }, { Name: "vol2", Volume: common.DevfileVolume{ - Name: &volNames[1], - Size: &volSize, + Name: volNames[1], + Size: volSize, }, }, }, @@ -79,7 +79,11 @@ func TestCreate(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: versionsCommon.DevfileComponentTypeDockerimage, + Components: []versionsCommon.DevfileComponent{ + { + Type: versionsCommon.ContainerComponentType, + }, + }, }, } diff --git a/pkg/devfile/adapters/docker/storage/utils_test.go b/pkg/devfile/adapters/docker/storage/utils_test.go index 6030772914d..114d3a7c731 100644 --- a/pkg/devfile/adapters/docker/storage/utils_test.go +++ b/pkg/devfile/adapters/docker/storage/utils_test.go @@ -28,15 +28,15 @@ func TestCreateComponentStorage(t *testing.T) { { Name: "vol1", Volume: common.DevfileVolume{ - Name: &volNames[0], - Size: &volSize, + Name: volNames[0], + Size: volSize, }, }, { Name: "vol2", Volume: common.DevfileVolume{ - Name: &volNames[1], - Size: &volSize, + Name: volNames[1], + Size: volSize, }, }, }, @@ -49,15 +49,15 @@ func TestCreateComponentStorage(t *testing.T) { { Name: "vol1", Volume: common.DevfileVolume{ - Name: &volNames[0], - Size: &volSize, + Name: volNames[0], + Size: volSize, }, }, { Name: "vol2", Volume: common.DevfileVolume{ - Name: &volNames[1], - Size: &volSize, + Name: volNames[1], + Size: volSize, }, }, }, @@ -95,8 +95,8 @@ func TestStorageCreate(t *testing.T) { storage: common.Storage{ Name: "vol1", Volume: common.DevfileVolume{ - Name: &volNames[0], - Size: &volSize, + Name: volNames[0], + Size: volSize, }, }, client: fakeClient, @@ -107,8 +107,8 @@ func TestStorageCreate(t *testing.T) { storage: common.Storage{ Name: "vol-name", Volume: common.DevfileVolume{ - Name: &volNames[0], - Size: &volSize, + Name: volNames[0], + Size: volSize, }, }, client: fakeErrorClient, @@ -119,7 +119,7 @@ func TestStorageCreate(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // Create one of the test volumes - _, err := Create(tt.client, *tt.storage.Volume.Name, testComponentName, tt.storage.Name) + _, err := Create(tt.client, tt.storage.Volume.Name, testComponentName, tt.storage.Name) if !tt.wantErr == (err != nil) { t.Errorf("Docker volume create unexpected error %v, wantErr %v", err, tt.wantErr) } @@ -156,9 +156,9 @@ func TestProcessVolumes(t *testing.T) { aliasVolumeMapping: map[string][]common.DevfileVolume{ "some-component": []common.DevfileVolume{ { - Name: &volumeNames[0], - ContainerPath: &volumePaths[0], - Size: &volumeSizes[0], + Name: volumeNames[0], + ContainerPath: volumePaths[0], + Size: volumeSizes[0], }, }, }, @@ -166,9 +166,9 @@ func TestProcessVolumes(t *testing.T) { wantStorage: []common.Storage{ { Volume: common.DevfileVolume{ - Name: &volumeNames[0], - ContainerPath: &volumePaths[0], - Size: &volumeSizes[0], + Name: volumeNames[0], + ContainerPath: volumePaths[0], + Size: volumeSizes[0], }, }, }, @@ -179,19 +179,19 @@ func TestProcessVolumes(t *testing.T) { aliasVolumeMapping: map[string][]common.DevfileVolume{ "some-component": []common.DevfileVolume{ { - Name: &volumeNames[0], - ContainerPath: &volumePaths[0], - Size: &volumeSizes[0], + Name: volumeNames[0], + ContainerPath: volumePaths[0], + Size: volumeSizes[0], }, { - Name: &volumeNames[1], - ContainerPath: &volumePaths[1], - Size: &volumeSizes[1], + Name: volumeNames[1], + ContainerPath: volumePaths[1], + Size: volumeSizes[1], }, { - Name: &volumeNames[2], - ContainerPath: &volumePaths[2], - Size: &volumeSizes[2], + Name: volumeNames[2], + ContainerPath: volumePaths[2], + Size: volumeSizes[2], }, }, }, @@ -199,23 +199,23 @@ func TestProcessVolumes(t *testing.T) { wantStorage: []common.Storage{ { Volume: common.DevfileVolume{ - Name: &volumeNames[0], - ContainerPath: &volumePaths[0], - Size: &volumeSizes[0], + Name: volumeNames[0], + ContainerPath: volumePaths[0], + Size: volumeSizes[0], }, }, { Volume: common.DevfileVolume{ - Name: &volumeNames[1], - ContainerPath: &volumePaths[1], - Size: &volumeSizes[1], + Name: volumeNames[1], + ContainerPath: volumePaths[1], + Size: volumeSizes[1], }, }, { Volume: common.DevfileVolume{ - Name: &volumeNames[2], - ContainerPath: &volumePaths[2], - Size: &volumeSizes[2], + Name: volumeNames[2], + ContainerPath: volumePaths[2], + Size: volumeSizes[2], }, }, }, @@ -226,33 +226,33 @@ func TestProcessVolumes(t *testing.T) { aliasVolumeMapping: map[string][]common.DevfileVolume{ "some-component": []common.DevfileVolume{ { - Name: &volumeNames[0], - ContainerPath: &volumePaths[0], - Size: &volumeSizes[0], + Name: volumeNames[0], + ContainerPath: volumePaths[0], + Size: volumeSizes[0], }, { - Name: &volumeNames[1], - ContainerPath: &volumePaths[1], - Size: &volumeSizes[1], + Name: volumeNames[1], + ContainerPath: volumePaths[1], + Size: volumeSizes[1], }, }, "second-component": []common.DevfileVolume{ { - Name: &volumeNames[0], - ContainerPath: &volumePaths[0], - Size: &volumeSizes[0], + Name: volumeNames[0], + ContainerPath: volumePaths[0], + Size: volumeSizes[0], }, }, "third-component": []common.DevfileVolume{ { - Name: &volumeNames[1], - ContainerPath: &volumePaths[1], - Size: &volumeSizes[1], + Name: volumeNames[1], + ContainerPath: volumePaths[1], + Size: volumeSizes[1], }, { - Name: &volumeNames[2], - ContainerPath: &volumePaths[2], - Size: &volumeSizes[2], + Name: volumeNames[2], + ContainerPath: volumePaths[2], + Size: volumeSizes[2], }, }, }, @@ -260,23 +260,23 @@ func TestProcessVolumes(t *testing.T) { wantStorage: []common.Storage{ { Volume: common.DevfileVolume{ - Name: &volumeNames[0], - ContainerPath: &volumePaths[0], - Size: &volumeSizes[0], + Name: volumeNames[0], + ContainerPath: volumePaths[0], + Size: volumeSizes[0], }, }, { Volume: common.DevfileVolume{ - Name: &volumeNames[1], - ContainerPath: &volumePaths[1], - Size: &volumeSizes[1], + Name: volumeNames[1], + ContainerPath: volumePaths[1], + Size: volumeSizes[1], }, }, { Volume: common.DevfileVolume{ - Name: &volumeNames[2], - ContainerPath: &volumePaths[2], - Size: &volumeSizes[2], + Name: volumeNames[2], + ContainerPath: volumePaths[2], + Size: volumeSizes[2], }, }, }, @@ -287,9 +287,9 @@ func TestProcessVolumes(t *testing.T) { aliasVolumeMapping: map[string][]common.DevfileVolume{ "some-component": []common.DevfileVolume{ { - Name: &volumeNames[0], - ContainerPath: &volumePaths[0], - Size: &volumeSizes[0], + Name: volumeNames[0], + ContainerPath: volumePaths[0], + Size: volumeSizes[0], }, }, }, @@ -317,7 +317,7 @@ func TestProcessVolumes(t *testing.T) { for i := range uniqueStorage { var volExists bool for j := range tt.wantStorage { - if *uniqueStorage[i].Volume.Name == *tt.wantStorage[j].Volume.Name && uniqueStorage[i].Volume.ContainerPath == tt.wantStorage[j].Volume.ContainerPath { + if uniqueStorage[i].Volume.Name == tt.wantStorage[j].Volume.Name && uniqueStorage[i].Volume.ContainerPath == tt.wantStorage[j].Volume.ContainerPath { volExists = true } } diff --git a/pkg/devfile/adapters/docker/utils/utils_test.go b/pkg/devfile/adapters/docker/utils/utils_test.go index 3bfe2d61c53..39deabce43a 100644 --- a/pkg/devfile/adapters/docker/utils/utils_test.go +++ b/pkg/devfile/adapters/docker/utils/utils_test.go @@ -130,40 +130,40 @@ func TestConvertEnvs(t *testing.T) { envVarsValues := []string{"value1", "value2", "value3"} tests := []struct { name string - envVars []common.DockerimageEnv + envVars []common.Env want []string }{ { name: "Case 1: One env var", - envVars: []common.DockerimageEnv{ + envVars: []common.Env{ { - Name: &envVarsNames[0], - Value: &envVarsValues[0], + Name: envVarsNames[0], + Value: envVarsValues[0], }, }, want: []string{"test=value1"}, }, { name: "Case 2: Multiple env vars", - envVars: []common.DockerimageEnv{ + envVars: []common.Env{ { - Name: &envVarsNames[0], - Value: &envVarsValues[0], + Name: envVarsNames[0], + Value: envVarsValues[0], }, { - Name: &envVarsNames[1], - Value: &envVarsValues[1], + Name: envVarsNames[1], + Value: envVarsValues[1], }, { - Name: &envVarsNames[2], - Value: &envVarsValues[2], + Name: envVarsNames[2], + Value: envVarsValues[2], }, }, want: []string{"test=value1", "sample-var=value2", "myvar=value3"}, }, { name: "Case 3: No env vars", - envVars: []common.DockerimageEnv{}, + envVars: []common.Env{}, want: []string{}, }, } @@ -187,7 +187,7 @@ func TestDoesContainerNeedUpdating(t *testing.T) { tests := []struct { name string - envVars []common.DockerimageEnv + envVars []common.Env mounts []mount.Mount image string containerConfig container.Config @@ -198,14 +198,14 @@ func TestDoesContainerNeedUpdating(t *testing.T) { }{ { name: "Case 1: No changes", - envVars: []common.DockerimageEnv{ + envVars: []common.Env{ { - Name: &envVarsNames[0], - Value: &envVarsValues[0], + Name: envVarsNames[0], + Value: envVarsValues[0], }, { - Name: &envVarsNames[1], - Value: &envVarsValues[1], + Name: envVarsNames[1], + Value: envVarsValues[1], }, }, mounts: []mount.Mount{ @@ -229,10 +229,10 @@ func TestDoesContainerNeedUpdating(t *testing.T) { }, { name: "Case 2: Update required, env var changed", - envVars: []common.DockerimageEnv{ + envVars: []common.Env{ { - Name: &envVarsNames[2], - Value: &envVarsValues[2], + Name: envVarsNames[2], + Value: envVarsValues[2], }, }, image: "golang", @@ -244,10 +244,10 @@ func TestDoesContainerNeedUpdating(t *testing.T) { }, { name: "Case 3: Update required, image changed", - envVars: []common.DockerimageEnv{ + envVars: []common.Env{ { - Name: &envVarsNames[2], - Value: &envVarsValues[2], + Name: envVarsNames[2], + Value: envVarsValues[2], }, }, image: "node", @@ -259,14 +259,14 @@ func TestDoesContainerNeedUpdating(t *testing.T) { }, { name: "Case 4: Update required, volumes changed", - envVars: []common.DockerimageEnv{ + envVars: []common.Env{ { - Name: &envVarsNames[0], - Value: &envVarsValues[0], + Name: envVarsNames[0], + Value: envVarsValues[0], }, { - Name: &envVarsNames[1], - Value: &envVarsValues[1], + Name: envVarsNames[1], + Value: envVarsValues[1], }, }, mounts: []mount.Mount{ @@ -294,14 +294,14 @@ func TestDoesContainerNeedUpdating(t *testing.T) { }, { name: "Case 5: Update required, port changed", - envVars: []common.DockerimageEnv{ + envVars: []common.Env{ { - Name: &envVarsNames[0], - Value: &envVarsValues[0], + Name: envVarsNames[0], + Value: envVarsValues[0], }, { - Name: &envVarsNames[1], - Value: &envVarsValues[1], + Name: envVarsNames[1], + Value: envVarsValues[1], }, }, mounts: []mount.Mount{ @@ -332,14 +332,14 @@ func TestDoesContainerNeedUpdating(t *testing.T) { }, { name: "Case 6: Update required, exposed port changed", - envVars: []common.DockerimageEnv{ + envVars: []common.Env{ { - Name: &envVarsNames[0], - Value: &envVarsValues[0], + Name: envVarsNames[0], + Value: envVarsValues[0], }, { - Name: &envVarsNames[1], - Value: &envVarsValues[1], + Name: envVarsNames[1], + Value: envVarsValues[1], }, }, mounts: []mount.Mount{ @@ -388,14 +388,14 @@ func TestDoesContainerNeedUpdating(t *testing.T) { }, { name: "Case 7: Update not required, exposed port unchanged", - envVars: []common.DockerimageEnv{ + envVars: []common.Env{ { - Name: &envVarsNames[0], - Value: &envVarsValues[0], + Name: envVarsNames[0], + Value: envVarsValues[0], }, { - Name: &envVarsNames[1], - Value: &envVarsValues[1], + Name: envVarsNames[1], + Value: envVarsValues[1], }, }, mounts: []mount.Mount{ @@ -447,8 +447,8 @@ func TestDoesContainerNeedUpdating(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { component := common.DevfileComponent{ - DevfileComponentDockerimage: common.DevfileComponentDockerimage{ - Image: &tt.image, + Container: &common.Container{ + Image: tt.image, Env: tt.envVars, }, } @@ -676,14 +676,13 @@ func TestUpdateComponentWithSupervisord(t *testing.T) { workDir := "/" emptyString := "" garbageString := "garbageString" - validCommandType := common.DevfileCommandTypeExec supervisordVolumeName := "supervisordVolumeName" defaultWorkDirEnv := adaptersCommon.EnvOdoCommandRunWorkingDir defaultCommandEnv := adaptersCommon.EnvOdoCommandRun tests := []struct { name string - commandActions []common.DevfileCommandAction + commandExecs []common.Exec commandName string comp common.DevfileComponent supervisordVolumeName string @@ -691,25 +690,27 @@ func TestUpdateComponentWithSupervisord(t *testing.T) { wantHostConfig container.HostConfig wantCommand []string wantArgs []string - wantEnv []common.DockerimageEnv + wantEnv []common.Env }{ { name: "Case 1: No component commands, args, env", - commandActions: []common.DevfileCommandAction{ + commandExecs: []common.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + Group: &common.Group{ + Kind: common.RunCommandGroupType, + }, + WorkingDir: workDir, }, }, commandName: emptyString, comp: common.DevfileComponent{ - Alias: &component, - DevfileComponentDockerimage: common.DevfileComponentDockerimage{ + Container: &common.Container{ Command: []string{}, Args: []string{}, - Env: []common.DockerimageEnv{}, + Env: []common.Env{}, + Name: component, }, }, supervisordVolumeName: supervisordVolumeName, @@ -725,34 +726,36 @@ func TestUpdateComponentWithSupervisord(t *testing.T) { }, wantCommand: []string{adaptersCommon.SupervisordBinaryPath}, wantArgs: []string{"-c", adaptersCommon.SupervisordConfFile}, - wantEnv: []common.DockerimageEnv{ + wantEnv: []common.Env{ { - Name: &defaultWorkDirEnv, - Value: &workDir, + Name: defaultWorkDirEnv, + Value: workDir, }, { - Name: &defaultCommandEnv, - Value: &command, + Name: defaultCommandEnv, + Value: command, }, }, }, { name: "Case 2: Existing component command and no args, env", - commandActions: []common.DevfileCommandAction{ + commandExecs: []common.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + Group: &common.Group{ + Kind: common.RunCommandGroupType, + }, + WorkingDir: workDir, }, }, commandName: emptyString, comp: common.DevfileComponent{ - Alias: &component, - DevfileComponentDockerimage: common.DevfileComponentDockerimage{ + Container: &common.Container{ Command: []string{"some", "command"}, Args: []string{}, - Env: []common.DockerimageEnv{}, + Env: []common.Env{}, + Name: component, }, }, supervisordVolumeName: supervisordVolumeName, @@ -768,34 +771,36 @@ func TestUpdateComponentWithSupervisord(t *testing.T) { }, wantCommand: []string{"some", "command"}, wantArgs: []string{}, - wantEnv: []common.DockerimageEnv{ + wantEnv: []common.Env{ { - Name: &defaultWorkDirEnv, - Value: &workDir, + Name: defaultWorkDirEnv, + Value: workDir, }, { - Name: &defaultCommandEnv, - Value: &command, + Name: defaultCommandEnv, + Value: command, }, }, }, { name: "Case 3: Existing component command and args and no env", - commandActions: []common.DevfileCommandAction{ + commandExecs: []common.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + Group: &common.Group{ + Kind: common.RunCommandGroupType, + }, + WorkingDir: workDir, }, }, commandName: emptyString, comp: common.DevfileComponent{ - Alias: &component, - DevfileComponentDockerimage: common.DevfileComponentDockerimage{ + Container: &common.Container{ Command: []string{"some", "command"}, Args: []string{"some", "args"}, - Env: []common.DockerimageEnv{}, + Env: []common.Env{}, + Name: component, }, }, supervisordVolumeName: supervisordVolumeName, @@ -811,43 +816,45 @@ func TestUpdateComponentWithSupervisord(t *testing.T) { }, wantCommand: []string{"some", "command"}, wantArgs: []string{"some", "args"}, - wantEnv: []common.DockerimageEnv{ + wantEnv: []common.Env{ { - Name: &defaultWorkDirEnv, - Value: &workDir, + Name: defaultWorkDirEnv, + Value: workDir, }, { - Name: &defaultCommandEnv, - Value: &command, + Name: defaultCommandEnv, + Value: command, }, }, }, { name: "Case 4: Existing component command, args and env", - commandActions: []common.DevfileCommandAction{ + commandExecs: []common.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + Group: &common.Group{ + Kind: common.RunCommandGroupType, + }, + WorkingDir: workDir, }, }, commandName: emptyString, comp: common.DevfileComponent{ - Alias: &component, - DevfileComponentDockerimage: common.DevfileComponentDockerimage{ + Container: &common.Container{ Command: []string{"some", "command"}, Args: []string{"some", "args"}, - Env: []common.DockerimageEnv{ + Env: []common.Env{ { - Name: &defaultWorkDirEnv, - Value: &garbageString, + Name: defaultWorkDirEnv, + Value: garbageString, }, { - Name: &defaultCommandEnv, - Value: &garbageString, + Name: defaultCommandEnv, + Value: garbageString, }, }, + Name: component, }, }, supervisordVolumeName: supervisordVolumeName, @@ -863,43 +870,45 @@ func TestUpdateComponentWithSupervisord(t *testing.T) { }, wantCommand: []string{"some", "command"}, wantArgs: []string{"some", "args"}, - wantEnv: []common.DockerimageEnv{ + wantEnv: []common.Env{ { - Name: &defaultWorkDirEnv, - Value: &garbageString, + Name: defaultWorkDirEnv, + Value: garbageString, }, { - Name: &defaultCommandEnv, - Value: &garbageString, + Name: defaultCommandEnv, + Value: garbageString, }, }, }, { name: "Case 5: Existing host config, should append to it", - commandActions: []common.DevfileCommandAction{ + commandExecs: []common.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + Group: &common.Group{ + Kind: common.RunCommandGroupType, + }, + WorkingDir: workDir, }, }, commandName: emptyString, comp: common.DevfileComponent{ - Alias: &component, - DevfileComponentDockerimage: common.DevfileComponentDockerimage{ + Container: &common.Container{ Command: []string{"some", "command"}, Args: []string{"some", "args"}, - Env: []common.DockerimageEnv{ + Env: []common.Env{ { - Name: &defaultWorkDirEnv, - Value: &garbageString, + Name: defaultWorkDirEnv, + Value: garbageString, }, { - Name: &defaultCommandEnv, - Value: &garbageString, + Name: defaultCommandEnv, + Value: garbageString, }, }, + Name: component, }, }, supervisordVolumeName: supervisordVolumeName, @@ -928,52 +937,30 @@ func TestUpdateComponentWithSupervisord(t *testing.T) { }, wantCommand: []string{"some", "command"}, wantArgs: []string{"some", "args"}, - wantEnv: []common.DockerimageEnv{ - { - Name: &defaultWorkDirEnv, - Value: &garbageString, - }, + wantEnv: []common.Env{ { - Name: &defaultCommandEnv, - Value: &garbageString, + Name: defaultWorkDirEnv, + Value: garbageString, }, - }, - }, - { - name: "Case 6: Not a run command component", - commandActions: []common.DevfileCommandAction{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, - }, - }, - commandName: emptyString, - comp: common.DevfileComponent{ - Alias: &garbageString, - DevfileComponentDockerimage: common.DevfileComponentDockerimage{ - Command: []string{}, - Args: []string{}, - Env: []common.DockerimageEnv{}, + Name: defaultCommandEnv, + Value: garbageString, }, }, - supervisordVolumeName: supervisordVolumeName, - hostConfig: container.HostConfig{}, - wantHostConfig: container.HostConfig{ - Mounts: []mount.Mount{}, - }, - wantCommand: []string{}, - wantArgs: []string{}, - wantEnv: []common.DockerimageEnv{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - CommandActions: tt.commandActions, - ComponentType: common.DevfileComponentTypeDockerimage, + ExecCommands: tt.commandExecs, + Components: []common.DevfileComponent{ + { + Container: &common.Container{ + Name: tt.comp.Container.Name, + }, + }, + }, }, } @@ -999,17 +986,17 @@ func TestUpdateComponentWithSupervisord(t *testing.T) { } // Check the component command - if !reflect.DeepEqual(tt.comp.Command, tt.wantCommand) { - t.Errorf("TestUpdateComponentWithSupervisord: component commands dont match actual: %v wanted: %v", tt.comp.Command, tt.wantCommand) + if !reflect.DeepEqual(tt.comp.Container.Command, tt.wantCommand) { + t.Errorf("TestUpdateComponentWithSupervisord: component commands dont match actual: %v wanted: %v", tt.comp.Container.Command, tt.wantCommand) } // Check the component args - if !reflect.DeepEqual(tt.comp.Args, tt.wantArgs) { - t.Errorf("TestUpdateComponentWithSupervisord: component args dont match actual: %v wanted: %v", tt.comp.Args, tt.wantArgs) + if !reflect.DeepEqual(tt.comp.Container.Args, tt.wantArgs) { + t.Errorf("TestUpdateComponentWithSupervisord: component args dont match actual: %v wanted: %v", tt.comp.Container.Args, tt.wantArgs) } // Check the component env - for _, compEnv := range tt.comp.Env { + for _, compEnv := range tt.comp.Container.Env { matched := false for _, wantEnv := range tt.wantEnv { if reflect.DeepEqual(wantEnv, compEnv) { @@ -1018,7 +1005,7 @@ func TestUpdateComponentWithSupervisord(t *testing.T) { } if !matched { - t.Errorf("TestUpdateComponentWithSupervisord: component env dont match env: %v:%v not present in wanted list", *compEnv.Name, *compEnv.Value) + t.Errorf("TestUpdateComponentWithSupervisord: component env dont match env: %v:%v not present in wanted list", compEnv.Name, compEnv.Value) } } diff --git a/pkg/devfile/adapters/helper_test.go b/pkg/devfile/adapters/helper_test.go index ac10709ca35..eadc0ff4fa1 100644 --- a/pkg/devfile/adapters/helper_test.go +++ b/pkg/devfile/adapters/helper_test.go @@ -23,7 +23,7 @@ func TestNewPlatformAdapter(t *testing.T) { adapterType: "kubernetes.Adapter", name: "get platform adapter", componentName: "test", - componentType: versionsCommon.DevfileComponentTypeDockerimage, + componentType: versionsCommon.ContainerComponentType, wantErr: false, }, } @@ -31,7 +31,11 @@ func TestNewPlatformAdapter(t *testing.T) { t.Run("get platform adapter", func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: tt.componentType, + Components: []versionsCommon.DevfileComponent{ + { + Type: tt.componentType, + }, + }, }, } diff --git a/pkg/devfile/adapters/kubernetes/storage/utils_test.go b/pkg/devfile/adapters/kubernetes/storage/utils_test.go index a60f2234c24..6c6dfac49f4 100644 --- a/pkg/devfile/adapters/kubernetes/storage/utils_test.go +++ b/pkg/devfile/adapters/kubernetes/storage/utils_test.go @@ -33,15 +33,15 @@ func TestCreateComponentStorage(t *testing.T) { { Name: "vol1-pvc", Volume: common.DevfileVolume{ - Name: &volNames[0], - Size: &volSize, + Name: volNames[0], + Size: volSize, }, }, { Name: "vol2-pvc", Volume: common.DevfileVolume{ - Name: &volNames[1], - Size: &volSize, + Name: volNames[1], + Size: volSize, }, }, }, @@ -66,7 +66,7 @@ func TestCreateComponentStorage(t *testing.T) { }) // Create one of the test volumes - createdPVC, err := Create(fkclient, *tt.storages[0].Volume.Name, *tt.storages[0].Volume.Size, testComponentName, tt.storages[0].Name) + createdPVC, err := Create(fkclient, tt.storages[0].Volume.Name, tt.storages[0].Volume.Size, testComponentName, tt.storages[0].Name) if err != nil { t.Errorf("Error creating PVC %v: %v", tt.storages[0].Name, err) } @@ -78,9 +78,9 @@ func TestCreateComponentStorage(t *testing.T) { fkclientset.Kubernetes.PrependReactor("create", "persistentvolumeclaims", func(action ktesting.Action) (bool, runtime.Object, error) { labels := map[string]string{ "component": testComponentName, - "storage-name": *tt.storages[1].Volume.Name, + "storage-name": tt.storages[1].Volume.Name, } - PVC := testingutil.FakePVC(tt.storages[1].Name, *tt.storages[1].Volume.Size, labels) + PVC := testingutil.FakePVC(tt.storages[1].Name, tt.storages[1].Volume.Size, labels) return true, PVC, nil }) @@ -114,8 +114,8 @@ func TestStorageCreate(t *testing.T) { storage: common.Storage{ Name: "vol1-pvc", Volume: common.DevfileVolume{ - Name: &volNames[0], - Size: &volSize, + Name: volNames[0], + Size: volSize, }, }, wantErr: false, @@ -126,8 +126,8 @@ func TestStorageCreate(t *testing.T) { storage: common.Storage{ Name: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", Volume: common.DevfileVolume{ - Name: &volNames[0], - Size: &volSize, + Name: volNames[0], + Size: volSize, }, }, wantErr: true, @@ -138,8 +138,8 @@ func TestStorageCreate(t *testing.T) { storage: common.Storage{ Name: "", Volume: common.DevfileVolume{ - Name: &volNames[0], - Size: &volSize, + Name: volNames[0], + Size: volSize, }, }, wantErr: true, @@ -150,8 +150,8 @@ func TestStorageCreate(t *testing.T) { storage: common.Storage{ Name: "vol1-pvc", Volume: common.DevfileVolume{ - Name: &volNames[0], - Size: &garbageVolSize, + Name: volNames[0], + Size: garbageVolSize, }, }, wantErr: true, @@ -179,17 +179,17 @@ func TestStorageCreate(t *testing.T) { fkclientset.Kubernetes.PrependReactor("create", "persistentvolumeclaims", func(action ktesting.Action) (bool, runtime.Object, error) { labels := map[string]string{ "component": testComponentName, - "storage-name": *tt.storage.Volume.Name, + "storage-name": tt.storage.Volume.Name, } if tt.wantErr { return true, nil, tt.err } - PVC := testingutil.FakePVC(tt.storage.Name, *tt.storage.Volume.Size, labels) + PVC := testingutil.FakePVC(tt.storage.Name, tt.storage.Volume.Size, labels) return true, PVC, nil }) // Create one of the test volumes - createdPVC, err := Create(fkclient, *tt.storage.Volume.Name, *tt.storage.Volume.Size, testComponentName, tt.storage.Name) + createdPVC, err := Create(fkclient, tt.storage.Volume.Name, tt.storage.Volume.Size, testComponentName, tt.storage.Name) if !tt.wantErr && err != nil { t.Errorf("Error creating PVC %v: %v", tt.storage.Name, err) } else if tt.wantErr && err != nil { diff --git a/pkg/devfile/adapters/kubernetes/utils/utils_test.go b/pkg/devfile/adapters/kubernetes/utils/utils_test.go index 8f12c3c8a78..2e0b4053e5a 100644 --- a/pkg/devfile/adapters/kubernetes/utils/utils_test.go +++ b/pkg/devfile/adapters/kubernetes/utils/utils_test.go @@ -19,9 +19,12 @@ func TestUpdateContainersWithSupervisord(t *testing.T) { component := "alias1" image := "image1" workDir := "/root" - validCommandType := common.DevfileCommandTypeExec emptyString := "" defaultCommand := []string{"tail"} + execGroup := versionsCommon.Group{ + IsDefault: true, + Kind: versionsCommon.RunCommandGroupType, + } defaultArgs := []string{"-f", "/dev/null"} supervisordCommand := []string{adaptersCommon.SupervisordBinaryPath} supervisordArgs := []string{"-c", adaptersCommon.SupervisordConfFile} @@ -30,7 +33,7 @@ func TestUpdateContainersWithSupervisord(t *testing.T) { name string runCommand string containers []corev1.Container - commandActions []common.DevfileCommandAction + execCommands []common.Exec componentType common.DevfileComponentType isSupervisordEntrypoint bool wantErr bool @@ -48,15 +51,15 @@ func TestUpdateContainersWithSupervisord(t *testing.T) { Env: []corev1.EnvVar{}, }, }, - commandActions: []versionsCommon.DevfileCommandAction{ + execCommands: []versionsCommon.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &execGroup, }, }, - componentType: common.DevfileComponentTypeDockerimage, + componentType: common.ContainerComponentType, isSupervisordEntrypoint: false, wantErr: false, }, @@ -73,14 +76,14 @@ func TestUpdateContainersWithSupervisord(t *testing.T) { Env: []corev1.EnvVar{}, }, }, - commandActions: []versionsCommon.DevfileCommandAction{ + execCommands: []versionsCommon.Exec{ { - Command: &command, - Component: &component, - Type: &validCommandType, + CommandLine: command, + Component: component, + Group: &execGroup, }, }, - componentType: common.DevfileComponentTypeDockerimage, + componentType: common.ContainerComponentType, isSupervisordEntrypoint: false, wantErr: false, }, @@ -95,15 +98,15 @@ func TestUpdateContainersWithSupervisord(t *testing.T) { Env: []corev1.EnvVar{}, }, }, - commandActions: []versionsCommon.DevfileCommandAction{ + execCommands: []versionsCommon.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &execGroup, }, }, - componentType: common.DevfileComponentTypeDockerimage, + componentType: common.ContainerComponentType, isSupervisordEntrypoint: true, wantErr: false, }, @@ -118,15 +121,16 @@ func TestUpdateContainersWithSupervisord(t *testing.T) { Env: []corev1.EnvVar{}, }, }, - commandActions: []versionsCommon.DevfileCommandAction{ + execCommands: []versionsCommon.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + Id: "customcommand", + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &execGroup, }, }, - componentType: common.DevfileComponentTypeDockerimage, + componentType: common.ContainerComponentType, isSupervisordEntrypoint: true, wantErr: false, }, @@ -141,15 +145,15 @@ func TestUpdateContainersWithSupervisord(t *testing.T) { Env: []corev1.EnvVar{}, }, }, - commandActions: []versionsCommon.DevfileCommandAction{ + execCommands: []versionsCommon.Exec{ { - Command: &command, - Component: &component, - Workdir: &workDir, - Type: &validCommandType, + CommandLine: command, + Component: component, + WorkingDir: workDir, + Group: &execGroup, }, }, - componentType: common.DevfileComponentTypeDockerimage, + componentType: common.ContainerComponentType, isSupervisordEntrypoint: true, wantErr: true, }, @@ -158,8 +162,14 @@ func TestUpdateContainersWithSupervisord(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: tt.componentType, - CommandActions: tt.commandActions, + Components: []versionsCommon.DevfileComponent{ + { + Container: &versionsCommon.Container{ + Name: component, + }, + }, + }, + ExecCommands: tt.execCommands, }, } @@ -177,7 +187,7 @@ func TestUpdateContainersWithSupervisord(t *testing.T) { envRunMatched := false envWorkDirMatched := false - if tt.commandActions[0].Workdir == nil { + if tt.execCommands[0].WorkingDir == "" { // if workdir is not present, dont test for matching the env envWorkDirMatched = true } @@ -191,10 +201,10 @@ func TestUpdateContainersWithSupervisord(t *testing.T) { } for _, envVar := range container.Env { - if envVar.Name == adaptersCommon.EnvOdoCommandRun && envVar.Value == *tt.commandActions[0].Command { + if envVar.Name == adaptersCommon.EnvOdoCommandRun && envVar.Value == tt.execCommands[0].CommandLine { envRunMatched = true } - if tt.commandActions[0].Workdir != nil && envVar.Name == adaptersCommon.EnvOdoCommandRunWorkingDir && envVar.Value == *tt.commandActions[0].Workdir { + if tt.execCommands[0].WorkingDir != "" && envVar.Name == adaptersCommon.EnvOdoCommandRunWorkingDir && envVar.Value == tt.execCommands[0].WorkingDir { envWorkDirMatched = true } } diff --git a/pkg/devfile/validate/components_test.go b/pkg/devfile/validate/components_test.go index 7da9b9d79bd..e6ae1a876bd 100644 --- a/pkg/devfile/validate/components_test.go +++ b/pkg/devfile/validate/components_test.go @@ -23,10 +23,13 @@ func TestValidateComponents(t *testing.T) { } }) - t.Run("DockerImage type of component present", func(t *testing.T) { + t.Run("Container type of component present", func(t *testing.T) { components := []common.DevfileComponent{ { + Container: &common.Container{ + Name: "container", + }, Type: common.ContainerComponentType, }, } @@ -38,7 +41,7 @@ func TestValidateComponents(t *testing.T) { } }) - t.Run("DockerImage type of component NOT present", func(t *testing.T) { + t.Run("Container type of component NOT present", func(t *testing.T) { components := []common.DevfileComponent{ { diff --git a/pkg/kclient/volumes_test.go b/pkg/kclient/volumes_test.go index 9d8d0c28ea1..dad3ee5cd20 100644 --- a/pkg/kclient/volumes_test.go +++ b/pkg/kclient/volumes_test.go @@ -380,26 +380,26 @@ func TestAddPVCAndVolumeMount(t *testing.T) { componentAliasToVolumes: map[string][]common.DevfileVolume{ "container1": []common.DevfileVolume{ { - Name: &volNames[0], - ContainerPath: &volContainerPath[0], + Name: volNames[0], + ContainerPath: volContainerPath[0], }, { - Name: &volNames[0], - ContainerPath: &volContainerPath[1], + Name: volNames[0], + ContainerPath: volContainerPath[1], }, { - Name: &volNames[1], - ContainerPath: &volContainerPath[2], + Name: volNames[1], + ContainerPath: volContainerPath[2], }, }, "container2": []common.DevfileVolume{ { - Name: &volNames[1], - ContainerPath: &volContainerPath[1], + Name: volNames[1], + ContainerPath: volContainerPath[1], }, { - Name: &volNames[2], - ContainerPath: &volContainerPath[2], + Name: volNames[2], + ContainerPath: volContainerPath[2], }, }, }, @@ -421,12 +421,12 @@ func TestAddPVCAndVolumeMount(t *testing.T) { componentAliasToVolumes: map[string][]common.DevfileVolume{ "container2": []common.DevfileVolume{ { - Name: &volNames[1], - ContainerPath: &volContainerPath[1], + Name: volNames[1], + ContainerPath: volContainerPath[1], }, { - Name: &volNames[2], - ContainerPath: &volContainerPath[2], + Name: volNames[2], + ContainerPath: volContainerPath[2], }, }, }, @@ -474,8 +474,8 @@ func TestAddPVCAndVolumeMount(t *testing.T) { volumeMatched := 0 for _, volumeMount := range container.VolumeMounts { for _, testVolume := range testContainerVolumes { - testVolumeName := *testVolume.Name - testVolumePath := *testVolume.ContainerPath + testVolumeName := testVolume.Name + testVolumePath := testVolume.ContainerPath if strings.Contains(volumeMount.Name, testVolumeName) && volumeMount.MountPath == testVolumePath { volumeMatched++ } diff --git a/pkg/sync/adapter_test.go b/pkg/sync/adapter_test.go index 84c07fe4aa0..e67cb5259af 100644 --- a/pkg/sync/adapter_test.go +++ b/pkg/sync/adapter_test.go @@ -40,8 +40,7 @@ func TestGetSyncFolder(t *testing.T) { projects: []versionsCommon.DevfileProject{ { Name: projectNames[0], - Source: versionsCommon.DevfileProjectSource{ - Type: versionsCommon.DevfileProjectTypeGit, + Git: &versionsCommon.Git{ Location: projectRepos[0], }, }, @@ -54,15 +53,19 @@ func TestGetSyncFolder(t *testing.T) { projects: []versionsCommon.DevfileProject{ { Name: projectNames[0], - Source: versionsCommon.DevfileProjectSource{ - Type: versionsCommon.DevfileProjectTypeGit, + Git: &versionsCommon.Git{ Location: projectRepos[0], }, }, { Name: projectNames[1], - Source: versionsCommon.DevfileProjectSource{ - Type: versionsCommon.DevfileProjectTypeGit, + Github: &versionsCommon.Github{ + Location: projectRepos[1], + }, + }, + { + Name: projectNames[1], + Zip: &versionsCommon.Zip{ Location: projectRepos[1], }, }, @@ -74,10 +77,9 @@ func TestGetSyncFolder(t *testing.T) { name: "Case 4: Clone path set", projects: []versionsCommon.DevfileProject{ { - ClonePath: &projectClonePath, + ClonePath: projectClonePath, Name: projectNames[0], - Source: versionsCommon.DevfileProjectSource{ - Type: versionsCommon.DevfileProjectTypeGit, + Zip: &versionsCommon.Zip{ Location: projectRepos[0], }, }, @@ -89,10 +91,9 @@ func TestGetSyncFolder(t *testing.T) { name: "Case 5: Invalid clone path, set with absolute path", projects: []versionsCommon.DevfileProject{ { - ClonePath: &invalidClonePaths[0], + ClonePath: invalidClonePaths[0], Name: projectNames[0], - Source: versionsCommon.DevfileProjectSource{ - Type: versionsCommon.DevfileProjectTypeGit, + Github: &versionsCommon.Github{ Location: projectRepos[0], }, }, @@ -104,10 +105,9 @@ func TestGetSyncFolder(t *testing.T) { name: "Case 6: Invalid clone path, starts with ..", projects: []versionsCommon.DevfileProject{ { - ClonePath: &invalidClonePaths[1], + ClonePath: invalidClonePaths[1], Name: projectNames[0], - Source: versionsCommon.DevfileProjectSource{ - Type: versionsCommon.DevfileProjectTypeGit, + Git: &versionsCommon.Git{ Location: projectRepos[0], }, }, @@ -119,10 +119,9 @@ func TestGetSyncFolder(t *testing.T) { name: "Case 7: Invalid clone path, contains ..", projects: []versionsCommon.DevfileProject{ { - ClonePath: &invalidClonePaths[2], + ClonePath: invalidClonePaths[2], Name: projectNames[0], - Source: versionsCommon.DevfileProjectSource{ - Type: versionsCommon.DevfileProjectTypeGit, + Zip: &versionsCommon.Zip{ Location: projectRepos[0], }, }, @@ -208,7 +207,7 @@ func TestGetCmdToDeleteFiles(t *testing.T) { func TestSyncFiles(t *testing.T) { testComponentName := "test" - componentType := versionsCommon.DevfileComponentTypeDockerimage + componentType := versionsCommon.ContainerComponentType fakeClient := lclient.FakeNew() fakeErrorClient := lclient.FakeErrorNew() @@ -308,7 +307,11 @@ func TestSyncFiles(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := parser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: componentType, + Components: []versionsCommon.DevfileComponent{ + { + Type: componentType, + }, + }, }, } @@ -337,7 +340,7 @@ func TestSyncFiles(t *testing.T) { func TestPushLocal(t *testing.T) { testComponentName := "test" - componentType := versionsCommon.DevfileComponentTypeDockerimage + componentType := versionsCommon.ContainerComponentType // create a temp dir for the file indexer directory, err := ioutil.TempDir("", "") @@ -429,7 +432,11 @@ func TestPushLocal(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := parser.DevfileObj{ Data: testingutil.TestDevfileData{ - ComponentType: componentType, + Components: []versionsCommon.DevfileComponent{ + { + Type: componentType, + }, + }, }, } diff --git a/pkg/testingutil/devfile.go b/pkg/testingutil/devfile.go index e874e883fdc..c9b7d9d14bb 100644 --- a/pkg/testingutil/devfile.go +++ b/pkg/testingutil/devfile.go @@ -18,17 +18,17 @@ func (d TestDevfileData) GetComponents() []versionsCommon.DevfileComponent { // GetEvents is a mock function to get events from devfile func (d TestDevfileData) GetEvents() versionsCommon.DevfileEvents { - return d.GetEvents() + return versionsCommon.DevfileEvents{} } // GetMetadata is a mock function to get metadata from devfile func (d TestDevfileData) GetMetadata() versionsCommon.DevfileMetadata { - return d.GetMetadata() + return versionsCommon.DevfileMetadata{} } // GetParent is a mock function to get parent from devfile func (d TestDevfileData) GetParent() versionsCommon.DevfileParent { - return d.GetParent() + return versionsCommon.DevfileParent{} } // GetAliasedComponents is a mock function to get the components that have an alias from a devfile @@ -110,3 +110,16 @@ func GetFakeComponent(name string) versionsCommon.DevfileComponent { }} } + +func GetFakeExecRunCommands() []versionsCommon.Exec { + return []versionsCommon.Exec{ + { + CommandLine: "ls -a", + Component: "alias1", + Group: &versionsCommon.Group{ + Kind: versionsCommon.RunCommandGroupType, + }, + WorkingDir: "/root", + }, + } +} From 935b4096754a4bb0f65934db38de7872b88ddeb0 Mon Sep 17 00:00:00 2001 From: Aditi Sharma Date: Wed, 27 May 2020 16:52:24 +0530 Subject: [PATCH 17/23] Add devfiles V2 examples (#3253) Add devfiles v2 examples for springboot and nodejs --- .../nodejs/devfile.yaml} | 0 .../source/devfilesV2/springboot/devfile.yaml | 56 +++++++++++++++++++ 2 files changed, 56 insertions(+) rename tests/examples/source/{devfiles/nodejs/devfileV2.yaml => devfilesV2/nodejs/devfile.yaml} (100%) create mode 100644 tests/examples/source/devfilesV2/springboot/devfile.yaml diff --git a/tests/examples/source/devfiles/nodejs/devfileV2.yaml b/tests/examples/source/devfilesV2/nodejs/devfile.yaml similarity index 100% rename from tests/examples/source/devfiles/nodejs/devfileV2.yaml rename to tests/examples/source/devfilesV2/nodejs/devfile.yaml diff --git a/tests/examples/source/devfilesV2/springboot/devfile.yaml b/tests/examples/source/devfilesV2/springboot/devfile.yaml new file mode 100644 index 00000000000..5d09244049e --- /dev/null +++ b/tests/examples/source/devfilesV2/springboot/devfile.yaml @@ -0,0 +1,56 @@ +schemaVersion: 2.0.0 +metadata: + name: java-spring-boot + version: 1.0.0 +projects: + - name: springbootproject + git: + location: "https://github.com/odo-devfiles/springboot-ex.git" +components: + - chePlugin: + id: redhat/java/latest + memoryLimit: 1512Mi + - container: + name: tools + image: maysunfaisal/springbootbuild + memoryLimit: 768Mi + command: ['tail'] + args: [ '-f', '/dev/null'] + mountSources: true + volumeMounts: + - name: springbootpvc + path: /data + - container: + name: runtime + image: maysunfaisal/springbootruntime + memoryLimit: 768Mi + command: ['tail'] + args: [ '-f', '/dev/null'] + endpoints: + - name: '8080/tcp' + targetPort: 8080 + configuration: + discoverable: false + public: true + protocol: http + mountSources: false + volumeMounts: + - name: springbootpvc + path: /data +commands: + - exec: + id: devBuild + component: tools + commandLine: "/artifacts/bin/build-container-full.sh" + workingDir: /projects/springbootproject + group: + kind: build + isDefault: true + - exec: + id: devRun + component: runtime + commandLine: "/artifacts/bin/start-server.sh" + workingDir: / + group: + kind: run + isDefault: true From e558170965dc6adb6ee8e198bf4eff562b8cea41 Mon Sep 17 00:00:00 2001 From: Aditi Sharma Date: Thu, 28 May 2020 16:22:16 +0530 Subject: [PATCH 18/23] Fix regression by sparse checkout dir PR (#3258) fix regression caused by rebase to master. Also add github, zip as supported project types. --- pkg/devfile/parser/data/1.0.0/components.go | 49 ++++++++++++++------- pkg/devfile/parser/data/1.0.0/types.go | 4 +- pkg/odo/cli/component/create.go | 18 +++----- 3 files changed, 44 insertions(+), 27 deletions(-) diff --git a/pkg/devfile/parser/data/1.0.0/components.go b/pkg/devfile/parser/data/1.0.0/components.go index 2dac81c3218..4cda764dd81 100644 --- a/pkg/devfile/parser/data/1.0.0/components.go +++ b/pkg/devfile/parser/data/1.0.0/components.go @@ -47,10 +47,8 @@ func (d *Devfile100) GetProjects() []common.DevfileProject { var projects []common.DevfileProject for _, v := range d.Projects { - // We are only supporting ProjectType git in V1 - if v.Source.Type == ProjectTypeGit { - projects = append(projects, convertV1ProjectToCommon(v)) - } + projects = append(projects, convertV1ProjectToCommon(v)) + } return projects @@ -166,21 +164,42 @@ func convertV1VolumeToCommon(v DockerimageVolume) common.VolumeMount { } func convertV1ProjectToCommon(p Project) common.DevfileProject { + var project = common.DevfileProject{ + ClonePath: p.ClonePath, + Name: p.Name, + } + + switch p.Source.Type { + case ProjectTypeGit: + git := common.Git{ + Branch: p.Source.Branch, + Location: p.Source.Location, + SparseCheckoutDir: p.Source.SparseCheckoutDir, + StartPoint: p.Source.StartPoint, + } - git := common.Git{ - Branch: p.Source.Branch, - Location: p.Source.Location, - SparseCheckoutDir: p.Source.SparseCheckoutDir, - StartPoint: p.Source.StartPoint, - } + project.Git = &git + + case ProjectTypeGitHub: + github := common.Github{ + Branch: p.Source.Branch, + Location: p.Source.Location, + SparseCheckoutDir: p.Source.SparseCheckoutDir, + StartPoint: p.Source.StartPoint, + } + project.Github = &github + + case ProjectTypeZip: + zip := common.Zip{ + Location: p.Source.Location, + SparseCheckoutDir: p.Source.SparseCheckoutDir, + } + project.Zip = &zip - return common.DevfileProject{ - ClonePath: p.ClonePath, - Git: &git, - Name: p.Name, - SourceType: common.GitProjectSourceType, } + return project + } func getGroup(name string) *common.Group { diff --git a/pkg/devfile/parser/data/1.0.0/types.go b/pkg/devfile/parser/data/1.0.0/types.go index 5f59003ab6e..d18358ebbe3 100644 --- a/pkg/devfile/parser/data/1.0.0/types.go +++ b/pkg/devfile/parser/data/1.0.0/types.go @@ -26,7 +26,9 @@ type Devfile100 struct { type ProjectType string const ( - ProjectTypeGit ProjectType = "git" + ProjectTypeGit ProjectType = "git" + ProjectTypeGitHub ProjectType = "github" + ProjectTypeZip ProjectType = "zip" ) var SupportedProjectTypes = []ProjectType{ProjectTypeGit} diff --git a/pkg/odo/cli/component/create.go b/pkg/odo/cli/component/create.go index 49011ba921b..4ef2188ed33 100644 --- a/pkg/odo/cli/component/create.go +++ b/pkg/odo/cli/component/create.go @@ -770,13 +770,14 @@ func (co *CreateOptions) downloadProject(projectPassed string) error { return err } - var url string + var url, sparseDir string if project.Git != nil { if strings.Contains(project.Git.Location, "github.com") { url, err = util.GetGitHubZipURL(project.Git.Location) if err != nil { return err } + sparseDir = project.Git.SparseCheckoutDir } else { return errors.Errorf("Project type git with non github url not supported") } @@ -785,13 +786,15 @@ func (co *CreateOptions) downloadProject(projectPassed string) error { if err != nil { return err } + sparseDir = project.Github.SparseCheckoutDir } else if project.Zip != nil { url = project.Zip.Location + sparseDir = project.Github.SparseCheckoutDir } else { return errors.Errorf("Project type not supported") } - err = checkoutProject(project, url, path) + err = checkoutProject(sparseDir, url, path) if err != nil { return err @@ -904,15 +907,8 @@ func ensureAndLogProperResourceUsage(resource, resourceMin, resourceMax, resourc } } -func checkoutProject(project common.DevfileProject, zipURL, path string) error { - var sparseCheckoutDir string - if project.Git.SparseCheckoutDir != "" { - sparseCheckoutDir = project.Git.SparseCheckoutDir - } else if project.Github.SparseCheckoutDir != "" { - sparseCheckoutDir = project.Github.SparseCheckoutDir - } else if project.Zip.SparseCheckoutDir != "" { - sparseCheckoutDir = project.Zip.SparseCheckoutDir - } +func checkoutProject(sparseCheckoutDir, zipURL, path string) error { + if sparseCheckoutDir != "" { err := util.GetAndExtractZip(zipURL, path, sparseCheckoutDir) if err != nil { From bf8fed0a7da626c2cb3c66411923c4088a55ad6e Mon Sep 17 00:00:00 2001 From: Aditi Sharma Date: Fri, 29 May 2020 14:17:31 +0530 Subject: [PATCH 19/23] Address review comments (#3267) --- pkg/devfile/adapters/common/command.go | 15 ++++++---- pkg/devfile/adapters/common/types.go | 2 +- .../adapters/kubernetes/component/adapter.go | 3 +- pkg/devfile/parser/data/common/types.go | 29 ++++++++++--------- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/pkg/devfile/adapters/common/command.go b/pkg/devfile/adapters/common/command.go index a6020be98fa..9a2b08ce85d 100644 --- a/pkg/devfile/adapters/common/command.go +++ b/pkg/devfile/adapters/common/command.go @@ -12,7 +12,9 @@ import ( // GetCommand iterates through the devfile commands and returns the associated devfile command func getCommand(data data.DevfileData, commandName string, groupType common.DevfileCommandGroupType) (supportedCommand common.DevfileCommand, err error) { - for _, command := range data.GetCommands() { + commands := data.GetCommands() + + for _, command := range commands { command = updateGroupforCustomCommand(commandName, groupType, command) @@ -43,7 +45,7 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf // 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) + return supportedCommand, fmt.Errorf("mismatched group kind, command %s is of group kind %v groupType in devfile", commandName, groupType) } supportedCommand = command @@ -62,7 +64,7 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf if commandName == "" { // if default command is not found return the first command found for the matching type. - for _, command := range data.GetCommands() { + for _, command := range commands { if command.Exec.Group.Kind == groupType { supportedCommand = command return supportedCommand, nil @@ -92,6 +94,7 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf // 1. command has to be of type exec // 2. component should be present // 3. command should be present +// 4. command must have group func validateCommand(data data.DevfileData, command common.DevfileCommand) (err error) { // type must be exec @@ -116,13 +119,13 @@ func validateCommand(data data.DevfileData, command common.DevfileCommand) (err // must map to a supported component components := GetSupportedComponents(data) - isActionValid := false + isComponentValid := false for _, component := range components { if command.Exec.Component == component.Container.Name { - isActionValid = true + isComponentValid = true } } - if !isActionValid { + if !isComponentValid { return fmt.Errorf("the command does not map to a supported component") } diff --git a/pkg/devfile/adapters/common/types.go b/pkg/devfile/adapters/common/types.go index 3e4a86138b6..8c24b2c8ffa 100644 --- a/pkg/devfile/adapters/common/types.go +++ b/pkg/devfile/adapters/common/types.go @@ -54,7 +54,7 @@ type ComponentInfo struct { ContainerName string } -// PushCommandsMap stores the commands to be executed as per there types. +// PushCommandsMap stores the commands to be executed as per their types. type PushCommandsMap map[common.DevfileCommandGroupType]common.DevfileCommand // NewPushCommandMap returns the instance of PushCommandsMap diff --git a/pkg/devfile/adapters/kubernetes/component/adapter.go b/pkg/devfile/adapters/kubernetes/component/adapter.go index d06f69cbab5..1a7f60aa311 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter.go @@ -315,8 +315,7 @@ func (a Adapter) execDevfile(commandsMap common.PushCommandsMap, componentExists PodName: podName, } - // Only add runinit to the expected commands if the component doesn't already exist - // This would be the case when first running the container + // only execute Init command, if it is first run of container. if !componentExists { // Get Init Command command, ok := commandsMap[versionsCommon.InitCommandGroupType] diff --git a/pkg/devfile/parser/data/common/types.go b/pkg/devfile/parser/data/common/types.go index 3249f7c6e48..5944f66c47f 100644 --- a/pkg/devfile/parser/data/common/types.go +++ b/pkg/devfile/parser/data/common/types.go @@ -1,6 +1,6 @@ package common -// ProjectSourceType describes the type of Project sources. +// DevfileProjectSourceType describes the type of Project sources. // Only one of the following project sources may be specified. type DevfileProjectSourceType string @@ -11,6 +11,8 @@ const ( CustomProjectSourceType DevfileProjectSourceType = "Custom" ) +// DevfileComponentType describes the type of component. +// Only one of the following component type may be specified type DevfileComponentType string const ( @@ -22,6 +24,8 @@ const ( CustomComponentType DevfileComponentType = "Custom" ) +// DevfileCommandType describes the type of command. +// Only one of the following command type may be specified. type DevfileCommandType string const ( @@ -32,8 +36,7 @@ const ( CustomCommandType DevfileCommandType = "Custom" ) -// CommandGroupType describes the kind of command group. -// +kubebuilder:validation:Enum=build;run;test;debug +// DevfileCommandGroupType describes the kind of command group. type DevfileCommandGroupType string const ( @@ -45,17 +48,17 @@ const ( InitCommandGroupType DevfileCommandGroupType = "init" ) -// Metadata Optional metadata +// DevfileMetadata metadata for devfile type DevfileMetadata struct { - // Optional devfile name + // Name Optional devfile name Name string `json:"name,omitempty"` - // Optional semver-compatible version + // Version Optional semver-compatible version Version string `json:"version,omitempty"` } -// CommandsItems +// DevfileCommand command specified in devfile type DevfileCommand struct { // Exec command @@ -65,7 +68,7 @@ type DevfileCommand struct { Type DevfileCommandType `json:"type,omitempty"` } -// ComponentsItems +// DevfileComponent component specified in devfile type DevfileComponent struct { // CheEditor component @@ -86,14 +89,14 @@ type DevfileComponent struct { // Openshift component Openshift *Openshift `json:"openshift,omitempty"` - // Type of project source + // Type of component Type DevfileComponentType `json:"type,omitempty"` // Volume component Volume *Volume `json:"volume,omitempty"` } -// ProjectsItems +// DevfileProject project defined in devfile type DevfileProject struct { // Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name. @@ -234,7 +237,7 @@ type Env struct { Value string `json:"value"` } -// Events Bindings of commands to events. Each command is referred-to by its name. +// DevfileEvents events Bindings of commands to events. Each command is referred-to by its name. type DevfileEvents struct { // Names of commands that should be executed after the workspace is completely started. In the case of Che-Theia, these commands should be executed after all plugins and extensions have started, including project cloning. This means that those commands are not triggered until the user opens the IDE in his browser. @@ -352,7 +355,7 @@ type Openshift struct { Url string `json:"url,omitempty"` } -// Parent Parent workspace template +// DevfileParent Parent workspace template type DevfileParent struct { // Reference to a Kubernetes CRD of type DevWorkspaceTemplate @@ -384,7 +387,7 @@ type Volume struct { Size string `json:"size,omitempty"` } -// VolumeMountsItems Volume that should be mounted to a component container +// VolumeMount Volume that should be mounted to a component container type VolumeMount struct { // The volume mount name is the name of an existing `Volume` component. If no corresponding `Volume` component exist it is implicitly added. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files. From 04838f49d070f0264044a8a6774afdb658e863ab Mon Sep 17 00:00:00 2001 From: Aditi Sharma Date: Tue, 2 Jun 2020 14:38:26 +0530 Subject: [PATCH 20/23] Address review comments part 2 fix log levels to v4 fix error formatting add case no's in test cases update some comments --- pkg/devfile/adapters/common/command.go | 31 ++++++++++---------- pkg/devfile/adapters/common/command_test.go | 30 +++++++++---------- pkg/devfile/adapters/common/utils_test.go | 10 +++---- pkg/devfile/adapters/docker/storage/utils.go | 4 +-- pkg/devfile/parser/data/1.0.0/components.go | 3 +- pkg/devfile/validate/components.go | 2 +- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/pkg/devfile/adapters/common/command.go b/pkg/devfile/adapters/common/command.go index 9a2b08ce85d..222890d2dde 100644 --- a/pkg/devfile/adapters/common/command.go +++ b/pkg/devfile/adapters/common/command.go @@ -42,11 +42,10 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf // e.g --build-command "mybuild" // exec: // id: mybuild - // group: - // kind: build + // group: + // kind: build if command.Exec.Group.Kind != groupType { - return supportedCommand, fmt.Errorf("mismatched group kind, command %s is of group kind %v groupType in devfile", commandName, groupType) - + return supportedCommand, fmt.Errorf("command group mismatched, command %s is of group %v in devfile.yaml", commandName, command.Exec.Group.Kind) } supportedCommand = command return supportedCommand, nil @@ -54,7 +53,7 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf continue } - // if not command specified via flag, default command has the highest priority + // if no command specified via flag, default command has the highest priority // We need to scan all the commands to find default command if command.Exec.Group.Kind == groupType && command.Exec.Group.IsDefault { supportedCommand = command @@ -75,9 +74,9 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf // 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) + 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) + 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) @@ -99,21 +98,21 @@ func validateCommand(data data.DevfileData, command common.DevfileCommand) (err // type must be exec if command.Exec == nil { - return fmt.Errorf("Command must be of type \"exec\"") + return fmt.Errorf("command must be of type \"exec\"") } // component must be specified if command.Exec.Component == "" { - return fmt.Errorf("Exec commands must reference a component") + return fmt.Errorf("exec commands must reference a component") } // must specify a command if command.Exec.CommandLine == "" { - return fmt.Errorf("Exec commands must have a command") + return fmt.Errorf("exec commands must have a command") } if command.Exec.Group == nil { - return fmt.Errorf("Exec commands must have group") + return fmt.Errorf("exec commands must have group") } // must map to a supported component @@ -165,11 +164,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 commandMap[common.InitCommandGroupType] = initCommand - klog.V(3).Infof("Init command: %v", initCommand.Exec.Id) + klog.V(4).Infof("Init command: %v", initCommand.Exec.Id) } buildCommand, buildCmdErr := GetBuildCommand(data, devfileBuildCmd) @@ -178,18 +177,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 commandMap[common.BuildCommandGroupType] = buildCommand - klog.V(3).Infof("Build command: %v", buildCommand.Exec.Id) + klog.V(4).Infof("Build command: %v", buildCommand.Exec.Id) } runCommand, runCmdErr := GetRunCommand(data, devfileRunCmd) if runCmdErr == nil && !reflect.DeepEqual(emptyCommand, runCommand) { isRunCommandValid = true commandMap[common.RunCommandGroupType] = runCommand - klog.V(3).Infof("Run command: %v", runCommand.Exec.Id) + 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 diff --git a/pkg/devfile/adapters/common/command_test.go b/pkg/devfile/adapters/common/command_test.go index 0a0cbefc1b0..1fb9226232c 100644 --- a/pkg/devfile/adapters/common/command_test.go +++ b/pkg/devfile/adapters/common/command_test.go @@ -350,7 +350,7 @@ func TestGetBuildCommand(t *testing.T) { wantErr bool }{ { - name: "Case: Default Build Command", + name: "Case 1: Default Build Command", commandName: emptyString, execCommands: []common.Exec{ { @@ -363,7 +363,7 @@ func TestGetBuildCommand(t *testing.T) { wantErr: false, }, { - name: "Case: Build Command passed through the odo flag", + name: "Case 2: Build Command passed through the odo flag", commandName: "flagcommand", execCommands: []common.Exec{ { @@ -384,7 +384,7 @@ func TestGetBuildCommand(t *testing.T) { wantErr: false, }, { - name: "Case: Missing Build Command", + name: "Case 3: Missing Build Command", commandName: "customcommand123", execCommands: []common.Exec{ { @@ -437,7 +437,7 @@ func TestGetRunCommand(t *testing.T) { wantErr bool }{ { - name: "Case: Default Run Command", + name: "Case 1: Default Run Command", commandName: emptyString, execCommands: []common.Exec{ { @@ -450,7 +450,7 @@ func TestGetRunCommand(t *testing.T) { wantErr: false, }, { - name: "Case: Run Command passed through odo flag", + name: "Case 2: Run Command passed through odo flag", commandName: "flagcommand", execCommands: []common.Exec{ { @@ -471,7 +471,7 @@ func TestGetRunCommand(t *testing.T) { wantErr: false, }, { - name: "Case: Missing Run Command", + name: "Case 3: Missing Run Command", commandName: "", execCommands: []common.Exec{ { @@ -567,7 +567,7 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { wantErr bool }{ { - name: "Case: Default Devfile Commands", + name: "Case 1: Default Devfile Commands", initCommand: emptyString, buildCommand: emptyString, runCommand: emptyString, @@ -576,7 +576,7 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { wantErr: false, }, { - name: "Case: Default Init and Build Command, and Provided Run Command", + name: "Case 2: Default Init and Build Command, and Provided Run Command", initCommand: emptyString, buildCommand: emptyString, runCommand: "customcommand", @@ -585,7 +585,7 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { wantErr: false, }, { - name: "Case: Empty Component", + name: "Case 3: Empty Component", initCommand: emptyString, buildCommand: "customcommand", runCommand: "customcommand", @@ -594,7 +594,7 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { wantErr: true, }, { - name: "Case: Provided Wrong Build Command and Provided Run Command", + name: "Case 4: Provided Wrong Build Command and Provided Run Command", initCommand: emptyString, buildCommand: "customcommand123", runCommand: "customcommand", @@ -603,7 +603,7 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { wantErr: true, }, { - name: "Case: Provided Wrong Init Command and Provided Build and Run Command", + name: "Case 5: Provided Wrong Init Command and Provided Build and Run Command", initCommand: "customcommand123", buildCommand: emptyString, runCommand: "customcommand", @@ -612,7 +612,7 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { wantErr: true, }, { - name: "Case: Missing Init and Build Command, and Provided Run Command", + name: "Case 6: Missing Init and Build Command, and Provided Run Command", initCommand: emptyString, buildCommand: emptyString, runCommand: "customcommand", @@ -628,7 +628,7 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { wantErr: false, }, { - name: "Case: Missing Init Command with provided Build and Run Command", + name: "Case 7: Missing Init Command with provided Build and Run Command", initCommand: emptyString, buildCommand: "build command", runCommand: "run command", @@ -651,7 +651,7 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { wantErr: false, }, { - name: "Case: Missing Build Command with provided Init and Run Command", + name: "Case 8: Missing Build Command with provided Init and Run Command", initCommand: "init command", buildCommand: emptyString, runCommand: "run command", @@ -673,7 +673,7 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { wantErr: false, }, { - name: "Case: Optional Init Command with provided Build and Run Command", + name: "Case 9: Optional Init Command with provided Build and Run Command", initCommand: "init command", buildCommand: "build command", runCommand: "run command", diff --git a/pkg/devfile/adapters/common/utils_test.go b/pkg/devfile/adapters/common/utils_test.go index 55fbbd27202..3e8b9e59ab6 100644 --- a/pkg/devfile/adapters/common/utils_test.go +++ b/pkg/devfile/adapters/common/utils_test.go @@ -19,29 +19,29 @@ func TestGetSupportedComponents(t *testing.T) { expectedMatchesCount int }{ { - name: "Case: Invalid devfile", + name: "Case 1: Invalid devfile", component: []versionsCommon.DevfileComponent{}, expectedMatchesCount: 0, }, { - name: "Case: Valid devfile with wrong component type (Openshift)", + name: "Case 2: Valid devfile with wrong component type (Openshift)", component: []versionsCommon.DevfileComponent{{Openshift: &versionsCommon.Openshift{}}}, expectedMatchesCount: 0, }, { - name: "Case: Valid devfile with wrong component type (Kubernetes)", + name: "Case 3: Valid devfile with wrong component type (Kubernetes)", component: []versionsCommon.DevfileComponent{{Kubernetes: &versionsCommon.Kubernetes{}}}, expectedMatchesCount: 0, }, { - name: "Case: Valid devfile with correct component type (Container)", + name: "Case 4 : Valid devfile with correct component type (Container)", component: []versionsCommon.DevfileComponent{testingutil.GetFakeComponent("comp1"), testingutil.GetFakeComponent("comp2")}, expectedMatchesCount: 2, }, { - name: "Case: Valid devfile with correct component type (Container) without name", + name: "Case 5: Valid devfile with correct component type (Container) without name", component: []versionsCommon.DevfileComponent{testingutil.GetFakeComponent("comp1"), testingutil.GetFakeComponent("")}, expectedMatchesCount: 1, }, diff --git a/pkg/devfile/adapters/docker/storage/utils.go b/pkg/devfile/adapters/docker/storage/utils.go index 736cd793ae2..3addf0b08a5 100644 --- a/pkg/devfile/adapters/docker/storage/utils.go +++ b/pkg/devfile/adapters/docker/storage/utils.go @@ -113,7 +113,7 @@ func ProcessVolumes(client *lclient.Client, componentName string, componentAlias processedVolumes[vol.Name] = true // Generate the volume Names - klog.V(3).Infof("Generating Docker volumes name for %v", vol.Name) + klog.V(4).Infof("Generating Docker volumes name for %v", vol.Name) generatedDockerVolName, err := GenerateVolName(vol.Name, componentName) if err != nil { return nil, nil, err @@ -125,7 +125,7 @@ func ProcessVolumes(client *lclient.Client, componentName string, componentAlias return nil, nil, err } if len(existingVolName) > 0 { - klog.V(3).Infof("Found an existing Docker volume for %v, volume %v will be re-used", vol.Name, existingVolName) + klog.V(4).Infof("Found an existing Docker volume for %v, volume %v will be re-used", vol.Name, existingVolName) generatedDockerVolName = existingVolName } diff --git a/pkg/devfile/parser/data/1.0.0/components.go b/pkg/devfile/parser/data/1.0.0/components.go index 4cda764dd81..8b4e9b7ecf6 100644 --- a/pkg/devfile/parser/data/1.0.0/components.go +++ b/pkg/devfile/parser/data/1.0.0/components.go @@ -25,7 +25,8 @@ func (d *Devfile100) GetComponents() []common.DevfileComponent { // GetAliasedComponents returns the slice of DevfileComponent objects that each have an alias func (d *Devfile100) GetAliasedComponents() []common.DevfileComponent { - // TODO(adi): we might not need this for V2 as name is a required field now. + // TODO(adi): All components are aliased for V2, this method should be removed from interface + // when we remove V1 var comps []common.DevfileComponent for _, v := range d.Components { comps = append(comps, convertV1ComponentToCommon(v)) diff --git a/pkg/devfile/validate/components.go b/pkg/devfile/validate/components.go index 8e8a362664d..1471c3347ab 100644 --- a/pkg/devfile/validate/components.go +++ b/pkg/devfile/validate/components.go @@ -20,7 +20,7 @@ func ValidateComponents(components []common.DevfileComponent) error { return fmt.Errorf(ErrorNoComponents) } - // Check weather component of type container is present + // Check if component of type container is present isContainerComponentPresent := false for _, component := range components { if component.Container != nil { From 524e70a2968b6b80208e9107b99f7cb8ff6eeb69 Mon Sep 17 00:00:00 2001 From: Aditi Sharma Date: Tue, 2 Jun 2020 15:10:48 +0530 Subject: [PATCH 21/23] Address review comments part 2 Remove validation for group --- pkg/devfile/adapters/common/command.go | 31 +++++++-------------- pkg/devfile/adapters/common/command_test.go | 4 +-- pkg/devfile/adapters/common/utils.go | 2 +- pkg/devfile/parser/data/1.0.0/components.go | 21 ++++++++------ tests/integration/devfile/utils/utils.go | 4 +-- 5 files changed, 28 insertions(+), 34 deletions(-) diff --git a/pkg/devfile/adapters/common/command.go b/pkg/devfile/adapters/common/command.go index 222890d2dde..67aa41df3c5 100644 --- a/pkg/devfile/adapters/common/command.go +++ b/pkg/devfile/adapters/common/command.go @@ -16,8 +16,6 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf for _, command := range commands { - command = updateGroupforCustomCommand(commandName, groupType, command) - // validate command err = validateCommand(data, command) @@ -30,13 +28,10 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf // if not found fallback to error. if commandName != "" { - if command.Exec.Id == commandName { + // Update Group only custom commands (specified by odo flags) + command = updateGroupforCommand(groupType, command) - if command.Exec.Group.Kind == "" { - // Devfile V1 for commands passed from flags - // Group type is not updated during conversion - command.Exec.Group.Kind = groupType - } + if command.Exec.Id == commandName { // we have found the command with name, its groupType Should match to the flag // e.g --build-command "mybuild" @@ -55,7 +50,8 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf // if no command specified via flag, default command has the highest priority // We need to scan all the commands to find default command - if command.Exec.Group.Kind == groupType && command.Exec.Group.IsDefault { + // 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 } @@ -64,11 +60,11 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf if commandName == "" { // if default command is not found return the first command found for the matching type. for _, command := range commands { - if command.Exec.Group.Kind == groupType { + + if command.Exec.Group != nil && command.Exec.Group.Kind == groupType { supportedCommand = command return supportedCommand, nil } - } } @@ -81,8 +77,7 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf if groupType == common.RunCommandGroupType { err = fmt.Errorf(msg) } else { - klog.V(3).Info(msg) - + klog.V(4).Info(msg) } } @@ -92,7 +87,6 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf // validateCommand validates the given command // 1. command has to be of type exec // 2. component should be present -// 3. command should be present // 4. command must have group func validateCommand(data data.DevfileData, command common.DevfileCommand) (err error) { @@ -111,10 +105,6 @@ func validateCommand(data data.DevfileData, command common.DevfileCommand) (err return fmt.Errorf("exec commands must have a command") } - if command.Exec.Group == nil { - return fmt.Errorf("exec commands must have group") - } - // must map to a supported component components := GetSupportedComponents(data) @@ -210,11 +200,10 @@ func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, de } // Need to update group on custom commands specified by odo flags -func updateGroupforCustomCommand(commandName string, groupType common.DevfileCommandGroupType, command common.DevfileCommand) common.DevfileCommand { +func updateGroupforCommand(groupType common.DevfileCommandGroupType, command common.DevfileCommand) common.DevfileCommand { // Update Group only for exec commands - // Update Group only custom commands (specified by odo flags) // Update Group only when Group is not nil, devfile v2 might contain group for custom commands. - if command.Exec != nil && commandName != "" && command.Exec.Group == nil { + if command.Exec != nil && command.Exec.Group == nil { command.Exec.Group = &common.Group{Kind: groupType} return command } diff --git a/pkg/devfile/adapters/common/command_test.go b/pkg/devfile/adapters/common/command_test.go index 1fb9226232c..c5ead0ed3d2 100644 --- a/pkg/devfile/adapters/common/command_test.go +++ b/pkg/devfile/adapters/common/command_test.go @@ -220,13 +220,13 @@ func TestValidateAction(t *testing.T) { wantErr: true, }, { - name: "Case: Invalid Exec Command with Group nil", + name: "Case: valid Exec Command with Group nil", exec: common.Exec{ CommandLine: command, Component: component, WorkingDir: workDir, }, - wantErr: true, + wantErr: false, }, } for _, tt := range tests { diff --git a/pkg/devfile/adapters/common/utils.go b/pkg/devfile/adapters/common/utils.go index 509796e1e9e..d6df250786c 100644 --- a/pkg/devfile/adapters/common/utils.go +++ b/pkg/devfile/adapters/common/utils.go @@ -83,7 +83,7 @@ type CommandNames struct { func isComponentSupported(component common.DevfileComponent) bool { // Currently odo only uses devfile components of type container, since most of the Che registry devfiles use it if component.Container != nil { - klog.V(3).Infof("Found component \"%v\" with name \"%v\"\n", common.ContainerComponentType, component.Container.Name) + klog.V(4).Infof("Found component \"%v\" with name \"%v\"\n", common.ContainerComponentType, component.Container.Name) return true } return false diff --git a/pkg/devfile/parser/data/1.0.0/components.go b/pkg/devfile/parser/data/1.0.0/components.go index 8b4e9b7ecf6..88c6b6e6437 100644 --- a/pkg/devfile/parser/data/1.0.0/components.go +++ b/pkg/devfile/parser/data/1.0.0/components.go @@ -204,19 +204,24 @@ func convertV1ProjectToCommon(p Project) common.DevfileProject { } func getGroup(name string) *common.Group { - group := common.Group{} switch name { case "devrun": - group.Kind = common.RunCommandGroupType - group.IsDefault = true + return &common.Group{ + Kind: common.RunCommandGroupType, + IsDefault: true, + } case "devbuild": - group.Kind = common.BuildCommandGroupType - group.IsDefault = true + return &common.Group{ + Kind: common.BuildCommandGroupType, + IsDefault: true, + } case "devinit": - group.Kind = common.InitCommandGroupType - group.IsDefault = true + return &common.Group{ + Kind: common.InitCommandGroupType, + IsDefault: true, + } } - return &group + return nil } diff --git a/tests/integration/devfile/utils/utils.go b/tests/integration/devfile/utils/utils.go index 47807e7fb0e..424626f6058 100644 --- a/tests/integration/devfile/utils/utils.go +++ b/tests/integration/devfile/utils/utils.go @@ -71,7 +71,7 @@ func ExecWithMissingRunCommand(projectDirPath, cmpName, namespace string) { args = useProjectIfAvailable(args, namespace) output := helper.CmdShouldFail("odo", args...) Expect(output).NotTo(ContainSubstring("Executing devrun command")) - Expect(output).To(ContainSubstring("The command type \"run\" is not found in the devfile")) + Expect(output).To(ContainSubstring("the command type \"run\" is not found in the devfile")) } // ExecWithCustomCommand executes odo push with a custom command @@ -107,7 +107,7 @@ func ExecWithWrongCustomCommand(projectDirPath, cmpName, namespace string) { args = useProjectIfAvailable(args, namespace) output := helper.CmdShouldFail("odo", args...) Expect(output).NotTo(ContainSubstring("Executing buildgarbage command")) - Expect(output).To(ContainSubstring("The command \"%v\" is not found in the devfile", garbageCommand)) + Expect(output).To(ContainSubstring("the command \"%v\" is not found in the devfile", garbageCommand)) } // ExecPushToTestFileChanges executes odo push with and without a file change From 5d1a072f155f92656e554532f5b94eb3fe796374 Mon Sep 17 00:00:00 2001 From: Luke Weston Date: Wed, 27 May 2020 10:46:28 +0100 Subject: [PATCH 22/23] Added PostStart Exec command functionality --- pkg/devfile/adapters/common/command.go | 35 ++++++++---- .../adapters/docker/component/adapter.go | 25 +++++++++ .../nodejs/devfileV2-with-post-start.yaml | 54 +++++++++++++++++++ .../devfile/cmd_devfile_push_test.go | 11 ++++ 4 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 tests/examples/source/devfiles/nodejs/devfileV2-with-post-start.yaml diff --git a/pkg/devfile/adapters/common/command.go b/pkg/devfile/adapters/common/command.go index 67aa41df3c5..07d50a9f50d 100644 --- a/pkg/devfile/adapters/common/command.go +++ b/pkg/devfile/adapters/common/command.go @@ -27,20 +27,31 @@ func getCommand(data data.DevfileData, commandName string, groupType common.Devf // 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) + + } - // 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("command group mismatched, command %s is of group %v in devfile.yaml", commandName, command.Exec.Group.Kind) } supportedCommand = command return supportedCommand, nil @@ -127,6 +138,10 @@ func GetInitCommand(data data.DevfileData, devfileInitCmd string) (initCommand c 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) { @@ -203,7 +218,7 @@ func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, de 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 { + if command.Exec != nil && command.Exec.Group == nil && groupType != "" { command.Exec.Group = &common.Group{Kind: groupType} return command } diff --git a/pkg/devfile/adapters/docker/component/adapter.go b/pkg/devfile/adapters/docker/component/adapter.go index 698b2e5c379..e4fa8921127 100644 --- a/pkg/devfile/adapters/docker/component/adapter.go +++ b/pkg/devfile/adapters/docker/component/adapter.go @@ -12,6 +12,7 @@ import ( "github.com/openshift/odo/pkg/devfile/adapters/common" "github.com/openshift/odo/pkg/devfile/adapters/docker/storage" "github.com/openshift/odo/pkg/devfile/adapters/docker/utils" + "github.com/openshift/odo/pkg/exec" "github.com/openshift/odo/pkg/lclient" "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/sync" @@ -118,6 +119,30 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) { return errors.Wrapf(err, "failed to sync to component with name %s", a.ComponentName) } + if !componentExists { + events := a.Devfile.Data.GetEvents() + if len(events.PostStart) > 0 { + for _, postCommand := range events.PostStart { + // Get command + command, err := common.GetCommandByName(a.Devfile.Data, postCommand) + if err != nil { + + } + // Get container for command + containerID := utils.GetContainerIDForAlias(containers, command.Exec.Component) + compInfo := common.ComponentInfo{ContainerName: containerID} + // Execute command in container + + // If composite would go here & recursive loop + + err = exec.ExecuteDevfileBuildAction(&a.Client, *command.Exec, command.Exec.Id, compInfo, false) + if err != nil { + return err + } + } + } + } + if execRequired { log.Infof("\nExecuting devfile commands for component %s", a.ComponentName) err = a.execDevfile(pushDevfileCommands, componentExists, parameters.Show, containers) diff --git a/tests/examples/source/devfiles/nodejs/devfileV2-with-post-start.yaml b/tests/examples/source/devfiles/nodejs/devfileV2-with-post-start.yaml new file mode 100644 index 00000000000..7c491c762b8 --- /dev/null +++ b/tests/examples/source/devfiles/nodejs/devfileV2-with-post-start.yaml @@ -0,0 +1,54 @@ +schemaVersion: "2.0.0" +metadata: + name: test-devfile +projects: + - name: nodejs-web-app + git: + location: "https://github.com/che-samples/web-nodejs-sample.git" +components: + - container: + image: quay.io/eclipse/che-nodejs10-ubi:nightly + mountSources: true + name: "runtime" + memoryLimit: 1024Mi + env: + - name: FOO + value: "bar" + endpoints: + - name: '3000/tcp' + targetPort: 3000 + # odo not using currently, added to validate JSON Schema + configuration: + protocol: tcp + scheme: http + type: terminal + - container: + image: quay.io/eclipse/che-nodejs10-ubi:nightly + mountSources: true + name: "tools" + memoryLimit: 1024Mi +commands: + - exec: + id: plastic + commandLine: echo I am a PostStart + component: tools + workingDir: ${CHE_PROJECTS_ROOT}/ + group: + kind: test + - exec: + id: download dependencies + commandLine: "npm install" + component: runtime + workingDir: ${CHE_PROJECTS_ROOT}/nodejs-web-app/app + group: + kind: build + - exec: + id: run the app + commandLine: "nodemon app.js" + component: runtime + workingDir: ${CHE_PROJECTS_ROOT}/nodejs-web-app/app + group: + kind: run +events: + postStart: + - "plastic" diff --git a/tests/integration/devfile/cmd_devfile_push_test.go b/tests/integration/devfile/cmd_devfile_push_test.go index 508cdcf96b5..cf36a6ac5b5 100644 --- a/tests/integration/devfile/cmd_devfile_push_test.go +++ b/tests/integration/devfile/cmd_devfile_push_test.go @@ -248,6 +248,17 @@ var _ = Describe("odo devfile push command tests", func() { }) }) + It("should execute PostStart and devrun commands if present", func() { + helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, cmpName) + + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileV2-with-post-start.yaml"), filepath.Join(context, "devfile.yaml")) + + output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--namespace", namespace) + Expect(output).To(ContainSubstring("Executing plastic command \"echo hello")) + Expect(output).To(ContainSubstring("Executing devrun command \"/artifacts/bin/start-server.sh\"")) + }) + It("should be able to handle a missing devbuild command", func() { utils.ExecWithMissingBuildCommand(context, cmpName, namespace) }) From d8d7bb364fcfed09feea841bf50be4dff80ae522 Mon Sep 17 00:00:00 2001 From: Luke Weston Date: Tue, 2 Jun 2020 18:02:52 +0100 Subject: [PATCH 23/23] minor changes to poststart test --- .../source/devfiles/nodejs/devfileV2-with-post-start.yaml | 4 ++-- tests/integration/devfile/cmd_devfile_push_test.go | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/examples/source/devfiles/nodejs/devfileV2-with-post-start.yaml b/tests/examples/source/devfiles/nodejs/devfileV2-with-post-start.yaml index 7c491c762b8..6c8df0e01ce 100644 --- a/tests/examples/source/devfiles/nodejs/devfileV2-with-post-start.yaml +++ b/tests/examples/source/devfiles/nodejs/devfileV2-with-post-start.yaml @@ -29,7 +29,7 @@ components: memoryLimit: 1024Mi commands: - exec: - id: plastic + id: myPostStart commandLine: echo I am a PostStart component: tools workingDir: ${CHE_PROJECTS_ROOT}/ @@ -51,4 +51,4 @@ commands: kind: run events: postStart: - - "plastic" + - "myPostStart" diff --git a/tests/integration/devfile/cmd_devfile_push_test.go b/tests/integration/devfile/cmd_devfile_push_test.go index cf36a6ac5b5..b10b70374d9 100644 --- a/tests/integration/devfile/cmd_devfile_push_test.go +++ b/tests/integration/devfile/cmd_devfile_push_test.go @@ -248,15 +248,14 @@ var _ = Describe("odo devfile push command tests", func() { }) }) - It("should execute PostStart and devrun commands if present", func() { + It("should execute PostStart commands if present", func() { helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, cmpName) helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileV2-with-post-start.yaml"), filepath.Join(context, "devfile.yaml")) output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--namespace", namespace) - Expect(output).To(ContainSubstring("Executing plastic command \"echo hello")) - Expect(output).To(ContainSubstring("Executing devrun command \"/artifacts/bin/start-server.sh\"")) + Expect(output).To(ContainSubstring("Executing myPostStart command \"echo hello")) }) It("should be able to handle a missing devbuild command", func() {