...

Source file src/github.com/redhat-developer/odo/pkg/libdevfile/libdevfile.go

Documentation: github.com/redhat-developer/odo/pkg/libdevfile

     1  package libdevfile
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"regexp"
     8  	"strings"
     9  
    10  	"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
    11  	"github.com/devfile/api/v2/pkg/validation/variables"
    12  	"github.com/devfile/library/v2/pkg/devfile/parser"
    13  	"github.com/devfile/library/v2/pkg/devfile/parser/data"
    14  	"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
    15  	devfilefs "github.com/devfile/library/v2/pkg/testingutil/filesystem"
    16  	"k8s.io/klog"
    17  
    18  	"github.com/redhat-developer/odo/pkg/util"
    19  )
    20  
    21  const DebugEndpointNamePrefix = "debug"
    22  
    23  type Handler interface {
    24  	ApplyImage(image v1alpha2.Component) error
    25  	ApplyKubernetes(kubernetes v1alpha2.Component, kind v1alpha2.CommandGroupKind) error
    26  	ApplyOpenShift(openshift v1alpha2.Component, kind v1alpha2.CommandGroupKind) error
    27  	ExecuteNonTerminatingCommand(ctx context.Context, command v1alpha2.Command) error
    28  	ExecuteTerminatingCommand(ctx context.Context, command v1alpha2.Command) error
    29  }
    30  
    31  // Deploy executes the default deploy command of the devfile.
    32  func Deploy(ctx context.Context, devfileObj parser.DevfileObj, handler Handler) error {
    33  	return ExecuteCommandByNameAndKind(ctx, devfileObj, "", v1alpha2.DeployCommandGroupKind, handler, false)
    34  }
    35  
    36  // Build executes the default Build command of the devfile.
    37  // If buildCmd is empty, this looks for the default Build command in the Devfile. No error is returned and no operation is performed
    38  // if the default command could not be found.
    39  // An error is returned if buildCmd is not empty and has no corresponding command in the Devfile.
    40  func Build(ctx context.Context, devfileObj parser.DevfileObj, buildCmd string, handler Handler) error {
    41  	return ExecuteCommandByNameAndKind(ctx, devfileObj, buildCmd, v1alpha2.BuildCommandGroupKind, handler, buildCmd == "")
    42  }
    43  
    44  // ExecuteCommandByNameAndKind executes the specified command cmdName of the given kind in the Devfile.
    45  // If cmdName is empty, it executes the default command for the given kind or returns an error if there is no default command.
    46  // If ignoreCommandNotFound is true, nothing is executed if the command is not found and no error is returned.
    47  func ExecuteCommandByNameAndKind(ctx context.Context, devfileObj parser.DevfileObj, cmdName string, kind v1alpha2.CommandGroupKind, handler Handler, ignoreCommandNotFound bool) error {
    48  	cmd, hasDefaultCmd, err := GetCommand(devfileObj, cmdName, kind)
    49  	if err != nil {
    50  		if _, isNotFound := err.(NoCommandFoundError); isNotFound {
    51  			if ignoreCommandNotFound {
    52  				klog.V(3).Infof("ignoring command not found: %v", cmdName)
    53  				return nil
    54  			}
    55  		}
    56  		return err
    57  	}
    58  	if !hasDefaultCmd {
    59  		if ignoreCommandNotFound {
    60  			klog.V(3).Infof("ignoring default %v command not found", kind)
    61  			return nil
    62  		}
    63  		return NewNoDefaultCommandFoundError(kind)
    64  	}
    65  
    66  	return executeCommand(ctx, devfileObj, cmd, handler)
    67  }
    68  
    69  // ExecuteCommandByName executes the specified command cmdName in the Devfile.
    70  // If ignoreCommandNotFound is true, nothing is executed if the command is not found and no error is returned.
    71  func ExecuteCommandByName(ctx context.Context, devfileObj parser.DevfileObj, cmdName string, handler Handler, ignoreCommandNotFound bool) error {
    72  	commands, err := devfileObj.Data.GetCommands(
    73  		common.DevfileOptions{
    74  			FilterByName: cmdName,
    75  		},
    76  	)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	if len(commands) != 1 {
    81  		return NewNoCommandFoundError("", cmdName)
    82  	}
    83  
    84  	cmd := commands[0]
    85  	return executeCommand(ctx, devfileObj, cmd, handler)
    86  }
    87  
    88  // executeCommand executes a specific command of a devfile using handler as backend
    89  func executeCommand(ctx context.Context, devfileObj parser.DevfileObj, command v1alpha2.Command, handler Handler) error {
    90  	cmd, err := newCommand(devfileObj, command)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	return cmd.Execute(ctx, handler, nil)
    95  }
    96  
    97  // GetCommand iterates through the devfile commands and returns the devfile command with the specified name and group kind.
    98  // If commandName is empty, it returns the default command for the group kind; or, if there is only one command for the specified kind, it will return that
    99  // (even if it is not marked as the default).
   100  // It returns an error if there is more than one default command.
   101  func GetCommand(
   102  	devfileObj parser.DevfileObj,
   103  	commandName string,
   104  	groupType v1alpha2.CommandGroupKind,
   105  ) (v1alpha2.Command, bool, error) {
   106  	if commandName == "" {
   107  		return getDefaultCommand(devfileObj, groupType)
   108  	}
   109  	cmdByName, err := getCommandByName(devfileObj, groupType, commandName)
   110  	if err != nil {
   111  		return v1alpha2.Command{}, false, err
   112  	}
   113  	return cmdByName, true, nil
   114  }
   115  
   116  // getDefaultCommand iterates through the devfile commands and returns the default command associated with the group kind.
   117  // If there is no default command, the second return value is false.
   118  func getDefaultCommand(devfileObj parser.DevfileObj, groupType v1alpha2.CommandGroupKind) (v1alpha2.Command, bool, error) {
   119  	commands, err := devfileObj.Data.GetCommands(common.DevfileOptions{CommandOptions: common.CommandOptions{CommandGroupKind: groupType}})
   120  	if err != nil {
   121  		return v1alpha2.Command{}, false, err
   122  	}
   123  
   124  	// if there is only one command of a given group kind, use it as default
   125  	if len(commands) == 1 {
   126  		return commands[0], true, nil
   127  	}
   128  
   129  	defaultCmds := make([]v1alpha2.Command, 0)
   130  
   131  	for _, cmd := range commands {
   132  		cmdGroup := common.GetGroup(cmd)
   133  		if cmdGroup != nil {
   134  			if cmdGroup.IsDefault != nil && *cmdGroup.IsDefault {
   135  				defaultCmds = append(defaultCmds, cmd)
   136  			}
   137  		} else {
   138  			klog.V(2).Infof("command %s has no group", cmd.Id)
   139  		}
   140  	}
   141  
   142  	if len(defaultCmds) == 0 {
   143  		return v1alpha2.Command{}, false, nil
   144  	}
   145  	if len(defaultCmds) > 1 {
   146  		return v1alpha2.Command{}, false, NewMoreThanOneDefaultCommandFoundError(groupType)
   147  	}
   148  	// #nosec
   149  	// gosec:G602 -> This is safe since we checked the length before
   150  	return defaultCmds[0], true, nil
   151  }
   152  
   153  // getCommandByName iterates through the devfile commands and returns the command with the specified name and group.
   154  // It returns an error if no command was found.
   155  func getCommandByName(devfileObj parser.DevfileObj, groupType v1alpha2.CommandGroupKind, commandName string) (v1alpha2.Command, error) {
   156  	commands, err := devfileObj.Data.GetCommands(common.DevfileOptions{CommandOptions: common.CommandOptions{CommandGroupKind: groupType}})
   157  	if err != nil {
   158  		return v1alpha2.Command{}, err
   159  	}
   160  
   161  	for _, cmd := range commands {
   162  		if cmd.Id == commandName {
   163  			return cmd, nil
   164  		}
   165  	}
   166  
   167  	return v1alpha2.Command{}, NewNoCommandFoundError(groupType, commandName)
   168  }
   169  
   170  // ValidateAndGetCommand validates and returns the command specified if it is valid.
   171  // It works just like GetCommand, except that it returns an error if it could not find the command.
   172  //
   173  // If commandName is empty, it looks up the default command for the given kind.
   174  //
   175  // A command is "valid" here if it was found given its name (if commandName is not empty),
   176  // or (for a default command), if there is no other default command for the same kind.
   177  func ValidateAndGetCommand(devfileObj parser.DevfileObj, commandName string, groupType v1alpha2.CommandGroupKind) (v1alpha2.Command, error) {
   178  	cmd, ok, err := GetCommand(devfileObj, commandName, groupType)
   179  	if err != nil {
   180  		return v1alpha2.Command{}, err
   181  	}
   182  	if !ok {
   183  		return v1alpha2.Command{}, NewNoCommandFoundError(groupType, commandName)
   184  	}
   185  	return cmd, nil
   186  }
   187  
   188  // ValidateAndGetPushCommands validates the build and the run commands, if provided through odo dev or else checks the devfile for devBuild and devRun.
   189  // It returns the build and run commands if validated successfully, or an error otherwise.
   190  func ValidateAndGetPushCommands(
   191  	devfileObj parser.DevfileObj,
   192  	devfileBuildCmd,
   193  	devfileRunCmd string,
   194  ) (map[v1alpha2.CommandGroupKind]v1alpha2.Command, error) {
   195  	var buildCmd v1alpha2.Command
   196  	var present bool
   197  	var err error
   198  
   199  	if devfileBuildCmd != "" {
   200  		buildCmd, err = ValidateAndGetCommand(devfileObj, devfileBuildCmd, v1alpha2.BuildCommandGroupKind)
   201  		present = true
   202  	} else {
   203  		buildCmd, present, err = GetCommand(devfileObj, devfileBuildCmd, v1alpha2.BuildCommandGroupKind)
   204  	}
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	commandMap := make(map[v1alpha2.CommandGroupKind]v1alpha2.Command)
   210  	if present {
   211  		klog.V(2).Infof("Build command: %v", buildCmd.Id)
   212  		commandMap[v1alpha2.BuildCommandGroupKind] = buildCmd
   213  	} else {
   214  		// Build command is optional, unless it was explicitly specified by the caller (at which point it would have been validated via ValidateAndGetCommand).
   215  		klog.V(2).Infof("No build command was provided")
   216  	}
   217  
   218  	var runCmd v1alpha2.Command
   219  	runCmd, err = ValidateAndGetCommand(devfileObj, devfileRunCmd, v1alpha2.RunCommandGroupKind)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  	klog.V(2).Infof("Run command: %v", runCmd.Id)
   224  	commandMap[v1alpha2.RunCommandGroupKind] = runCmd
   225  
   226  	return commandMap, nil
   227  }
   228  
   229  func HasPostStartEvents(devfileObj parser.DevfileObj) bool {
   230  	postStartEvents := devfileObj.Data.GetEvents().PostStart
   231  	return len(postStartEvents) > 0
   232  }
   233  
   234  func HasPreStopEvents(devfileObj parser.DevfileObj) bool {
   235  	preStopEvents := devfileObj.Data.GetEvents().PreStop
   236  	return len(preStopEvents) > 0
   237  }
   238  
   239  func ExecPostStartEvents(ctx context.Context, devfileObj parser.DevfileObj, handler Handler) error {
   240  	postStartEvents := devfileObj.Data.GetEvents().PostStart
   241  	return execDevfileEvent(ctx, devfileObj, postStartEvents, handler)
   242  }
   243  
   244  func ExecPreStopEvents(ctx context.Context, devfileObj parser.DevfileObj, handler Handler) error {
   245  	preStopEvents := devfileObj.Data.GetEvents().PreStop
   246  	return execDevfileEvent(ctx, devfileObj, preStopEvents, handler)
   247  }
   248  
   249  func hasCommand(devfileData data.DevfileData, kind v1alpha2.CommandGroupKind) bool {
   250  	commands, err := devfileData.GetCommands(common.DevfileOptions{
   251  		CommandOptions: common.CommandOptions{
   252  			CommandGroupKind: kind,
   253  		},
   254  	})
   255  	return err == nil && len(commands) > 0
   256  }
   257  
   258  func HasRunCommand(devfileData data.DevfileData) bool {
   259  	return hasCommand(devfileData, v1alpha2.RunCommandGroupKind)
   260  }
   261  
   262  func HasDeployCommand(devfileData data.DevfileData) bool {
   263  	return hasCommand(devfileData, v1alpha2.DeployCommandGroupKind)
   264  }
   265  
   266  func HasDebugCommand(devfileData data.DevfileData) bool {
   267  	return hasCommand(devfileData, v1alpha2.DebugCommandGroupKind)
   268  }
   269  
   270  // execDevfileEvent receives a Devfile Event (PostStart, PreStop etc.) and loops through them
   271  // Each Devfile Command associated with the given event is retrieved, and executed in the container specified
   272  // in the command
   273  func execDevfileEvent(ctx context.Context, devfileObj parser.DevfileObj, events []string, handler Handler) error {
   274  	if len(events) > 0 {
   275  		commandMap, err := allCommandsMap(devfileObj)
   276  		if err != nil {
   277  			return err
   278  		}
   279  		for _, commandName := range events {
   280  			command, ok := commandMap[commandName]
   281  			if !ok {
   282  				return fmt.Errorf("unable to find devfile command %q", commandName)
   283  			}
   284  
   285  			c, err := newCommand(devfileObj, command)
   286  			if err != nil {
   287  				return err
   288  			}
   289  			// Execute command in container
   290  			err = c.Execute(ctx, handler, nil)
   291  			if err != nil {
   292  				return fmt.Errorf("unable to execute devfile command %q: %w", commandName, err)
   293  			}
   294  		}
   295  	}
   296  	return nil
   297  }
   298  
   299  // GetDevfileContainerEndpointMapping returns a map of container components names and slice of its endpoints,
   300  // given a Devfile object in parameter.
   301  // Debug endpoints will be included only if includeDebug is true.
   302  func GetDevfileContainerEndpointMapping(devFileObj parser.DevfileObj, includeDebug bool) (map[string][]v1alpha2.Endpoint, error) {
   303  	// get the endpoint/port information for containers in devfile
   304  	containers, err := devFileObj.Data.GetComponents(common.DevfileOptions{
   305  		ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.ContainerComponentType},
   306  	})
   307  	if err != nil {
   308  		return nil, err
   309  	}
   310  	return GetContainerEndpointMapping(containers, includeDebug), nil
   311  }
   312  
   313  // GetContainerEndpointMapping returns a map of container names and slice of its endpoints.
   314  // Debug endpoints will be included only if includeDebug is true.
   315  func GetContainerEndpointMapping(containers []v1alpha2.Component, includeDebug bool) map[string][]v1alpha2.Endpoint {
   316  	ceMapping := make(map[string][]v1alpha2.Endpoint)
   317  	for _, container := range containers {
   318  		if container.ComponentUnion.Container == nil {
   319  			// this is not a container component; continue prevents panic when accessing Endpoints field
   320  			continue
   321  		}
   322  
   323  		var ports []v1alpha2.Endpoint
   324  		for _, e := range container.Container.Endpoints {
   325  			if !includeDebug && IsDebugEndpoint(e) {
   326  				klog.V(4).Infof("not running in Debug mode, so ignored Debug port for container %v:%v:%v",
   327  					container.Name, e.Name, e.TargetPort)
   328  				continue
   329  			}
   330  			ports = append(ports, e)
   331  		}
   332  		if len(ports) != 0 {
   333  			ceMapping[container.Name] = ports
   334  		}
   335  	}
   336  	return ceMapping
   337  }
   338  
   339  // GetEndpointsFromDevfile returns a slice of all endpoints in a devfile and ignores the endpoints with exposure values in ignoreExposures
   340  func GetEndpointsFromDevfile(devfileObj parser.DevfileObj, ignoreExposures []v1alpha2.EndpointExposure) ([]v1alpha2.Endpoint, error) {
   341  	containers, err := devfileObj.Data.GetComponents(common.DevfileOptions{
   342  		ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.ContainerComponentType},
   343  	})
   344  	if err != nil {
   345  		return nil, err
   346  	}
   347  
   348  	var allEndpoints []v1alpha2.Endpoint
   349  	for _, c := range containers {
   350  		allEndpoints = append(allEndpoints, c.Container.Endpoints...)
   351  	}
   352  
   353  	var endpoints []v1alpha2.Endpoint
   354  	for _, e := range allEndpoints {
   355  		ignore := false
   356  		for _, i := range ignoreExposures {
   357  			if e.Exposure == i {
   358  				ignore = true
   359  			}
   360  		}
   361  		if !ignore {
   362  			endpoints = append(endpoints, e)
   363  		}
   364  	}
   365  	return endpoints, nil
   366  }
   367  
   368  // GetDebugEndpointsForComponent returns all Debug endpoints for the specified component.
   369  // It returns an error if the component specified is not a container component.
   370  func GetDebugEndpointsForComponent(cmp v1alpha2.Component) ([]v1alpha2.Endpoint, error) {
   371  	if cmp.Container == nil {
   372  		return nil, fmt.Errorf("component %q is not a container component", cmp.Name)
   373  	}
   374  
   375  	var result []v1alpha2.Endpoint
   376  	for _, ep := range cmp.Container.Endpoints {
   377  		if IsDebugEndpoint(ep) {
   378  			result = append(result, ep)
   379  		}
   380  	}
   381  	return result, nil
   382  }
   383  
   384  // IsDebugEndpoint returns whether the specified endpoint represents a Debug endpoint,
   385  // based on the following naming convention: it is considered a Debug endpoint if it's named "debug" or if its name starts with "debug-".
   386  func IsDebugEndpoint(ep v1alpha2.Endpoint) bool {
   387  	return IsDebugPort(ep.Name)
   388  }
   389  
   390  // IsDebugPort returns whether the specified string represents a Debug endpoint,
   391  // based on the following naming convention: it is considered a Debug endpoint if it's named "debug" or if its name starts with "debug-".
   392  func IsDebugPort(name string) bool {
   393  	return name == DebugEndpointNamePrefix || strings.HasPrefix(name, DebugEndpointNamePrefix+"-")
   394  }
   395  
   396  // GetContainerComponentsForCommand returns the list of container components that would get used if the specified command runs.
   397  func GetContainerComponentsForCommand(devfileObj parser.DevfileObj, cmd v1alpha2.Command) ([]string, error) {
   398  	// No error if cmd is empty
   399  	if reflect.DeepEqual(cmd, v1alpha2.Command{}) {
   400  		return nil, nil
   401  	}
   402  
   403  	commandType, err := common.GetCommandType(cmd)
   404  	if err != nil {
   405  		return nil, err
   406  	}
   407  
   408  	hasComponent := func(n string) bool {
   409  		_, ok, _ := findComponentByNameAndType(devfileObj, n, v1alpha2.ContainerComponentType)
   410  		return ok
   411  	}
   412  
   413  	switch commandType {
   414  	case v1alpha2.ExecCommandType:
   415  		if hasComponent(cmd.Exec.Component) {
   416  			return []string{cmd.Exec.Component}, nil
   417  		}
   418  		return nil, nil
   419  	case v1alpha2.ApplyCommandType:
   420  		if hasComponent(cmd.Apply.Component) {
   421  			return []string{cmd.Apply.Component}, nil
   422  		}
   423  		return nil, nil
   424  	case v1alpha2.CompositeCommandType:
   425  		var commandsMap map[string]v1alpha2.Command
   426  		commandsMap, err = allCommandsMap(devfileObj)
   427  		if err != nil {
   428  			return nil, err
   429  		}
   430  
   431  		var res []string
   432  		set := make(map[string]bool)
   433  		var componentsForCommand []string
   434  		for _, c := range cmd.Composite.Commands {
   435  			fromCommandMap, present := commandsMap[strings.ToLower(c)]
   436  			if !present {
   437  				return nil, fmt.Errorf("command %q not found in all commands map", c)
   438  			}
   439  			componentsForCommand, err = GetContainerComponentsForCommand(devfileObj, fromCommandMap)
   440  			if err != nil {
   441  				return nil, err
   442  			}
   443  			for _, s := range componentsForCommand {
   444  				if _, ok := set[s]; !ok && hasComponent(s) {
   445  					set[s] = true
   446  					res = append(res, s)
   447  				}
   448  			}
   449  		}
   450  
   451  		return res, nil
   452  
   453  	default:
   454  		return nil, fmt.Errorf("type not handled for command %q: %v", cmd.Id, commandType)
   455  	}
   456  }
   457  
   458  // FindComponentByName returns the Devfile component that matches the specified name.
   459  func FindComponentByName(d data.DevfileData, n string) (v1alpha2.Component, bool, error) {
   460  	comps, err := d.GetComponents(common.DevfileOptions{})
   461  	if err != nil {
   462  		return v1alpha2.Component{}, false, err
   463  	}
   464  	for _, c := range comps {
   465  		if c.Name == n {
   466  			return c, true, nil
   467  		}
   468  	}
   469  	return v1alpha2.Component{}, false, nil
   470  }
   471  
   472  // GetK8sManifestsWithVariablesSubstituted returns the full content of either a Kubernetes or an Openshift
   473  // Devfile component, either Inlined or referenced via a URI.
   474  // No matter how the component is defined, it returns the content with all variables substituted
   475  // using the global variables map defined in `devfileObj`.
   476  // An error is returned if the content references an invalid variable key not defined in the Devfile object.
   477  func GetK8sManifestsWithVariablesSubstituted(devfileObj parser.DevfileObj, devfileCmpName string,
   478  	context string, fs devfilefs.Filesystem) (string, error) {
   479  
   480  	components, err := devfileObj.Data.GetComponents(common.DevfileOptions{FilterByName: devfileCmpName})
   481  	if err != nil {
   482  		return "", err
   483  	}
   484  
   485  	if len(components) == 0 {
   486  		return "", NewComponentNotExistError(devfileCmpName)
   487  	}
   488  
   489  	if len(components) != 1 {
   490  		return "", NewComponentsWithSameNameError(devfileCmpName)
   491  	}
   492  
   493  	devfileCmp := components[0]
   494  	componentType, err := common.GetComponentType(devfileCmp)
   495  	if err != nil {
   496  		return "", err
   497  	}
   498  
   499  	var content, uri string
   500  	switch componentType {
   501  	case v1alpha2.KubernetesComponentType:
   502  		content = devfileCmp.Kubernetes.Inlined
   503  		if devfileCmp.Kubernetes.Uri != "" {
   504  			uri = devfileCmp.Kubernetes.Uri
   505  		}
   506  
   507  	case v1alpha2.OpenshiftComponentType:
   508  		content = devfileCmp.Openshift.Inlined
   509  		if devfileCmp.Openshift.Uri != "" {
   510  			uri = devfileCmp.Openshift.Uri
   511  		}
   512  
   513  	default:
   514  		return "", fmt.Errorf("unexpected component type %s", componentType)
   515  	}
   516  
   517  	if uri != "" {
   518  		return loadResourceManifestFromUriAndResolveVariables(devfileObj, uri, context, fs)
   519  	}
   520  	return substituteVariables(devfileObj.Data.GetDevfileWorkspaceSpec().Variables, content)
   521  }
   522  
   523  func loadResourceManifestFromUriAndResolveVariables(devfileObj parser.DevfileObj, uri string,
   524  	context string, fs devfilefs.Filesystem) (string, error) {
   525  	content, err := util.GetDataFromURI(uri, context, fs)
   526  	if err != nil {
   527  		return content, err
   528  	}
   529  	return substituteVariables(devfileObj.Data.GetDevfileWorkspaceSpec().Variables, content)
   530  }
   531  
   532  // substituteVariables validates the string for a global variable in the given `devfileObj` and replaces it.
   533  // An error is returned if the string references an invalid variable key not defined in the Devfile object.
   534  //
   535  // Inspired from variables.validateAndReplaceDataWithVariable, which is unfortunately not exported
   536  func substituteVariables(devfileVars map[string]string, val string) (string, error) {
   537  	// example of the regex: {{variable}} / {{ variable }}
   538  	matches := regexp.MustCompile(`\{\{\s*(.*?)\s*\}\}`).FindAllStringSubmatch(val, -1)
   539  	var invalidKeys []string
   540  	for _, match := range matches {
   541  		varValue, ok := devfileVars[match[1]]
   542  		if !ok {
   543  			invalidKeys = append(invalidKeys, match[1])
   544  		} else {
   545  			val = strings.Replace(val, match[0], varValue, -1)
   546  		}
   547  	}
   548  
   549  	if len(invalidKeys) > 0 {
   550  		return val, &variables.InvalidKeysError{Keys: invalidKeys}
   551  	}
   552  
   553  	return val, nil
   554  }
   555  
   556  // findComponentByNameAndType returns the Devfile component that matches the specified name and type.
   557  func findComponentByNameAndType(d parser.DevfileObj, n string, t v1alpha2.ComponentType) (v1alpha2.Component, bool, error) {
   558  	comps, err := d.Data.GetComponents(common.DevfileOptions{ComponentOptions: common.ComponentOptions{ComponentType: t}})
   559  	if err != nil {
   560  		return v1alpha2.Component{}, false, err
   561  	}
   562  	for _, c := range comps {
   563  		if c.Name == n {
   564  			return c, true, nil
   565  		}
   566  	}
   567  	return v1alpha2.Component{}, false, nil
   568  }
   569  

View as plain text