Add Support for Devfile V2 (#3216)

* 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 <adsharma@redhat.com>

* 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

* Fixes command.go and kubernetes adapter (#3194)

Signed-off-by: mik-dass <mrinald7@gmail.com>

* Fixes utils of k8s adapter (#3195)

* Update Command Logic to use groups (#3196)

* Updates create logic to v2 (#3200)

* Fixes utils of docker docker adapter (#3198)

Signed-off-by: mik-dass <mrinald7@gmail.com>

* 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

* Updated Logs for V2 (#3208)

Fixed interface implementation for v2
Updated logs
refactored commands.go

* 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

* Update commands check

* Fixes lower and upper case for commands (#3219)

* Fixes type of project and components for devfile v1 (#3228)

* 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

* 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

* 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

* Fixes unit tests (#3240)

Signed-off-by: mik-dass <mrinald7@gmail.com>

* Add devfiles V2 examples (#3253)

Add devfiles v2 examples for springboot and nodejs

* Fix regression by sparse checkout dir PR (#3258)

fix regression caused by rebase to master.
Also add github, zip as supported project types.

* Address review comments (#3267)

* Address review comments part 2

fix log levels to v4
fix error formatting
add case no's in test cases
update some comments

* Address review comments part 2

Remove validation for group

Co-authored-by: Mrinal Das <mrinald7@gmail.com>
Co-authored-by: Jonathan West <jgwest@users.noreply.github.com>
This commit is contained in:
Aditi Sharma
2020-06-03 03:46:39 +05:30
committed by GitHub
parent 1f70c1b666
commit 5f71123d43
47 changed files with 3988 additions and 1636 deletions

View File

@@ -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.

View File

@@ -7,102 +7,115 @@ 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 {
func getCommand(data data.DevfileData, commandName string, groupType common.DevfileCommandGroupType) (supportedCommand common.DevfileCommand, err error) {
// Get the supported actions
supportedCommandActions, err := getSupportedCommandActions(data, command)
commands := data.GetCommands()
// 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))
for _, command := range commands {
// validate command
err = validateCommand(data, command)
if err != nil {
return common.DevfileCommand{}, err
}
// if command is specified via flags, it has the highest priority
// search through all commands to find the specified command name
// if not found fallback to error.
if commandName != "" {
// Update Group only custom commands (specified by odo flags)
command = updateGroupforCommand(groupType, command)
if command.Exec.Id == commandName {
// 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
}
continue
}
// The command is supported, use it
supportedCommand.Name = command.Name
supportedCommand.Actions = supportedCommandActions
supportedCommand.Attributes = command.Attributes
// if no command specified via flag, default command has the highest priority
// We need to scan all the commands to find default command
// exec.Group is a pointer, to avoid null pointer
if command.Exec.Group != nil && command.Exec.Group.Kind == groupType && command.Exec.Group.IsDefault {
supportedCommand = command
return supportedCommand, nil
}
}
// The command was not found
msg := fmt.Sprintf("The command \"%v\" was not found in the devfile", commandName)
if required {
// Not found and required, return an error
err = fmt.Errorf(msg)
} else {
// Not found and optional, so just log it
klog.V(3).Info(msg)
}
if commandName == "" {
// if default command is not found return the first command found for the matching type.
for _, command := range commands {
return
}
// getSupportedCommandActions returns the supported actions for a given command and any errors
// If some actions are supported and others have errors both the supported actions and an aggregated error will be returned.
func getSupportedCommandActions(data data.DevfileData, command common.DevfileCommand) (supportedCommandActions []common.DevfileCommandAction, err error) {
klog.V(3).Infof("Validating actions for command: %v ", command.Name)
problemMsg := ""
for i, action := range command.Actions {
// Check if the command action is of type exec
err := validateAction(data, action)
if err == nil {
klog.V(3).Infof("Action %d maps to component %v", i+1, *action.Component)
supportedCommandActions = append(supportedCommandActions, action)
} else {
problemMsg += fmt.Sprintf("Problem with command \"%v\" action #%d: %v", command.Name, i+1, err)
if command.Exec.Group != nil && command.Exec.Group.Kind == groupType {
supportedCommand = command
return supportedCommand, nil
}
}
}
if len(problemMsg) > 0 {
err = fmt.Errorf(problemMsg)
// if any command specified via flag is not found in devfile then it is an error.
if commandName != "" {
err = fmt.Errorf("the command \"%v\" is not found in the devfile", commandName)
} else {
msg := fmt.Sprintf("the command type \"%v\" is not found in the devfile", groupType)
// if run command is not found in devfile then it is an error
if groupType == common.RunCommandGroupType {
err = fmt.Errorf(msg)
} else {
klog.V(4).Info(msg)
}
}
return
}
// validateAction validates the given action
// 1. action has to be of type exec
// validateCommand validates the given command
// 1. command has to be of type exec
// 2. component should be present
// 3. command should be present
func validateAction(data data.DevfileData, action common.DevfileCommandAction) (err error) {
// 4. command must have group
func validateCommand(data data.DevfileData, command common.DevfileCommand) (err error) {
// type must be exec
if *action.Type != common.DevfileCommandTypeExec {
return fmt.Errorf("Actions must be of type \"exec\"")
if command.Exec == nil {
return fmt.Errorf("command must be of type \"exec\"")
}
// component must be specified
if action.Component == nil || *action.Component == "" {
return fmt.Errorf("Actions must reference a component")
if command.Exec.Component == "" {
return fmt.Errorf("exec commands must reference a component")
}
// must specify a command
if action.Command == nil || *action.Command == "" {
return fmt.Errorf("Actions must have a command")
if command.Exec.CommandLine == "" {
return fmt.Errorf("exec commands must have a command")
}
// must map to a supported component
components := GetSupportedComponents(data)
isActionValid := false
isComponentValid := false
for _, component := range components {
if *action.Component == *component.Alias && isComponentSupported(component) {
isActionValid = true
if command.Exec.Component == component.Container.Name {
isComponentValid = true
}
}
if !isActionValid {
return fmt.Errorf("The action does not map to a supported component")
if !isComponentValid {
return fmt.Errorf("the command does not map to a supported component")
}
return
@@ -110,37 +123,29 @@ func validateAction(data data.DevfileData, action common.DevfileCommandAction) (
// GetInitCommand iterates through the components in the devfile and returns the init command
func GetInitCommand(data data.DevfileData, devfileInitCmd string) (initCommand common.DevfileCommand, err error) {
if devfileInitCmd != "" {
// a init command was specified so if it is not found then it is an error
return getCommand(data, devfileInitCmd, true)
}
// a init command was not specified so if it is not found then it is not an error
return getCommand(data, string(DefaultDevfileInitCommand), false)
return getCommand(data, devfileInitCmd, common.InitCommandGroupType)
}
// GetBuildCommand iterates through the components in the devfile and returns the build command
func GetBuildCommand(data data.DevfileData, devfileBuildCmd string) (buildCommand common.DevfileCommand, err error) {
if devfileBuildCmd != "" {
// a build command was specified so if it is not found then it is an error
return getCommand(data, devfileBuildCmd, true)
}
// a build command was not specified so if it is not found then it is not an error
return getCommand(data, string(DefaultDevfileBuildCommand), false)
return getCommand(data, devfileBuildCmd, common.BuildCommandGroupType)
}
// GetRunCommand iterates through the components in the devfile and returns the run command
func GetRunCommand(data data.DevfileData, devfileRunCmd string) (runCommand common.DevfileCommand, err error) {
if devfileRunCmd != "" {
return getCommand(data, devfileRunCmd, true)
}
return getCommand(data, string(DefaultDevfileRunCommand), true)
return getCommand(data, devfileRunCmd, common.RunCommandGroupType)
}
// ValidateAndGetPushDevfileCommands validates the build and the run command,
// if provided through odo push or else checks the devfile for devBuild and devRun.
// It returns the build and run commands if its validated successfully, error otherwise.
func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, devfileBuildCmd, devfileRunCmd string) (pushDevfileCommands []common.DevfileCommand, err error) {
func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, devfileBuildCmd, devfileRunCmd string) (commandMap PushCommandsMap, err error) {
var emptyCommand common.DevfileCommand
commandMap = NewPushCommandMap()
isInitCommandValid, isBuildCommandValid, isRunCommandValid := false, false, false
initCommand, initCmdErr := GetInitCommand(data, devfileInitCmd)
@@ -149,11 +154,11 @@ func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, de
if isInitCmdEmpty && initCmdErr == nil {
// If there was no init command specified through odo push and no default init command in the devfile, default validate to true since the init command is optional
isInitCommandValid = true
klog.V(3).Infof("No init command was provided")
klog.V(4).Infof("No init command was provided")
} else if !isInitCmdEmpty && initCmdErr == nil {
isInitCommandValid = true
pushDevfileCommands = append(pushDevfileCommands, initCommand)
klog.V(3).Infof("Init command: %v", initCommand.Name)
commandMap[common.InitCommandGroupType] = initCommand
klog.V(4).Infof("Init command: %v", initCommand.Exec.Id)
}
buildCommand, buildCmdErr := GetBuildCommand(data, devfileBuildCmd)
@@ -162,18 +167,18 @@ func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, de
if isBuildCmdEmpty && buildCmdErr == nil {
// If there was no build command specified through odo push and no default build command in the devfile, default validate to true since the build command is optional
isBuildCommandValid = true
klog.V(3).Infof("No build command was provided")
klog.V(4).Infof("No build command was provided")
} else if !reflect.DeepEqual(emptyCommand, buildCommand) && buildCmdErr == nil {
isBuildCommandValid = true
pushDevfileCommands = append(pushDevfileCommands, buildCommand)
klog.V(3).Infof("Build command: %v", buildCommand.Name)
commandMap[common.BuildCommandGroupType] = buildCommand
klog.V(4).Infof("Build command: %v", buildCommand.Exec.Id)
}
runCommand, runCmdErr := GetRunCommand(data, devfileRunCmd)
if runCmdErr == nil && !reflect.DeepEqual(emptyCommand, runCommand) {
pushDevfileCommands = append(pushDevfileCommands, runCommand)
isRunCommandValid = true
klog.V(3).Infof("Run command: %v", runCommand.Name)
commandMap[common.RunCommandGroupType] = runCommand
klog.V(4).Infof("Run command: %v", runCommand.Exec.Id)
}
// If either command had a problem, return an empty list of commands and an error
@@ -188,8 +193,19 @@ func ValidateAndGetPushDevfileCommands(data data.DevfileData, devfileInitCmd, de
if runCmdErr != nil {
commandErrors += fmt.Sprintf(runCmdErr.Error(), "\n")
}
return []common.DevfileCommand{}, fmt.Errorf(commandErrors)
return commandMap, fmt.Errorf(commandErrors)
}
return pushDevfileCommands, nil
return commandMap, nil
}
// Need to update group on custom commands specified by odo flags
func updateGroupforCommand(groupType common.DevfileCommandGroupType, command common.DevfileCommand) common.DevfileCommand {
// Update Group only for exec commands
// Update Group only when Group is not nil, devfile v2 might contain group for custom commands.
if command.Exec != nil && command.Exec.Group == nil {
command.Exec.Group = &common.Group{Kind: groupType}
return command
}
return command
}

File diff suppressed because it is too large Load Diff

View File

@@ -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"
)
@@ -14,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
@@ -52,3 +53,11 @@ type ComponentInfo struct {
PodName string
ContainerName string
}
// PushCommandsMap stores the commands to be executed as per their types.
type PushCommandsMap map[common.DevfileCommandGroupType]common.DevfileCommand
// NewPushCommandMap returns the instance of PushCommandsMap
func NewPushCommandMap() PushCommandsMap {
return make(map[common.DevfileCommandGroupType]common.DevfileCommand)
}

View File

@@ -81,8 +81,12 @@ 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
// Currently odo only uses devfile components of type container, since most of the Che registry devfiles use it
if component.Container != nil {
klog.V(4).Infof("Found component \"%v\" with name \"%v\"\n", common.ContainerComponentType, component.Container.Name)
return true
}
return false
}
// GetBootstrapperImage returns the odo-init bootstrapper image
@@ -99,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.Alias)
components = append(components, comp)
}
}
@@ -112,14 +115,14 @@ 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 {
for _, volume := range comp.Volumes {
if len(comp.Container.VolumeMounts) != 0 {
for _, volume := range comp.Container.VolumeMounts {
vol := DevfileVolume{
Name: volume.Name,
ContainerPath: volume.ContainerPath,
Size: &size,
ContainerPath: volume.Path,
Size: size,
}
componentAliasToVolumes[*comp.Alias] = append(componentAliasToVolumes[*comp.Alias], vol)
componentAliasToVolumes[comp.Container.Name] = append(componentAliasToVolumes[comp.Container.Name], vol)
}
}
}
@@ -127,9 +130,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.Env, envVarName string) bool {
for _, envVar := range envVars {
if *envVar.Name == envVarName {
if envVar.Name == envVarName {
return true
}
}
@@ -138,9 +141,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 +155,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.

View File

@@ -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: "",
name: "Case 1: Invalid devfile",
component: []versionsCommon.DevfileComponent{},
expectedMatchesCount: 0,
},
{
name: "Case: Valid devfile with wrong component type (CheEditor)",
componentType: versionsCommon.DevfileComponentTypeCheEditor,
alias: []string{"alias1", "alias2"},
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 (ChePlugin)",
componentType: versionsCommon.DevfileComponentTypeChePlugin,
alias: []string{"alias1", "alias2"},
name: "Case 3: Valid devfile with wrong component type (Kubernetes)",
component: []versionsCommon.DevfileComponent{{Kubernetes: &versionsCommon.Kubernetes{}}},
expectedMatchesCount: 0,
},
{
name: "Case: Valid devfile with wrong component type (Kubernetes)",
componentType: versionsCommon.DevfileComponentTypeKubernetes,
alias: []string{"alias1", "alias2"},
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 (Dockerimage)",
componentType: versionsCommon.DevfileComponentTypeDockerimage,
alias: []string{"alias1", "alias2"},
name: "Case 4 : Valid devfile with correct component type (Container)",
component: []versionsCommon.DevfileComponent{testingutil.GetFakeComponent("comp1"), testingutil.GetFakeComponent("comp2")},
expectedMatchesCount: 2,
},
{
name: "Case 5: 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,
},

View File

@@ -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,
},
},
},
}

View File

@@ -72,17 +72,18 @@ 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],
Target: *vol.ContainerPath,
Source: a.volumeNameToDockerVolName[vol.Name],
Target: vol.ContainerPath,
}
dockerVolumeMounts = append(dockerVolumeMounts, volMount)
}
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,17 +121,17 @@ 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],
Target: *vol.ContainerPath,
Source: a.volumeNameToDockerVolName[vol.Name],
Target: vol.ContainerPath,
}
dockerVolumeMounts = append(dockerVolumeMounts, volMount)
}
@@ -140,7 +141,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 +156,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 +168,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 +192,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 +201,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 +235,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 +255,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 +268,15 @@ 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)
containerConfig := a.Client.GenerateContainerConfig(*comp.Image, comp.Command, comp.Args, envVars, containerLabels, ports)
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, comp.Container.Command, comp.Container.Args, 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{}
@@ -344,85 +344,63 @@ func getPortMap(context string, endpoints []versionsCommon.DockerimageEndpoint,
// 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})
}
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.Name == currentCommand.DefaultName || command.Name == 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,
}
err = exec.ExecuteDevfileBuildAction(&a.Client, action, command.Name, 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, 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)
return
}
err = exec.ExecuteDevfileRunAction(&a.Client, action, command.Name, compInfo, show)
}
}
// 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
}
}
}
// 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
}

View File

@@ -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,
},
},
},
}

