Skip to content

Commit

Permalink
odo deploy command (#5228)
Browse files Browse the repository at this point in the history
* Update devfile library

* deploy cmd

* Init "odo deploy" command reference

* Integration test

* Fix odo deploy

* Filter components to apply during odo push

* Output

* Remove redondant level 1 title

* Doc review

* Use server-side apply to apply Kubernetes components

* Review

* Replace PROJECT_ROOT with PROJECTS_ROOT
  • Loading branch information
feloy committed Nov 24, 2021
1 parent 7f8efcc commit ac478e9
Show file tree
Hide file tree
Showing 63 changed files with 1,990 additions and 995 deletions.
6 changes: 2 additions & 4 deletions docs/website/docs/command-reference/build-images.md
Expand Up @@ -3,8 +3,6 @@ title: odo build-images
sidebar_position: 1
---

# odo build-images

odo can build container images based on Dockerfiles, and push these images to their registries.

When running the command `odo build-images`, odo searches for all components in the `devfile.yaml` with the `image` type, for example:
Expand All @@ -15,13 +13,13 @@ components:
imageName: quay.io/myusername/myimage
dockerfile:
uri: ./Dockerfile
buildContext: ${PROJECT_ROOT}
buildContext: ${PROJECTS_ROOT}
name: component-built-from-dockerfile
```

The `uri` field indicates the relative path of the Dockerfile to use, relative to the directory containing the `devfile.yaml`. The devfile specification indicates that `uri` could also be an HTTP URL, but this case is not supported by odo yet.

The `buildContext` indicates the directory used as build context. The default value is `${PROJECT_ROOT}`.
The `buildContext` indicates the directory used as build context. The default value is `${PROJECTS_ROOT}`.

For each image component, odo executes either `podman` or `docker` (the first one found, in this order), to build the image with the specified Dockerfile, build context and arguments.

Expand Down
1 change: 0 additions & 1 deletion docs/website/docs/command-reference/catalog.md
Expand Up @@ -2,7 +2,6 @@
title: odo catalog
sidebar_position: 2
---
# odo catalog

odo uses different *catalogs* to deploy *components* and *services*.

Expand Down
2 changes: 0 additions & 2 deletions docs/website/docs/command-reference/create.md
Expand Up @@ -3,8 +3,6 @@ title: odo create
sidebar_position: 3
---

# odo create

odo uses the (_devfile_)[https://devfile.io] to store the configuration of and describe the resources like storage, services, etc. of a component. The _odo create_ command allows you to generate this file.

## Creating a component
Expand Down
67 changes: 67 additions & 0 deletions docs/website/docs/command-reference/deploy.md
@@ -0,0 +1,67 @@
---
title: odo deploy
sidebar_position: 4
---

odo can be used to deploy components in a similar manner they would be deployed by a CI/CD system,
by first building the images of the containers to deploy, then by deploying the Kubernetes resources
necessary to deploy the components.

When running the command `odo deploy`, odo searches for the default command of kind `deploy` in the devfile, and executes this command.
The kind `deploy` is supported by the devfile format starting from version 2.2.0.

The `deploy` command is typically a *composite* command, composed of several *apply* commands:
- a command referencing an `image` component that, when applied, will build the image of the container to deploy, and push it to its registry,
- a command referencing a [`kubernetes` component](https://devfile.io/docs/devfile/2.2.0/user-guide/adding-kubernetes-component-to-a-devfile.html) that, when applied, will create a Kubernetes resource in the cluster.

With the following example `devfile.yaml` file, a container image will be built by using the `Dockerfile` present in the directory,
the image will be pushed to its registry and a Kubernetes Deployment will be created in the cluster, using this freshly built image.

```
schemaVersion: 2.2.0
[...]
variables:
CONTAINER_IMAGE: quay.io/phmartin/myimage
commands:
- id: build-image
apply:
component: outerloop-build
- id: deployk8s
apply:
component: outerloop-deploy
- id: deploy
composite:
commands:
- build-image
- deployk8s
group:
kind: deploy
isDefault: true
components:
- name: outerloop-build
image:
imageName: "{{CONTAINER_IMAGE}}"
dockerfile:
uri: ./Dockerfile
buildContext: ${PROJECTS_ROOT}
- name: outerloop-deploy
kubernetes:
inlined: |
kind: Deployment
apiVersion: apps/v1
metadata:
name: my-component
spec:
replicas: 1
selector:
matchLabels:
app: node-app
template:
metadata:
labels:
app: node-app
spec:
containers:
- name: main
image: {{CONTAINER_IMAGE}}
```
1 change: 0 additions & 1 deletion docs/website/docs/command-reference/json-output.md
Expand Up @@ -2,7 +2,6 @@
title: JSON Output
sidebar_position: 100
---
# JSON Output

The `odo` commands that output some content generally accept a `-o json` flag to output this content in a JSON format, suitable for other programs to parse this output more easily.

Expand Down
3 changes: 1 addition & 2 deletions docs/website/docs/command-reference/registry.md
@@ -1,8 +1,7 @@
---
title: odo registry
sidebar_position: 4
sidebar_position: 5
---
# odo registry

odo uses the portable *devfile* format to describe the components. odo can connect to various devfile registries to download devfiles for different languages and frameworks.

Expand Down
3 changes: 1 addition & 2 deletions docs/website/docs/command-reference/service.md
@@ -1,8 +1,7 @@
---
title: odo service
sidebar_position: 5
sidebar_position: 6
---
# odo service

odo can deploy *services* with the help of *operators*.

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -6,7 +6,7 @@ require (
github.com/Netflix/go-expect v0.0.0-20201125194554-85d881c3777e
github.com/Xuanwo/go-locale v1.0.0
github.com/blang/semver v3.5.1+incompatible
github.com/devfile/api/v2 v2.0.0-20210917193329-089a48011460
github.com/devfile/api/v2 v2.0.0-20211116183836-dfec9a4d3b63
github.com/devfile/library v1.2.0
github.com/devfile/registry-support/index/generator v0.0.0-20210916150157-08b31e03fdf0
github.com/devfile/registry-support/registry-library v0.0.0-20210928163805-b0916a4f1aca
Expand Down
5 changes: 4 additions & 1 deletion go.sum
Expand Up @@ -262,8 +262,11 @@ github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/devfile/api/v2 v2.0.0-20210910153124-da620cd1a7a1/go.mod h1:kLX/nW93gigOHXK3NLeJL2fSS/sgEe+OHu8bo3aoOi4=
github.com/devfile/api/v2 v2.0.0-20210917193329-089a48011460 h1:cmd+3poyUwevcWchYdvE02YT1nQU4SJpA5/wrdLrpWE=
github.com/devfile/api/v2 v2.0.0-20210917193329-089a48011460/go.mod h1:kLX/nW93gigOHXK3NLeJL2fSS/sgEe+OHu8bo3aoOi4=
github.com/devfile/api/v2 v2.0.0-20211021164004-dabee4e633ed h1:OXF9l+MlJrirXAqKN6EZUVaHB0FKm7nh0EjpktwnBig=
github.com/devfile/api/v2 v2.0.0-20211021164004-dabee4e633ed/go.mod h1:d99eTN6QxgzihOOFyOZA+VpUyD4Q1pYRYHZ/ci9J96Q=
github.com/devfile/api/v2 v2.0.0-20211116183836-dfec9a4d3b63 h1:apVyqTxjZ2hVDwjkWpMVOiiDSMfHY6g/I167fH8T3hQ=
github.com/devfile/api/v2 v2.0.0-20211116183836-dfec9a4d3b63/go.mod h1:d99eTN6QxgzihOOFyOZA+VpUyD4Q1pYRYHZ/ci9J96Q=
github.com/devfile/library v1.1.1-0.20210910214722-7c5ff63711ec/go.mod h1:svPWwWb+BP15SXCHl0dyOeE4Sohrjl5a2BaOzc/riLc=
github.com/devfile/library v1.2.0 h1:OT1Irwg5EZhlCpsAFkjIzd3bqyzbLG0JmFPMHeE1e7M=
github.com/devfile/library v1.2.0/go.mod h1:gyiQS+ZImnM4/d+wFUl3gJmIozOSXMenl0WX8cx4zu4=
Expand Down
6 changes: 6 additions & 0 deletions pkg/devfile/adapters/common/apply.go
@@ -0,0 +1,6 @@
package common

// ApplyClient is a wrapper around ApplyComponent which runs an apply command on a component
type ApplyClient interface {
ApplyComponent(component string) error
}
4 changes: 3 additions & 1 deletion pkg/devfile/adapters/common/command.go
Expand Up @@ -39,8 +39,10 @@ func New(devfile devfilev1.Command, knowCommands map[string]devfilev1.Command, e
return newParallelCompositeCommand(components...), nil
}
return newCompositeCommand(components...), nil
} else if devfile.Exec != nil {
return newExecCommand(devfile, executor)
} else {
return newSimpleCommand(devfile, executor)
return newApplyCommand(devfile, executor)
}
}

Expand Down
27 changes: 27 additions & 0 deletions pkg/devfile/adapters/common/command_apply.go
@@ -0,0 +1,27 @@
package common

import (
devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
)

// applyCommand is a command implementation for Apply commands
type applyCommand struct {
adapter commandExecutor
id string
component string
}

// newApplyCommand creates a new applyCommand instance, adapting the devfile-defined command to run in the target component's container
func newApplyCommand(command devfilev1.Command, executor commandExecutor) (command, error) {
apply := command.Apply
return &applyCommand{
adapter: executor,
id: command.Id,
component: apply.Component,
}, nil
}

func (s applyCommand) Execute(show bool) error {
err := s.adapter.ApplyComponent(s.component)
return err
}
20 changes: 10 additions & 10 deletions pkg/devfile/adapters/common/command_simple.go
Expand Up @@ -10,8 +10,8 @@ import (
"github.com/pkg/errors"
)

// simpleCommand is a command implementation for non-composite commands
type simpleCommand struct {
// execCommand is a command implementation for non-composite commands
type execCommand struct {
info ComponentInfo
adapter commandExecutor
cmd []string // cmd represents the effective command that will be run in the container
Expand All @@ -22,9 +22,9 @@ type simpleCommand struct {
msg string
}

// newSimpleCommand creates a new simpleCommand instance, adapting the devfile-defined command to run in the target component's
// newExecCommand creates a new execCommand instance, adapting the devfile-defined command to run in the target component's
// container, modifying it to add environment variables or adapting the path as needed.
func newSimpleCommand(command devfilev1.Command, executor commandExecutor) (command, error) {
func newExecCommand(command devfilev1.Command, executor commandExecutor) (command, error) {
exe := command.Exec

// deal with environment variables
Expand All @@ -46,23 +46,23 @@ func newSimpleCommand(command devfilev1.Command, executor commandExecutor) (comm
cmd = []string{ShellExecutable, "-c", cmdLine}
}

return newOverriddenSimpleCommand(command, executor, cmd)
return newOverriddenExecCommand(command, executor, cmd)
}

// newOverriddenSimpleCommand creates a new simpleCommand albeit overriding the command specified in the devfile with the specified one
// newOverriddenExecCommand creates a new execCommand albeit overriding the command specified in the devfile with the specified one
// returning a pointer to the newly created instance so that clients can further modify it if needed.
// Note that the specified command will be run as-is in the target component's container so needs to be set accordingly as
// opposed to the implementation provided by newSimpleCommand which will take the devfile's command definition and adapt it to
// opposed to the implementation provided by newExecCommand which will take the devfile's command definition and adapt it to
// run in the container.
func newOverriddenSimpleCommand(command devfilev1.Command, executor commandExecutor, cmd []string) (*simpleCommand, error) {
func newOverriddenExecCommand(command devfilev1.Command, executor commandExecutor, cmd []string) (*execCommand, error) {
// create the component info associated with the command
info, err := executor.ComponentInfo(command)
if err != nil {
return nil, err
}

originalCmd := command.Exec.CommandLine
return &simpleCommand{
return &execCommand{
info: info,
adapter: executor,
cmd: cmd,
Expand All @@ -74,7 +74,7 @@ func newOverriddenSimpleCommand(command devfilev1.Command, executor commandExecu
}, nil
}

func (s simpleCommand) Execute(show bool) error {
func (s execCommand) Execute(show bool) error {
var spinner *log.Status
showSpinner := len(s.msg) > 0
if showSpinner {
Expand Down
4 changes: 2 additions & 2 deletions pkg/devfile/adapters/common/command_supervisor.go
Expand Up @@ -36,7 +36,7 @@ func newSupervisorInitCommand(command devfilev1.Command, adapter commandExecutor
// newSupervisorStopCommand creates a command implementation that stops the specified command via the supervisor
func newSupervisorStopCommand(command devfilev1.Command, executor commandExecutor) (command, error) {
cmd := []string{SupervisordBinaryPath, SupervisordCtlSubCommand, "stop", "all"}
if stop, err := newOverriddenSimpleCommand(command, executor, cmd); err == nil {
if stop, err := newOverriddenExecCommand(command, executor, cmd); err == nil {
// use empty spinner message to avoid showing it altogether
stop.msg = ""
return stop, err
Expand All @@ -48,7 +48,7 @@ func newSupervisorStopCommand(command devfilev1.Command, executor commandExecuto
// newSupervisorStartCommand creates a command implementation that starts the specified command via the supervisor
func newSupervisorStartCommand(command devfilev1.Command, cmd string, adapter commandExecutor, restart bool) (command, error) {
cmdLine := []string{SupervisordBinaryPath, SupervisordCtlSubCommand, "start", cmd}
start, err := newOverriddenSimpleCommand(command, adapter, cmdLine)
start, err := newOverriddenExecCommand(command, adapter, cmdLine)
if err != nil {
return nil, err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/devfile/adapters/common/executor.go
Expand Up @@ -8,6 +8,7 @@ import (
// commandExecutor defines the interface adapters must implement to be able to execute commands in a generic way
type commandExecutor interface {
ExecClient
ApplyClient
// Logger returns the MachineEventLoggingClient associated with this executor
Logger() machineoutput.MachineEventLoggingClient
// ComponentInfo retrieves the component information associated with the specified command
Expand Down
4 changes: 4 additions & 0 deletions pkg/devfile/adapters/common/generic.go
Expand Up @@ -224,3 +224,7 @@ func (a GenericAdapter) ExecDevfileEvent(events []string, eventType DevfileEvent
}
return nil
}

func (a GenericAdapter) ApplyComponent(component string) error {
return nil
}
1 change: 1 addition & 0 deletions pkg/devfile/adapters/common/interface.go
Expand Up @@ -18,4 +18,5 @@ type ComponentAdapter interface {
StartSupervisordCtlStatusWatch()
Log(follow bool, command devfilev1.Command) (io.ReadCloser, error)
Exec(command []string) error
Deploy() error
}
8 changes: 8 additions & 0 deletions pkg/devfile/adapters/kubernetes/adapter.go
Expand Up @@ -42,6 +42,10 @@ func (k Adapter) Push(parameters common.PushParameters) error {
return nil
}

func (k Adapter) Deploy() error {
return k.componentAdapter.Deploy()
}

// CheckSupervisordCommandStatus calls the component adapter's CheckSupervisordCommandStatus
func (k Adapter) CheckSupervisordCommandStatus(command devfilev1.Command) error {
err := k.componentAdapter.CheckSupervisordCommandStatus(command)
Expand Down Expand Up @@ -107,3 +111,7 @@ func (k Adapter) StartContainerStatusWatch() {
func (k Adapter) StartSupervisordCtlStatusWatch() {
k.componentAdapter.StartSupervisordCtlStatusWatch()
}

func (k Adapter) ApplyComponent(component string) error {
return k.componentAdapter.ApplyComponent(component)
}

0 comments on commit ac478e9

Please sign in to comment.