...

Source file src/github.com/redhat-developer/odo/pkg/init/backend/flags.go

Documentation: github.com/redhat-developer/odo/pkg/init/backend

     1  package backend
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"k8s.io/klog"
    11  
    12  	"github.com/redhat-developer/odo/pkg/libdevfile"
    13  	"github.com/redhat-developer/odo/pkg/registry"
    14  
    15  	"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
    16  	"github.com/devfile/api/v2/pkg/devfile"
    17  	"github.com/devfile/library/v2/pkg/devfile/parser"
    18  	"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
    19  	dfutil "github.com/devfile/library/v2/pkg/util"
    20  
    21  	"github.com/redhat-developer/odo/pkg/api"
    22  	"github.com/redhat-developer/odo/pkg/devfile/location"
    23  	"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
    24  )
    25  
    26  const (
    27  	FLAG_NAME             = "name"
    28  	FLAG_DEVFILE          = "devfile"
    29  	FLAG_DEVFILE_REGISTRY = "devfile-registry"
    30  	FLAG_STARTER          = "starter"
    31  	FLAG_DEVFILE_PATH     = "devfile-path"
    32  	FLAG_DEVFILE_VERSION  = "devfile-version"
    33  	FLAG_RUN_PORT         = "run-port"
    34  	FLAG_ARCHITECTURE     = "architecture"
    35  )
    36  
    37  // FlagsBackend is a backend that will extract all needed information from flags passed to the command
    38  type FlagsBackend struct {
    39  	registryClient registry.Client
    40  }
    41  
    42  var _ InitBackend = (*FlagsBackend)(nil)
    43  
    44  var knownArchitectures []string = []string{
    45  	string(devfile.AMD64),
    46  	string(devfile.ARM64),
    47  	string(devfile.PPC64LE),
    48  	string(devfile.S390X),
    49  }
    50  
    51  func NewFlagsBackend(registryClient registry.Client) *FlagsBackend {
    52  	return &FlagsBackend{
    53  		registryClient: registryClient,
    54  	}
    55  }
    56  
    57  func (o *FlagsBackend) Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error {
    58  	if flags[FLAG_NAME] == "" {
    59  		return errors.New("missing --name parameter: please add --name <name> to specify a name for the component")
    60  	}
    61  	if flags[FLAG_DEVFILE] == "" && flags[FLAG_DEVFILE_PATH] == "" {
    62  		return errors.New("either --devfile or --devfile-path parameter should be specified")
    63  	}
    64  	if flags[FLAG_DEVFILE] != "" && flags[FLAG_DEVFILE_PATH] != "" {
    65  		return errors.New("only one of --devfile or --devfile-path parameter should be specified")
    66  	}
    67  
    68  	registryName := flags[FLAG_DEVFILE_REGISTRY]
    69  	if registryName != "" {
    70  		registries, err := o.registryClient.GetDevfileRegistries(registryName)
    71  		if err != nil {
    72  			return err
    73  		}
    74  		if len(registries) == 0 {
    75  			//revive:disable:error-strings This is a top-level error message displayed as is to the end user
    76  			return fmt.Errorf(`Registry %q not found in the list of devfile registries.
    77  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).`,
    78  				registryName)
    79  			//revive:enable:error-strings
    80  		}
    81  		for _, r := range registries {
    82  			isGithubRegistry, err := registry.IsGithubBasedRegistry(r.URL)
    83  			if err != nil {
    84  				return err
    85  			}
    86  			if r.Name == registryName && isGithubRegistry {
    87  				return &registry.ErrGithubRegistryNotSupported{}
    88  			}
    89  		}
    90  	}
    91  
    92  	if flags[FLAG_DEVFILE_PATH] != "" && registryName != "" {
    93  		return errors.New("--devfile-registry parameter cannot be used with --devfile-path")
    94  	}
    95  
    96  	err := dfutil.ValidateK8sResourceName("name", flags[FLAG_NAME])
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	empty, err := location.DirIsEmpty(fs, dir)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	if !empty && flags[FLAG_STARTER] != "" {
   106  		return errors.New("--starter parameter cannot be used when the directory is not empty")
   107  	}
   108  
   109  	archs, err := parseStringArrayFlagValue(flags[FLAG_ARCHITECTURE])
   110  	if err != nil {
   111  		return err
   112  	}
   113  	for _, arch := range archs {
   114  		if !isKnownArch(arch) {
   115  			return fmt.Errorf("value %q is not valid for flag --architecture. Possible values are: %s", arch, strings.Join(knownArchitectures, ", "))
   116  		}
   117  	}
   118  
   119  	return nil
   120  }
   121  
   122  func isKnownArch(arch string) bool {
   123  	for _, known := range knownArchitectures {
   124  		if known == arch {
   125  			return true
   126  		}
   127  	}
   128  	return false
   129  }
   130  
   131  func (o *FlagsBackend) SelectDevfile(ctx context.Context, flags map[string]string, _ filesystem.Filesystem, _ string) (*api.DetectionResult, error) {
   132  	// This has been validated before
   133  	archs, _ := parseStringArrayFlagValue(flags[FLAG_ARCHITECTURE])
   134  	return &api.DetectionResult{
   135  		Devfile:         flags[FLAG_DEVFILE],
   136  		DevfileRegistry: flags[FLAG_DEVFILE_REGISTRY],
   137  		DevfilePath:     flags[FLAG_DEVFILE_PATH],
   138  		DevfileVersion:  flags[FLAG_DEVFILE_VERSION],
   139  		Architectures:   archs,
   140  	}, nil
   141  }
   142  
   143  func (o *FlagsBackend) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string) (*v1alpha2.StarterProject, error) {
   144  	starter := flags[FLAG_STARTER]
   145  	if starter == "" {
   146  		return nil, nil
   147  	}
   148  	projects, err := devfile.Data.GetStarterProjects(common.DevfileOptions{})
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	var prj v1alpha2.StarterProject
   153  	for _, prj = range projects {
   154  		if prj.Name == starter {
   155  			return &prj, nil
   156  		}
   157  	}
   158  	return nil, fmt.Errorf("starter project %q not found in devfile", starter)
   159  }
   160  
   161  func (o *FlagsBackend) PersonalizeName(_ parser.DevfileObj, flags map[string]string) (string, error) {
   162  	if validK8sNameErr := dfutil.ValidateK8sResourceName("name", flags[FLAG_NAME]); validK8sNameErr != nil {
   163  		return "", validK8sNameErr
   164  	}
   165  	return flags[FLAG_NAME], nil
   166  
   167  }
   168  
   169  func (o FlagsBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) (parser.DevfileObj, error) {
   170  	return devfileobj, nil
   171  }
   172  
   173  func (o FlagsBackend) HandleApplicationPorts(devfileobj parser.DevfileObj, _ []int, flags map[string]string) (parser.DevfileObj, error) {
   174  	d, err := setPortsForFlag(devfileobj, flags, FLAG_RUN_PORT)
   175  	if err != nil {
   176  		return parser.DevfileObj{}, err
   177  	}
   178  
   179  	return d, nil
   180  }
   181  
   182  func setPortsForFlag(devfileobj parser.DevfileObj, flags map[string]string, flagName string) (parser.DevfileObj, error) {
   183  	flagVal := flags[flagName]
   184  
   185  	split, err := parseStringArrayFlagValue(flagVal)
   186  	if err != nil || len(split) == 0 {
   187  		return devfileobj, nil
   188  	}
   189  
   190  	var ports []int
   191  	for _, s := range split {
   192  		var p int
   193  		p, err = strconv.Atoi(s)
   194  		if err != nil {
   195  			return parser.DevfileObj{}, fmt.Errorf("invalid value for %s (%q): %w", flagName, s, err)
   196  		}
   197  		ports = append(ports, p)
   198  	}
   199  
   200  	var kind v1alpha2.CommandGroupKind
   201  	switch flagName {
   202  	case FLAG_RUN_PORT:
   203  		kind = v1alpha2.RunCommandGroupKind
   204  	default:
   205  		return parser.DevfileObj{}, fmt.Errorf("unknown flag: %q", flagName)
   206  	}
   207  
   208  	cmd, ok, err := libdevfile.GetCommand(devfileobj, "", kind)
   209  	if err != nil {
   210  		return parser.DevfileObj{}, err
   211  	}
   212  	if !ok {
   213  		klog.V(3).Infof("Specified %s flag will not be applied - no default (or single non-default) command found for kind %v", flagName, kind)
   214  		return devfileobj, nil
   215  	}
   216  	// command must be an exec command to determine the right container component endpoints to update.
   217  	cmdType, err := common.GetCommandType(cmd)
   218  	if err != nil {
   219  		return parser.DevfileObj{}, err
   220  	}
   221  	if cmdType != v1alpha2.ExecCommandType {
   222  		return parser.DevfileObj{},
   223  			fmt.Errorf("%v cannot be used with non-exec commands. Found out that command (id: %s) for kind %v is of type %q instead",
   224  				flagName, cmd.Id, kind, cmdType)
   225  	}
   226  
   227  	cmp, ok, err := libdevfile.FindComponentByName(devfileobj.Data, cmd.Exec.Component)
   228  	if err != nil {
   229  		return parser.DevfileObj{}, err
   230  	}
   231  	if !ok {
   232  		return parser.DevfileObj{}, fmt.Errorf("component not found in Devfile for exec command %q", cmd.Id)
   233  	}
   234  	cmpType, err := common.GetComponentType(cmp)
   235  	if err != nil {
   236  		return parser.DevfileObj{}, err
   237  	}
   238  	if cmpType != v1alpha2.ContainerComponentType {
   239  		return parser.DevfileObj{},
   240  			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",
   241  				flagName, cmd.Id, kind, cmpType)
   242  	}
   243  
   244  	err = setPortsInContainerComponent(&devfileobj, &cmp, ports, false)
   245  	if err != nil {
   246  		return parser.DevfileObj{}, err
   247  	}
   248  	return devfileobj, nil
   249  }
   250  
   251  func parseStringArrayFlagValue(flagVal string) ([]string, error) {
   252  	if flagVal == "" {
   253  		return []string{}, nil
   254  	}
   255  	// Repeatable flags are formatted as "[val1,val2]"
   256  	if !(strings.HasPrefix(flagVal, "[") && strings.HasSuffix(flagVal, "]")) {
   257  		return nil, fmt.Errorf("malformed value %q", flagVal)
   258  	}
   259  	portsStr := flagVal[1 : len(flagVal)-1]
   260  	return strings.Split(portsStr, ","), nil
   261  }
   262  

View as plain text