View File

@@ -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,
},
},
},
}

View File

@@ -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(4).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(4).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
}
}
}

View File

@@ -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
}
}

View File

@@ -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"
@@ -18,7 +19,6 @@ import (
"github.com/openshift/odo/pkg/util"
"github.com/pkg/errors"
"k8s.io/klog"
)
const (
@@ -61,20 +61,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 +87,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 +100,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 +209,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 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.Env, adaptersCommon.EnvOdoCommandRun) {
envName := adaptersCommon.EnvOdoCommandRun
envValue := *action.Command
comp.Env = append(comp.Env, common.DockerimageEnv{
Name: &envName,
Value: &envValue,
})
}
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.Env, adaptersCommon.EnvOdoCommandRunWorkingDir) && action.Workdir != nil {
envName := adaptersCommon.EnvOdoCommandRunWorkingDir
envValue := *action.Workdir
comp.Env = append(comp.Env, common.DockerimageEnv{
Name: &envName,
Value: &envValue,
})
}
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,
})
}
}
}

View File

@@ -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{
wantEnv: []common.Env{
{
Name: &defaultWorkDirEnv,
Value: &garbageString,
Name: defaultWorkDirEnv,
Value: garbageString,
},
{
Name: &defaultCommandEnv,
Value: &garbageString,
Name: defaultCommandEnv,
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{},
},
},
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)
}
}

