Files
odo/pkg/init/backend/flags.go
Philippe Martin d41364e68e odo init filters devfile stacks by supported architectures (#7004)
* Add --architecture flag

* Ask architecture during interactive mode

* Display architectures of detected Devfile

* Fix integration tests

* Fix automated doc

* Fix e2e tests

* Ignore empty lines on doc automation tests

* Update pkg/odo/cli/registry/registry.go

Co-authored-by: Armel Soro <armel@rm3l.org>

* Fix Architectures field in API

* Change "select architectures" prompt

---------

Co-authored-by: Armel Soro <armel@rm3l.org>
2023-08-01 19:36:48 +02:00

262 lines
7.9 KiB
Go

package backend
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"
"github.com/devfile/api/v2/pkg/devfile"
"github.com/devfile/library/v2/pkg/devfile/parser"
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
dfutil "github.com/devfile/library/v2/pkg/util"
"github.com/redhat-developer/odo/pkg/api"
"github.com/redhat-developer/odo/pkg/devfile/location"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
)
const (
FLAG_NAME = "name"
FLAG_DEVFILE = "devfile"
FLAG_DEVFILE_REGISTRY = "devfile-registry"
FLAG_STARTER = "starter"
FLAG_DEVFILE_PATH = "devfile-path"
FLAG_DEVFILE_VERSION = "devfile-version"
FLAG_RUN_PORT = "run-port"
FLAG_ARCHITECTURE = "architecture"
)
// FlagsBackend is a backend that will extract all needed information from flags passed to the command
type FlagsBackend struct {
registryClient registry.Client
}
var _ InitBackend = (*FlagsBackend)(nil)
var knownArchitectures []string = []string{
string(devfile.AMD64),
string(devfile.ARM64),
string(devfile.PPC64LE),
string(devfile.S390X),
}
func NewFlagsBackend(registryClient registry.Client) *FlagsBackend {
return &FlagsBackend{
registryClient: registryClient,
}
}
func (o *FlagsBackend) Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error {
if flags[FLAG_NAME] == "" {
return errors.New("missing --name parameter: please add --name <name> to specify a name for the component")
}
if flags[FLAG_DEVFILE] == "" && flags[FLAG_DEVFILE_PATH] == "" {
return errors.New("either --devfile or --devfile-path parameter should be specified")
}
if flags[FLAG_DEVFILE] != "" && flags[FLAG_DEVFILE_PATH] != "" {
return errors.New("only one of --devfile or --devfile-path parameter should be specified")
}
registryName := flags[FLAG_DEVFILE_REGISTRY]
if registryName != "" {
registries, err := o.registryClient.GetDevfileRegistries(registryName)
if err != nil {
return err
}
if len(registries) == 0 {
//revive:disable:error-strings This is a top-level error message displayed as is to the end user
return fmt.Errorf(`Registry %q not found in the list of devfile registries.
Please use 'odo preference <add/remove> registry'' command to configure devfile registries or add an in-cluster registry (see https://devfile.io/docs/2.2.0/deploying-a-devfile-registry).`,
registryName)
//revive:enable:error-strings
}
for _, r := range registries {
isGithubRegistry, err := registry.IsGithubBasedRegistry(r.URL)
if err != nil {
return err
}
if r.Name == registryName && isGithubRegistry {
return &registry.ErrGithubRegistryNotSupported{}
}
}
}
if flags[FLAG_DEVFILE_PATH] != "" && registryName != "" {
return errors.New("--devfile-registry parameter cannot be used with --devfile-path")
}
err := dfutil.ValidateK8sResourceName("name", flags[FLAG_NAME])
if err != nil {
return err
}
empty, err := location.DirIsEmpty(fs, dir)
if err != nil {
return err
}
if !empty && flags[FLAG_STARTER] != "" {
return errors.New("--starter parameter cannot be used when the directory is not empty")
}
archs, err := parseStringArrayFlagValue(flags[FLAG_ARCHITECTURE])
if err != nil {
return err
}
for _, arch := range archs {
if !isKnownArch(arch) {
return fmt.Errorf("value %q is not valid for flag --architecture. Possible values are: %s", arch, strings.Join(knownArchitectures, ", "))
}
}
return nil
}
func isKnownArch(arch string) bool {
for _, known := range knownArchitectures {
if known == arch {
return true
}
}
return false
}
func (o *FlagsBackend) SelectDevfile(ctx context.Context, flags map[string]string, _ filesystem.Filesystem, _ string) (*api.DetectionResult, error) {
// This has been validated before
archs, _ := parseStringArrayFlagValue(flags[FLAG_ARCHITECTURE])
return &api.DetectionResult{
Devfile: flags[FLAG_DEVFILE],
DevfileRegistry: flags[FLAG_DEVFILE_REGISTRY],
DevfilePath: flags[FLAG_DEVFILE_PATH],
DevfileVersion: flags[FLAG_DEVFILE_VERSION],
Architectures: archs,
}, nil
}
func (o *FlagsBackend) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string) (*v1alpha2.StarterProject, error) {
starter := flags[FLAG_STARTER]
if starter == "" {
return nil, nil
}
projects, err := devfile.Data.GetStarterProjects(common.DevfileOptions{})
if err != nil {
return nil, err
}
var prj v1alpha2.StarterProject
for _, prj = range projects {
if prj.Name == starter {
return &prj, nil
}
}
return nil, fmt.Errorf("starter project %q not found in devfile", starter)
}
func (o *FlagsBackend) PersonalizeName(_ parser.DevfileObj, flags map[string]string) (string, error) {
if validK8sNameErr := dfutil.ValidateK8sResourceName("name", flags[FLAG_NAME]); validK8sNameErr != nil {
return "", validK8sNameErr
}
return flags[FLAG_NAME], nil
}
func (o FlagsBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) (parser.DevfileObj, error) {
return devfileobj, nil
}
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]
split, err := parseStringArrayFlagValue(flagVal)
if err != nil || len(split) == 0 {
return devfileobj, nil
}
var ports []int
for _, s := range split {
var p int
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
}
func parseStringArrayFlagValue(flagVal string) ([]string, error) {
if flagVal == "" {
return []string{}, nil
}
// Repeatable flags are formatted as "[val1,val2]"
if !(strings.HasPrefix(flagVal, "[") && strings.HasSuffix(flagVal, "]")) {
return nil, fmt.Errorf("malformed value %q", flagVal)
}
portsStr := flagVal[1 : len(flagVal)-1]
return strings.Split(portsStr, ","), nil
}