mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
* Go: Bump github.com/securego/gosec/v2 from 2.15.0 to 2.17.0 Bumps [github.com/securego/gosec/v2](https://github.com/securego/gosec) from 2.15.0 to 2.17.0. - [Release notes](https://github.com/securego/gosec/releases) - [Changelog](https://github.com/securego/gosec/blob/master/.goreleaser.yml) - [Commits](https://github.com/securego/gosec/compare/v2.15.0...v2.17.0) --- updated-dependencies: - dependency-name: github.com/securego/gosec/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Go: Bump github.com/securego/gosec/v2 from 2.15.0 to 2.17.0 Bumps [github.com/securego/gosec/v2](https://github.com/securego/gosec) from 2.15.0 to 2.17.0. - [Release notes](https://github.com/securego/gosec/releases) - [Changelog](https://github.com/securego/gosec/blob/master/.goreleaser.yml) - [Commits](https://github.com/securego/gosec/compare/v2.15.0...v2.17.0) --- updated-dependencies: - dependency-name: github.com/securego/gosec/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Ignore gosec error --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Philippe Martin <phmartin@redhat.com>
569 lines
20 KiB
Go
569 lines
20 KiB
Go
package libdevfile
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
|
"github.com/devfile/api/v2/pkg/validation/variables"
|
|
"github.com/devfile/library/v2/pkg/devfile/parser"
|
|
"github.com/devfile/library/v2/pkg/devfile/parser/data"
|
|
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
|
|
devfilefs "github.com/devfile/library/v2/pkg/testingutil/filesystem"
|
|
"k8s.io/klog"
|
|
|
|
"github.com/redhat-developer/odo/pkg/util"
|
|
)
|
|
|
|
const DebugEndpointNamePrefix = "debug"
|
|
|
|
type Handler interface {
|
|
ApplyImage(image v1alpha2.Component) error
|
|
ApplyKubernetes(kubernetes v1alpha2.Component, kind v1alpha2.CommandGroupKind) error
|
|
ApplyOpenShift(openshift v1alpha2.Component, kind v1alpha2.CommandGroupKind) error
|
|
ExecuteNonTerminatingCommand(ctx context.Context, command v1alpha2.Command) error
|
|
ExecuteTerminatingCommand(ctx context.Context, command v1alpha2.Command) error
|
|
}
|
|
|
|
// Deploy executes the default deploy command of the devfile.
|
|
func Deploy(ctx context.Context, devfileObj parser.DevfileObj, handler Handler) error {
|
|
return ExecuteCommandByNameAndKind(ctx, devfileObj, "", v1alpha2.DeployCommandGroupKind, handler, false)
|
|
}
|
|
|
|
// Build executes the default Build command of the devfile.
|
|
// If buildCmd is empty, this looks for the default Build command in the Devfile. No error is returned and no operation is performed
|
|
// if the default command could not be found.
|
|
// An error is returned if buildCmd is not empty and has no corresponding command in the Devfile.
|
|
func Build(ctx context.Context, devfileObj parser.DevfileObj, buildCmd string, handler Handler) error {
|
|
return ExecuteCommandByNameAndKind(ctx, devfileObj, buildCmd, v1alpha2.BuildCommandGroupKind, handler, buildCmd == "")
|
|
}
|
|
|
|
// ExecuteCommandByNameAndKind executes the specified command cmdName of the given kind in the Devfile.
|
|
// If cmdName is empty, it executes the default command for the given kind or returns an error if there is no default command.
|
|
// If ignoreCommandNotFound is true, nothing is executed if the command is not found and no error is returned.
|
|
func ExecuteCommandByNameAndKind(ctx context.Context, devfileObj parser.DevfileObj, cmdName string, kind v1alpha2.CommandGroupKind, handler Handler, ignoreCommandNotFound bool) error {
|
|
cmd, hasDefaultCmd, err := GetCommand(devfileObj, cmdName, kind)
|
|
if err != nil {
|
|
if _, isNotFound := err.(NoCommandFoundError); isNotFound {
|
|
if ignoreCommandNotFound {
|
|
klog.V(3).Infof("ignoring command not found: %v", cmdName)
|
|
return nil
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
if !hasDefaultCmd {
|
|
if ignoreCommandNotFound {
|
|
klog.V(3).Infof("ignoring default %v command not found", kind)
|
|
return nil
|
|
}
|
|
return NewNoDefaultCommandFoundError(kind)
|
|
}
|
|
|
|
return executeCommand(ctx, devfileObj, cmd, handler)
|
|
}
|
|
|
|
// ExecuteCommandByName executes the specified command cmdName in the Devfile.
|
|
// If ignoreCommandNotFound is true, nothing is executed if the command is not found and no error is returned.
|
|
func ExecuteCommandByName(ctx context.Context, devfileObj parser.DevfileObj, cmdName string, handler Handler, ignoreCommandNotFound bool) error {
|
|
commands, err := devfileObj.Data.GetCommands(
|
|
common.DevfileOptions{
|
|
FilterByName: cmdName,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(commands) != 1 {
|
|
return NewNoCommandFoundError("", cmdName)
|
|
}
|
|
|
|
cmd := commands[0]
|
|
return executeCommand(ctx, devfileObj, cmd, handler)
|
|
}
|
|
|
|
// executeCommand executes a specific command of a devfile using handler as backend
|
|
func executeCommand(ctx context.Context, devfileObj parser.DevfileObj, command v1alpha2.Command, handler Handler) error {
|
|
cmd, err := newCommand(devfileObj, command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return cmd.Execute(ctx, handler, nil)
|
|
}
|
|
|
|
// GetCommand iterates through the devfile commands and returns the devfile command with the specified name and group kind.
|
|
// 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
|
|
// (even if it is not marked as the default).
|
|
// It returns an error if there is more than one default command.
|
|
func GetCommand(
|
|
devfileObj parser.DevfileObj,
|
|
commandName string,
|
|
groupType v1alpha2.CommandGroupKind,
|
|
) (v1alpha2.Command, bool, error) {
|
|
if commandName == "" {
|
|
return getDefaultCommand(devfileObj, groupType)
|
|
}
|
|
cmdByName, err := getCommandByName(devfileObj, groupType, commandName)
|
|
if err != nil {
|
|
return v1alpha2.Command{}, false, err
|
|
}
|
|
return cmdByName, true, nil
|
|
}
|
|
|
|
// getDefaultCommand iterates through the devfile commands and returns the default command associated with the group kind.
|
|
// If there is no default command, the second return value is false.
|
|
func getDefaultCommand(devfileObj parser.DevfileObj, groupType v1alpha2.CommandGroupKind) (v1alpha2.Command, bool, error) {
|
|
commands, err := devfileObj.Data.GetCommands(common.DevfileOptions{CommandOptions: common.CommandOptions{CommandGroupKind: groupType}})
|
|
if err != nil {
|
|
return v1alpha2.Command{}, false, err
|
|
}
|
|
|
|
// if there is only one command of a given group kind, use it as default
|
|
if len(commands) == 1 {
|
|
return commands[0], true, nil
|
|
}
|
|
|
|
defaultCmds := make([]v1alpha2.Command, 0)
|
|
|
|
for _, cmd := range commands {
|
|
cmdGroup := common.GetGroup(cmd)
|
|
if cmdGroup != nil {
|
|
if cmdGroup.IsDefault != nil && *cmdGroup.IsDefault {
|
|
defaultCmds = append(defaultCmds, cmd)
|
|
}
|
|
} else {
|
|
klog.V(2).Infof("command %s has no group", cmd.Id)
|
|
}
|
|
}
|
|
|
|
if len(defaultCmds) == 0 {
|
|
return v1alpha2.Command{}, false, nil
|
|
}
|
|
if len(defaultCmds) > 1 {
|
|
return v1alpha2.Command{}, false, NewMoreThanOneDefaultCommandFoundError(groupType)
|
|
}
|
|
// #nosec
|
|
// gosec:G602 -> This is safe since we checked the length before
|
|
return defaultCmds[0], true, nil
|
|
}
|
|
|
|
// getCommandByName iterates through the devfile commands and returns the command with the specified name and group.
|
|
// It returns an error if no command was found.
|
|
func getCommandByName(devfileObj parser.DevfileObj, groupType v1alpha2.CommandGroupKind, commandName string) (v1alpha2.Command, error) {
|
|
commands, err := devfileObj.Data.GetCommands(common.DevfileOptions{CommandOptions: common.CommandOptions{CommandGroupKind: groupType}})
|
|
if err != nil {
|
|
return v1alpha2.Command{}, err
|
|
}
|
|
|
|
for _, cmd := range commands {
|
|
if cmd.Id == commandName {
|
|
return cmd, nil
|
|
}
|
|
}
|
|
|
|
return v1alpha2.Command{}, NewNoCommandFoundError(groupType, commandName)
|
|
}
|
|
|
|
// ValidateAndGetCommand validates and returns the command specified if it is valid.
|
|
// It works just like GetCommand, except that it returns an error if it could not find the command.
|
|
//
|
|
// If commandName is empty, it looks up the default command for the given kind.
|
|
//
|
|
// A command is "valid" here if it was found given its name (if commandName is not empty),
|
|
// or (for a default command), if there is no other default command for the same kind.
|
|
func ValidateAndGetCommand(devfileObj parser.DevfileObj, commandName string, groupType v1alpha2.CommandGroupKind) (v1alpha2.Command, error) {
|
|
cmd, ok, err := GetCommand(devfileObj, commandName, groupType)
|
|
if err != nil {
|
|
return v1alpha2.Command{}, err
|
|
}
|
|
if !ok {
|
|
return v1alpha2.Command{}, NewNoCommandFoundError(groupType, commandName)
|
|
}
|
|
return cmd, nil
|
|
}
|
|
|
|
// ValidateAndGetPushCommands validates the build and the run commands, if provided through odo dev or else checks the devfile for devBuild and devRun.
|
|
// It returns the build and run commands if validated successfully, or an error otherwise.
|
|
func ValidateAndGetPushCommands(
|
|
devfileObj parser.DevfileObj,
|
|
devfileBuildCmd,
|
|
devfileRunCmd string,
|
|
) (map[v1alpha2.CommandGroupKind]v1alpha2.Command, error) {
|
|
var buildCmd v1alpha2.Command
|
|
var present bool
|
|
var err error
|
|
|
|
if devfileBuildCmd != "" {
|
|
buildCmd, err = ValidateAndGetCommand(devfileObj, devfileBuildCmd, v1alpha2.BuildCommandGroupKind)
|
|
present = true
|
|
} else {
|
|
buildCmd, present, err = GetCommand(devfileObj, devfileBuildCmd, v1alpha2.BuildCommandGroupKind)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
commandMap := make(map[v1alpha2.CommandGroupKind]v1alpha2.Command)
|
|
if present {
|
|
klog.V(2).Infof("Build command: %v", buildCmd.Id)
|
|
commandMap[v1alpha2.BuildCommandGroupKind] = buildCmd
|
|
} else {
|
|
// Build command is optional, unless it was explicitly specified by the caller (at which point it would have been validated via ValidateAndGetCommand).
|
|
klog.V(2).Infof("No build command was provided")
|
|
}
|
|
|
|
var runCmd v1alpha2.Command
|
|
runCmd, err = ValidateAndGetCommand(devfileObj, devfileRunCmd, v1alpha2.RunCommandGroupKind)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
klog.V(2).Infof("Run command: %v", runCmd.Id)
|
|
commandMap[v1alpha2.RunCommandGroupKind] = runCmd
|
|
|
|
return commandMap, nil
|
|
}
|
|
|
|
func HasPostStartEvents(devfileObj parser.DevfileObj) bool {
|
|
postStartEvents := devfileObj.Data.GetEvents().PostStart
|
|
return len(postStartEvents) > 0
|
|
}
|
|
|
|
func HasPreStopEvents(devfileObj parser.DevfileObj) bool {
|
|
preStopEvents := devfileObj.Data.GetEvents().PreStop
|
|
return len(preStopEvents) > 0
|
|
}
|
|
|
|
func ExecPostStartEvents(ctx context.Context, devfileObj parser.DevfileObj, handler Handler) error {
|
|
postStartEvents := devfileObj.Data.GetEvents().PostStart
|
|
return execDevfileEvent(ctx, devfileObj, postStartEvents, handler)
|
|
}
|
|
|
|
func ExecPreStopEvents(ctx context.Context, devfileObj parser.DevfileObj, handler Handler) error {
|
|
preStopEvents := devfileObj.Data.GetEvents().PreStop
|
|
return execDevfileEvent(ctx, devfileObj, preStopEvents, handler)
|
|
}
|
|
|
|
func hasCommand(devfileData data.DevfileData, kind v1alpha2.CommandGroupKind) bool {
|
|
commands, err := devfileData.GetCommands(common.DevfileOptions{
|
|
CommandOptions: common.CommandOptions{
|
|
CommandGroupKind: kind,
|
|
},
|
|
})
|
|
return err == nil && len(commands) > 0
|
|
}
|
|
|
|
func HasRunCommand(devfileData data.DevfileData) bool {
|
|
return hasCommand(devfileData, v1alpha2.RunCommandGroupKind)
|
|
}
|
|
|
|
func HasDeployCommand(devfileData data.DevfileData) bool {
|
|
return hasCommand(devfileData, v1alpha2.DeployCommandGroupKind)
|
|
}
|
|
|
|
func HasDebugCommand(devfileData data.DevfileData) bool {
|
|
return hasCommand(devfileData, v1alpha2.DebugCommandGroupKind)
|
|
}
|
|
|
|
// execDevfileEvent receives a Devfile Event (PostStart, PreStop etc.) and loops through them
|
|
// Each Devfile Command associated with the given event is retrieved, and executed in the container specified
|
|
// in the command
|
|
func execDevfileEvent(ctx context.Context, devfileObj parser.DevfileObj, events []string, handler Handler) error {
|
|
if len(events) > 0 {
|
|
commandMap, err := allCommandsMap(devfileObj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, commandName := range events {
|
|
command, ok := commandMap[commandName]
|
|
if !ok {
|
|
return fmt.Errorf("unable to find devfile command %q", commandName)
|
|
}
|
|
|
|
c, err := newCommand(devfileObj, command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Execute command in container
|
|
err = c.Execute(ctx, handler, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to execute devfile command %q: %w", commandName, err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetDevfileContainerEndpointMapping returns a map of container components names and slice of its endpoints,
|
|
// given a Devfile object in parameter.
|
|
// Debug endpoints will be included only if includeDebug is true.
|
|
func GetDevfileContainerEndpointMapping(devFileObj parser.DevfileObj, includeDebug bool) (map[string][]v1alpha2.Endpoint, error) {
|
|
// get the endpoint/port information for containers in devfile
|
|
containers, err := devFileObj.Data.GetComponents(common.DevfileOptions{
|
|
ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.ContainerComponentType},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return GetContainerEndpointMapping(containers, includeDebug), nil
|
|
}
|
|
|
|
// GetContainerEndpointMapping returns a map of container names and slice of its endpoints.
|
|
// Debug endpoints will be included only if includeDebug is true.
|
|
func GetContainerEndpointMapping(containers []v1alpha2.Component, includeDebug bool) map[string][]v1alpha2.Endpoint {
|
|
ceMapping := make(map[string][]v1alpha2.Endpoint)
|
|
for _, container := range containers {
|
|
if container.ComponentUnion.Container == nil {
|
|
// this is not a container component; continue prevents panic when accessing Endpoints field
|
|
continue
|
|
}
|
|
|
|
var ports []v1alpha2.Endpoint
|
|
for _, e := range container.Container.Endpoints {
|
|
if !includeDebug && IsDebugEndpoint(e) {
|
|
klog.V(4).Infof("not running in Debug mode, so ignored Debug port for container %v:%v:%v",
|
|
container.Name, e.Name, e.TargetPort)
|
|
continue
|
|
}
|
|
ports = append(ports, e)
|
|
}
|
|
if len(ports) != 0 {
|
|
ceMapping[container.Name] = ports
|
|
}
|
|
}
|
|
return ceMapping
|
|
}
|
|
|
|
// GetEndpointsFromDevfile returns a slice of all endpoints in a devfile and ignores the endpoints with exposure values in ignoreExposures
|
|
func GetEndpointsFromDevfile(devfileObj parser.DevfileObj, ignoreExposures []v1alpha2.EndpointExposure) ([]v1alpha2.Endpoint, error) {
|
|
containers, err := devfileObj.Data.GetComponents(common.DevfileOptions{
|
|
ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.ContainerComponentType},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var allEndpoints []v1alpha2.Endpoint
|
|
for _, c := range containers {
|
|
allEndpoints = append(allEndpoints, c.Container.Endpoints...)
|
|
}
|
|
|
|
var endpoints []v1alpha2.Endpoint
|
|
for _, e := range allEndpoints {
|
|
ignore := false
|
|
for _, i := range ignoreExposures {
|
|
if e.Exposure == i {
|
|
ignore = true
|
|
}
|
|
}
|
|
if !ignore {
|
|
endpoints = append(endpoints, e)
|
|
}
|
|
}
|
|
return endpoints, nil
|
|
}
|
|
|
|
// GetDebugEndpointsForComponent returns all Debug endpoints for the specified component.
|
|
// It returns an error if the component specified is not a container component.
|
|
func GetDebugEndpointsForComponent(cmp v1alpha2.Component) ([]v1alpha2.Endpoint, error) {
|
|
if cmp.Container == nil {
|
|
return nil, fmt.Errorf("component %q is not a container component", cmp.Name)
|
|
}
|
|
|
|
var result []v1alpha2.Endpoint
|
|
for _, ep := range cmp.Container.Endpoints {
|
|
if IsDebugEndpoint(ep) {
|
|
result = append(result, ep)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// IsDebugEndpoint returns whether the specified endpoint represents a Debug endpoint,
|
|
// based on the following naming convention: it is considered a Debug endpoint if it's named "debug" or if its name starts with "debug-".
|
|
func IsDebugEndpoint(ep v1alpha2.Endpoint) bool {
|
|
return IsDebugPort(ep.Name)
|
|
}
|
|
|
|
// IsDebugPort returns whether the specified string represents a Debug endpoint,
|
|
// based on the following naming convention: it is considered a Debug endpoint if it's named "debug" or if its name starts with "debug-".
|
|
func IsDebugPort(name string) bool {
|
|
return name == DebugEndpointNamePrefix || strings.HasPrefix(name, DebugEndpointNamePrefix+"-")
|
|
}
|
|
|
|
// GetContainerComponentsForCommand returns the list of container components that would get used if the specified command runs.
|
|
func GetContainerComponentsForCommand(devfileObj parser.DevfileObj, cmd v1alpha2.Command) ([]string, error) {
|
|
// No error if cmd is empty
|
|
if reflect.DeepEqual(cmd, v1alpha2.Command{}) {
|
|
return nil, nil
|
|
}
|
|
|
|
commandType, err := common.GetCommandType(cmd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hasComponent := func(n string) bool {
|
|
_, ok, _ := findComponentByNameAndType(devfileObj, n, v1alpha2.ContainerComponentType)
|
|
return ok
|
|
}
|
|
|
|
switch commandType {
|
|
case v1alpha2.ExecCommandType:
|
|
if hasComponent(cmd.Exec.Component) {
|
|
return []string{cmd.Exec.Component}, nil
|
|
}
|
|
return nil, nil
|
|
case v1alpha2.ApplyCommandType:
|
|
if hasComponent(cmd.Apply.Component) {
|
|
return []string{cmd.Apply.Component}, nil
|
|
}
|
|
return nil, nil
|
|
case v1alpha2.CompositeCommandType:
|
|
var commandsMap map[string]v1alpha2.Command
|
|
commandsMap, err = allCommandsMap(devfileObj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var res []string
|
|
set := make(map[string]bool)
|
|
var componentsForCommand []string
|
|
for _, c := range cmd.Composite.Commands {
|
|
fromCommandMap, present := commandsMap[strings.ToLower(c)]
|
|
if !present {
|
|
return nil, fmt.Errorf("command %q not found in all commands map", c)
|
|
}
|
|
componentsForCommand, err = GetContainerComponentsForCommand(devfileObj, fromCommandMap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, s := range componentsForCommand {
|
|
if _, ok := set[s]; !ok && hasComponent(s) {
|
|
set[s] = true
|
|
res = append(res, s)
|
|
}
|
|
}
|
|
}
|
|
|
|
return res, nil
|
|
|
|
default:
|
|
return nil, fmt.Errorf("type not handled for command %q: %v", cmd.Id, commandType)
|
|
}
|
|
}
|
|
|
|
// FindComponentByName returns the Devfile component that matches the specified name.
|
|
func FindComponentByName(d data.DevfileData, n string) (v1alpha2.Component, bool, error) {
|
|
comps, err := d.GetComponents(common.DevfileOptions{})
|
|
if err != nil {
|
|
return v1alpha2.Component{}, false, err
|
|
}
|
|
for _, c := range comps {
|
|
if c.Name == n {
|
|
return c, true, nil
|
|
}
|
|
}
|
|
return v1alpha2.Component{}, false, nil
|
|
}
|
|
|
|
// GetK8sManifestsWithVariablesSubstituted returns the full content of either a Kubernetes or an Openshift
|
|
// Devfile component, either Inlined or referenced via a URI.
|
|
// No matter how the component is defined, it returns the content with all variables substituted
|
|
// using the global variables map defined in `devfileObj`.
|
|
// An error is returned if the content references an invalid variable key not defined in the Devfile object.
|
|
func GetK8sManifestsWithVariablesSubstituted(devfileObj parser.DevfileObj, devfileCmpName string,
|
|
context string, fs devfilefs.Filesystem) (string, error) {
|
|
|
|
components, err := devfileObj.Data.GetComponents(common.DevfileOptions{FilterByName: devfileCmpName})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if len(components) == 0 {
|
|
return "", NewComponentNotExistError(devfileCmpName)
|
|
}
|
|
|
|
if len(components) != 1 {
|
|
return "", NewComponentsWithSameNameError(devfileCmpName)
|
|
}
|
|
|
|
devfileCmp := components[0]
|
|
componentType, err := common.GetComponentType(devfileCmp)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var content, uri string
|
|
switch componentType {
|
|
case v1alpha2.KubernetesComponentType:
|
|
content = devfileCmp.Kubernetes.Inlined
|
|
if devfileCmp.Kubernetes.Uri != "" {
|
|
uri = devfileCmp.Kubernetes.Uri
|
|
}
|
|
|
|
case v1alpha2.OpenshiftComponentType:
|
|
content = devfileCmp.Openshift.Inlined
|
|
if devfileCmp.Openshift.Uri != "" {
|
|
uri = devfileCmp.Openshift.Uri
|
|
}
|
|
|
|
default:
|
|
return "", fmt.Errorf("unexpected component type %s", componentType)
|
|
}
|
|
|
|
if uri != "" {
|
|
return loadResourceManifestFromUriAndResolveVariables(devfileObj, uri, context, fs)
|
|
}
|
|
return substituteVariables(devfileObj.Data.GetDevfileWorkspaceSpec().Variables, content)
|
|
}
|
|
|
|
func loadResourceManifestFromUriAndResolveVariables(devfileObj parser.DevfileObj, uri string,
|
|
context string, fs devfilefs.Filesystem) (string, error) {
|
|
content, err := util.GetDataFromURI(uri, context, fs)
|
|
if err != nil {
|
|
return content, err
|
|
}
|
|
return substituteVariables(devfileObj.Data.GetDevfileWorkspaceSpec().Variables, content)
|
|
}
|
|
|
|
// substituteVariables validates the string for a global variable in the given `devfileObj` and replaces it.
|
|
// An error is returned if the string references an invalid variable key not defined in the Devfile object.
|
|
//
|
|
// Inspired from variables.validateAndReplaceDataWithVariable, which is unfortunately not exported
|
|
func substituteVariables(devfileVars map[string]string, val string) (string, error) {
|
|
// example of the regex: {{variable}} / {{ variable }}
|
|
matches := regexp.MustCompile(`\{\{\s*(.*?)\s*\}\}`).FindAllStringSubmatch(val, -1)
|
|
var invalidKeys []string
|
|
for _, match := range matches {
|
|
varValue, ok := devfileVars[match[1]]
|
|
if !ok {
|
|
invalidKeys = append(invalidKeys, match[1])
|
|
} else {
|
|
val = strings.Replace(val, match[0], varValue, -1)
|
|
}
|
|
}
|
|
|
|
if len(invalidKeys) > 0 {
|
|
return val, &variables.InvalidKeysError{Keys: invalidKeys}
|
|
}
|
|
|
|
return val, nil
|
|
}
|
|
|
|
// findComponentByNameAndType returns the Devfile component that matches the specified name and type.
|
|
func findComponentByNameAndType(d parser.DevfileObj, n string, t v1alpha2.ComponentType) (v1alpha2.Component, bool, error) {
|
|
comps, err := d.Data.GetComponents(common.DevfileOptions{ComponentOptions: common.ComponentOptions{ComponentType: t}})
|
|
if err != nil {
|
|
return v1alpha2.Component{}, false, err
|
|
}
|
|
for _, c := range comps {
|
|
if c.Name == n {
|
|
return c, true, nil
|
|
}
|
|
}
|
|
return v1alpha2.Component{}, false, nil
|
|
}
|