View File

@@ -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,
},
},
},
}

View File

@@ -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
}
}
}
@@ -305,81 +305,63 @@ 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{}
// 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})
compInfo := common.ComponentInfo{
PodName: podName,
}
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.Name == currentCommand.DefaultName || command.Name == 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"
// only execute Init command, if it is first run of container.
if !componentExists {
// 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
}
for _, action := range command.Actions {
compInfo := common.ComponentInfo{
ContainerName: *action.Component,
PodName: podName,
}
}
err = exec.ExecuteDevfileBuildAction(&a.Client, action, command.Name, 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)
// 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
}
}
for _, action := range command.Actions {
// 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(*action.Component, podName, containers)
if err != nil {
return
}
}
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)
return
}
err = exec.ExecuteDevfileRunAction(&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
}
}
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

View File

@@ -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},
}
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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, comp.Container.Command, comp.Container.Args, 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)
}
// 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,
})
}
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 containers array since the array is not a pointer to the container
containers[i] = container
// 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: runCommand.Exec.CommandLine,
})
}
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,
})
}
// 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
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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",

View File

@@ -6,17 +6,38 @@ import (
"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): 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))
}
var aliasedComponents = []common.DevfileComponent{}
for _, comp := range d.Components {
if comp.Alias != nil {
aliasedComponents = append(aliasedComponents, comp)
for _, comp := range comps {
if comp.Container != nil {
if comp.Container.Name != "" {
aliasedComponents = append(aliasedComponents, comp)
}
}
}
return aliasedComponents
@@ -24,17 +45,183 @@ 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 {
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 _, command := range d.Commands {
command.Name = strings.ToLower(command.Name)
commands = append(commands, command)
var commands []common.DevfileCommand
for _, v := range d.Commands {
cmd := convertV1CommandToCommon(v)
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
name := strings.ToLower(c.Name)
for _, action := range c.Actions {
if action.Type == DevfileCommandTypeExec {
exec = common.Exec{
Attributes: c.Attributes,
CommandLine: action.Command,
Component: action.Component,
Group: getGroup(name),
Id: name,
WorkingDir: action.Workdir,
// Env:
// Label:
}
}
}
// TODO: Previewurl
return common.DevfileCommand{
//TODO(adi): Type
Exec: &exec,
}
}
func convertV1ComponentToCommon(c Component) (component 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,
Command: c.Command,
Args: c.Args,
}
component = common.DevfileComponent{Container: &container}
return component
}
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 {
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,
}
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 project
}
func getGroup(name string) *common.Group {
switch name {
case "devrun":
return &common.Group{
Kind: common.RunCommandGroupType,
IsDefault: true,
}
case "devbuild":
return &common.Group{
Kind: common.BuildCommandGroupType,
IsDefault: true,
}
case "devinit":
return &common.Group{
Kind: common.InitCommandGroupType,
IsDefault: true,
}
}
return nil
}

View File

@@ -1,26 +1,233 @@
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"
ProjectTypeGitHub ProjectType = "github"
ProjectTypeZip ProjectType = "zip"
)
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"`
}

