Skip to content

Commit

Permalink
Add new --run-port flag to odo init to set ports non-interactively (
Browse files Browse the repository at this point in the history
#6953)

* Add new `--run-port` flag to `odo init` to set ports non-interactively

As depicted in [1], this leverages the default (or single non-default) run command to find the linked container component.
As such, it assumes that the command found is an exec command,
and that the linked component is a container component.

[1] #6925

* Add unit and integration tests highlighting the expectations

* Document the new `--run-port` flag

* Fix some typos and language correctness issues in the `odo init` doc

* Add doc automation test for the output of `odo init --run-port`

This ensures the output and sample in the doc are kept in sync with the code base.
  • Loading branch information
rm3l authored Jul 6, 2023
1 parent f6bb4e2 commit c4b103d
Show file tree
Hide file tree
Showing 11 changed files with 926 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
```console
$ odo init --devfile go --name my-go-app --run-port 3456 --run-port 9876
__
/ \__ Initializing a new component
\__/ \
/ \__/ odo version: v3.12.0
\__/

✓ Downloading devfile "go" [48ms]

Your new component 'my-go-app' is ready in the current directory.
To start editing your component, use 'odo dev' and open this folder in your favorite IDE.
Changes will be directly reflected on the cluster.
```
34 changes: 31 additions & 3 deletions docs/website/docs/command-reference/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,20 @@ import NonEmptyDirectoryOutput from './docs-mdx/init/interactive_mode_directory_
In non-interactive mode, you will have to specify from the command-line the information needed to get a devfile.

If you want to download a devfile from a registry, you must specify the devfile name with the `--devfile` flag. The devfile with the specified name will be searched in the registries referenced (using `odo preference view`), and the first one matching will be downloaded.
If you want to download the devfile from a specific registry in the list or referenced registries, you can use the `--devfile-registry` flag to specify the name of this registry. By default odo uses official devfile registry [registry.devfile.io](https://registry.devfile.io). You can use registry's [web interface](https://registry.devfile.io/viewer) to view its content.
If you want to download a version devfile, you must specify the version with `--devfile-version` flag.
If you want to download the devfile from a specific registry in the list or referenced registries, you can use the `--devfile-registry` flag to specify the name of this registry. By default, `odo` uses the official devfile registry [registry.devfile.io](https://registry.devfile.io). You can use the registry [web interface](https://registry.devfile.io/viewer) to view its content.
If you want to download a specific version of a devfile, you can specify the version with the `--devfile-version` flag.

If you prefer to download a devfile from an URL or from the local filesystem, you can use the `--devfile-path` instead.
If you prefer to download a devfile from a URL or from the local filesystem, you can use the `--devfile-path` instead.

The `--starter` flag indicates the name of the starter project (as referenced in the selected devfile), that you want to use to start your development. To see the available starter projects for devfile stacks in the official devfile registry use its [web interface](https://registry.devfile.io/viewer) to view its content.

The required `--name` flag indicates how the component initialized by this command should be named. The name must follow the [Kubernetes naming convention](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names) and not be all-numeric.

If you know what ports your application uses, you can specify the `--run-port` flag to initialize the Devfile with the specified ports, instead of the default ones set in the registry.
The `--run-port` flag is a repeatable flag that will make `odo` read the downloaded Devfile and look for the container component referenced by the default `run` command.
It will then overwrite the container component endpoints with the ports specified.
As such, it requires the default `run` command to be an `exec` command pointing to a `container` component.

#### Fetch Devfile from any registry of the list

In this example, the devfile will be downloaded from the **StagingRegistry** registry, which is the first one in the list containing the `nodejs-react` devfile.
Expand Down Expand Up @@ -156,3 +161,26 @@ Use "latest" as the version name to fetch the latest version of a given Devfile.

</details>
:::

#### Specify the application ports


```console
odo init \
--devfile <devfile-name> \
--name <component-name> \
--run-port <port> [--run-port ANOTHER_PORT] \
[--starter STARTER]
```

In this example, `odo` will download the Devfile from the registry and overwrite the container endpoints with the ones specified in `--run-port`.
This works because the Devfile downloaded from the registry defines a default `run` command of type `exec` and referencing a `container` component.

<details>
<summary>Example</summary>

import DevfileWithRunPortOutput from './docs-mdx/init/devfile_with_run-port_output.mdx';

<DevfileWithRunPortOutput />

</details>
23 changes: 18 additions & 5 deletions pkg/init/backend/applicationports.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"strconv"

"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/v2/pkg/devfile/parser"
parsercommon "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
"k8s.io/klog"
Expand Down Expand Up @@ -40,27 +41,39 @@ func handleApplicationPorts(w io.Writer, devfileobj parser.DevfileObj, ports []i

component := components[0]

err = setPortsInContainerComponent(&devfileobj, &component, ports, true)
if err != nil {
return parser.DevfileObj{}, err
}
return devfileobj, nil
}

func setPortsInContainerComponent(devfileobj *parser.DevfileObj, component *v1alpha2.Component, ports []int, withDebug bool) error {
// Add the new ports at the beginning of the list (that is before any Debug endpoints).
// This way, application ports will be port-forwarded first.
portsToSet := make([]string, 0, len(ports))
for _, p := range ports {
portsToSet = append(portsToSet, strconv.Itoa(p))
}
debugEndpoints, err := libdevfile.GetDebugEndpointsForComponent(component)

debugEndpoints, err := libdevfile.GetDebugEndpointsForComponent(*component)
if err != nil {
return parser.DevfileObj{}, err
return err
}

// Clear the existing endpoint list
component.Container.Endpoints = nil

// Add the new application ports first
err = devfileobj.Data.SetPorts(map[string][]string{component.Name: portsToSet})
if err != nil {
return parser.DevfileObj{}, err
return err
}

// Append debug endpoints to the end of the list
component.Container.Endpoints = append(component.Container.Endpoints, debugEndpoints...)
if withDebug {
component.Container.Endpoints = append(component.Container.Endpoints, debugEndpoints...)
}

return devfileobj, nil
return nil
}
83 changes: 81 additions & 2 deletions pkg/init/backend/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import (
"context"
"errors"
"fmt"
"strconv"
"strings"

"k8s.io/klog"

"github.com/redhat-developer/odo/pkg/libdevfile"
"github.com/redhat-developer/odo/pkg/registry"

"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
Expand All @@ -24,6 +29,7 @@ const (
FLAG_STARTER = "starter"
FLAG_DEVFILE_PATH = "devfile-path"
FLAG_DEVFILE_VERSION = "devfile-version"
FLAG_RUN_PORT = "run-port"
)

// FlagsBackend is a backend that will extract all needed information from flags passed to the command
Expand Down Expand Up @@ -133,7 +139,80 @@ func (o FlagsBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) (pa
return devfileobj, nil
}

func (o FlagsBackend) HandleApplicationPorts(devfileobj parser.DevfileObj, ports []int, flags map[string]string) (parser.DevfileObj, error) {
// Currently not supported, but this will be done in a separate issue: https://github.com/redhat-developer/odo/issues/6211
func (o FlagsBackend) HandleApplicationPorts(devfileobj parser.DevfileObj, _ []int, flags map[string]string) (parser.DevfileObj, error) {
d, err := setPortsForFlag(devfileobj, flags, FLAG_RUN_PORT)
if err != nil {
return parser.DevfileObj{}, err
}

return d, nil
}

func setPortsForFlag(devfileobj parser.DevfileObj, flags map[string]string, flagName string) (parser.DevfileObj, error) {
flagVal := flags[flagName]
// Repeatable flags are formatted as "[val1,val2]"
if !(strings.HasPrefix(flagVal, "[") && strings.HasSuffix(flagVal, "]")) {
return devfileobj, nil
}
portsStr := flagVal[1 : len(flagVal)-1]

var ports []int
split := strings.Split(portsStr, ",")
for _, s := range split {
p, err := strconv.Atoi(s)
if err != nil {
return parser.DevfileObj{}, fmt.Errorf("invalid value for %s (%q): %w", flagName, s, err)
}
ports = append(ports, p)
}

var kind v1alpha2.CommandGroupKind
switch flagName {
case FLAG_RUN_PORT:
kind = v1alpha2.RunCommandGroupKind
default:
return parser.DevfileObj{}, fmt.Errorf("unknown flag: %q", flagName)
}

cmd, ok, err := libdevfile.GetCommand(devfileobj, "", kind)
if err != nil {
return parser.DevfileObj{}, err
}
if !ok {
klog.V(3).Infof("Specified %s flag will not be applied - no default (or single non-default) command found for kind %v", flagName, kind)
return devfileobj, nil
}
// command must be an exec command to determine the right container component endpoints to update.
cmdType, err := common.GetCommandType(cmd)
if err != nil {
return parser.DevfileObj{}, err
}
if cmdType != v1alpha2.ExecCommandType {
return parser.DevfileObj{},
fmt.Errorf("%v cannot be used with non-exec commands. Found out that command (id: %s) for kind %v is of type %q instead",
flagName, cmd.Id, kind, cmdType)
}

cmp, ok, err := libdevfile.FindComponentByName(devfileobj.Data, cmd.Exec.Component)
if err != nil {
return parser.DevfileObj{}, err
}
if !ok {
return parser.DevfileObj{}, fmt.Errorf("component not found in Devfile for exec command %q", cmd.Id)
}
cmpType, err := common.GetComponentType(cmp)
if err != nil {
return parser.DevfileObj{}, err
}
if cmpType != v1alpha2.ContainerComponentType {
return parser.DevfileObj{},
fmt.Errorf("%v cannot be used with non-container components. Found out that command (id: %s) for kind %v points to a compoenent of type %q instead",
flagName, cmd.Id, kind, cmpType)
}

err = setPortsInContainerComponent(&devfileobj, &cmp, ports, false)
if err != nil {
return parser.DevfileObj{}, err
}
return devfileobj, nil
}
Loading

0 comments on commit c4b103d

Please sign in to comment.