View File

@@ -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
}
// GetProjects returns the DevfileProject Object parsed from devfile
func (d *Devfile200) GetProjects() []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
}

View File

@@ -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"
]
}`

View File

@@ -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 ComponentType `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"`
}

View File

@@ -1,210 +1,452 @@
package common
// -------------- Supported devfile project types ------------ //
// DevfileProjectType store valid devfile project types
type DevfileProjectType string
// DevfileProjectSourceType 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
// DevfileComponentType describes the type of component.
// Only one of the following component type may be specified
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 ------------ //
// DevfileCommandType describes the type of command.
// Only one of the following command type may be specified.
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
// DevfileCommandGroupType describes the kind of command group.
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"
)
// DevfileMetadata metadata for devfile
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"`
// Name Optional devfile name
Name string `json:"name,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 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"`
// The Project Name
Name string `yaml:"name" json:"name"`
// Describes the project's source - type and location
Source DevfileProjectSource `yaml:"source" json:"source"`
}
type DevfileProjectSource struct {
Type DevfileProjectType `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"`
// Version Optional semver-compatible version
Version string `json:"version,omitempty"`
}
// DevfileCommand command specified in devfile
type DevfileCommand 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 []DevfileCommandAction `yaml:"actions" json:"actions"`
// Exec command
Exec *Exec `json:"exec,omitempty"`
// 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 DevfileCommandPreviewUrl `yaml:"previewUrl,omitempty" json:"previewUrl,omitempty"`
}
type DevfileCommandPreviewUrl struct {
Port *int32 `yaml:"port,omitempty" json:"port,omitempty"`
Path *string `yaml:"path,omitempty" json:"path,omitempty"`
}
type DevfileCommandAction 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 *DevfileCommandType `yaml:"type,omitempty" json:"type,omitempty"`
// Working directory where the command should be executed
Workdir *string `yaml:"workdir,omitempty" json:"workdir,omitempty"`
// Type of workspace command
Type DevfileCommandType `json:"type,omitempty"`
}
// DevfileComponent component specified in devfile
type DevfileComponent 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"`
// CheEditor component
CheEditor *CheEditor `json:"cheEditor,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"`
// ChePlugin component
ChePlugin *ChePlugin `json:"chePlugin,omitempty"`
// Describes type of the component, e.g. whether it is an plugin or editor or
// other type
Type DevfileComponentType `yaml:"type" json:"type"`
// Container component
Container *Container `json:"container,omitempty"`
// for type ChePlugin
DevfileComponentChePlugin `yaml:",inline" json:",inline"`
// Custom component
Custom *Custom `json:"custom,omitempty"`
// for type=dockerfile
DevfileComponentDockerimage `yaml:",inline" json:",inline"`
// Kubernetes component
Kubernetes *Kubernetes `json:"kubernetes,omitempty"`
// Openshift component
Openshift *Openshift `json:"openshift,omitempty"`
// Type of component
Type DevfileComponentType `json:"type,omitempty"`
// Volume component
Volume *Volume `json:"volume,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"`
// 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.
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 DevfileProjectSourceType `json:"sourceType,omitempty"`
// Project's Zip source
Zip *Zip `json:"zip,omitempty"`
}
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"`
// 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"`
}
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"`
// 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"`
}
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"`
// 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"`
}
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"`
// 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"`
}
type DockerimageVolume struct {
Name *string `yaml:"name,omitempty" json:"name,omitempty"`
ContainerPath *string `yaml:"containerPath,omitempty" json:"containerPath,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"`
Command []string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
}
type DockerimageEnv struct {
Name *string `yaml:"name,omitempty" json:"name,omitempty"`
Value *string `yaml:"value,omitempty" json:"value,omitempty"`
// Custom Custom component
type Custom struct {
ComponentClass string `json:"componentClass"`
EmbeddedResource *EmbeddedResource `json:"embeddedResource"`
Name string `json:"name"`
}
type DockerimageEndpoint struct {
Name *string `yaml:"name,omitempty" json:"name,omitempty"`
Port *int32 `yaml:"port,omitempty" json:"port,omitempty"`
// 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"`
}
// 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.
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 DevfileCommandGroupType `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"`
}
// DevfileParent 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"`
}
// 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"`
}
// 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.
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"`
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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,
},
},
}

View File

@@ -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 if component of type container is present
isContainerComponentPresent := false
for _, component := range components {
if component.Type == common.DevfileComponentTypeDockerimage {
isDockerImageComponentPresent = true
if component.Container != nil {
isContainerComponentPresent = true
break
}
}
if !isDockerImageComponentPresent {
return fmt.Errorf(ErrorNoDockerImageComponent)
if !isContainerComponentPresent {
return fmt.Errorf(ErrorNoContainerComponent)
}
// Successful

View File

@@ -23,11 +23,14 @@ 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{
{
Type: common.DevfileComponentTypeDockerimage,
Container: &common.Container{
Name: "container",
},
Type: common.ContainerComponentType,
},
}
@@ -38,19 +41,19 @@ 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{
{
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)

View File

@@ -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)
// Validate Components
if err := ValidateComponents(d.Components); err != nil {
return err
}
// Successful
klog.V(4).Info("Successfully validated devfile sections")
return nil
components = d.GetComponents()
}
if typeData == reflect.TypeOf(&v200.Devfile200{}) {
d := data.(*v200.Devfile200)
components = d.GetComponents()
}
// Validate Components
if err := ValidateComponents(components); err != nil {
return err
}
// Successful
klog.V(4).Info("Successfully validated devfile sections")
return nil
}

View File

@@ -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 != "" {
// 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)

View File

@@ -39,10 +39,10 @@ func GenerateContainer(name, image string, isPrivileged bool, command, args []st
Image: image,
ImagePullPolicy: corev1.PullAlways,
Resources: resourceReqs,
Command: command,
Args: args,
Env: envVars,
Ports: ports,
Command: command,
Args: args,
}
if isPrivileged {

View File

@@ -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)
}
}
}

View File

@@ -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++
}

View File

@@ -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)
}
@@ -770,29 +770,32 @@ 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, 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")
}
case "github":
zipUrl, err = util.GetGitHubZipURL(project.Source.Location)
} else if project.Github != nil {
url, err = util.GetGitHubZipURL(project.Github.Location)
if err != nil {
return err
}
case "zip":
zipUrl = project.Source.Location
default:
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, zipUrl, path)
err = checkoutProject(sparseDir, url, path)
if err != nil {
return err
}
@@ -904,9 +907,9 @@ 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
func checkoutProject(sparseCheckoutDir, zipURL, path string) error {
if sparseCheckoutDir != "" {
err := util.GetAndExtractZip(zipURL, path, sparseCheckoutDir)
if err != nil {
return errors.Wrap(err, "failed to download and extract project zip folder")

View File

@@ -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
}

View File

@@ -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,
},
},
},
}

View File

@@ -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 versionsCommon.DevfileEvents{}
}
// GetMetadata is a mock function to get metadata from devfile
func (d TestDevfileData) GetMetadata() versionsCommon.DevfileMetadata {
return versionsCommon.DevfileMetadata{}
}
// GetParent is a mock function to get parent from devfile
func (d TestDevfileData) GetParent() versionsCommon.DevfileParent {
return versionsCommon.DevfileParent{}
}
// 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,75 @@ 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],
},
},
{
ClonePath: &clonePath[1],
Name: projectName[1],
Source: versionsCommon.DevfileProjectSource{
Type: versionsCommon.DevfileProjectTypeGit,
Location: sourceLocation[1],
},
project1 := versionsCommon.DevfileProject{
ClonePath: clonePath[0],
Name: projectName[0],
Git: &versionsCommon.Git{
Location: sourceLocation[0],
},
}
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,
}}
}
func GetFakeExecRunCommands() []versionsCommon.Exec {
return []versionsCommon.Exec{
{
CommandLine: "ls -a",
Component: "alias1",
Group: &versionsCommon.Group{
Kind: versionsCommon.RunCommandGroupType,
},
WorkingDir: "/root",
},
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

1
vendor/k8s.io/api/go.sum generated vendored
View File

@@ -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=