...

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

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

     1  package backend
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  
     9  	dfutil "github.com/devfile/library/v2/pkg/util"
    10  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    11  	"k8s.io/apimachinery/pkg/runtime/schema"
    12  
    13  	"github.com/redhat-developer/odo/pkg/binding/asker"
    14  	"github.com/redhat-developer/odo/pkg/kclient"
    15  )
    16  
    17  const (
    18  	FLAG_WORKLOAD          = "workload"
    19  	FLAG_SERVICE           = "service"
    20  	FLAG_SERVICE_NAMESPACE = "service-namespace"
    21  	FLAG_NAME              = "name"
    22  	FLAG_BIND_AS_FILES     = "bind-as-files"
    23  	FLAG_NAMING_STRATEGY   = "naming-strategy"
    24  )
    25  
    26  // FlagsBackend is a backend that will extract all needed information from flags passed to the command
    27  type FlagsBackend struct{}
    28  
    29  var _ AddBindingBackend = (*FlagsBackend)(nil)
    30  
    31  func NewFlagsBackend() *FlagsBackend {
    32  	return &FlagsBackend{}
    33  }
    34  
    35  func (o *FlagsBackend) Validate(flags map[string]string, withDevfile bool) error {
    36  	if flags[FLAG_SERVICE] == "" {
    37  		return errors.New("missing --service parameter: please add --service <name>[/<kind>.<apigroup>] to specify the service instance for binding")
    38  	}
    39  	if flags[FLAG_NAME] == "" {
    40  		return errors.New("missing --name parameter: please add --name <name> to specify a name for the service binding instance")
    41  	}
    42  
    43  	if withDevfile && flags[FLAG_WORKLOAD] != "" {
    44  		return errors.New("--workload cannot be used from a directory containing a Devfile")
    45  	}
    46  
    47  	if !withDevfile && flags[FLAG_WORKLOAD] == "" {
    48  		return errors.New("missing --workload parameter: please add --workload <workload> so specify a workload to bind information to")
    49  	}
    50  	return dfutil.ValidateK8sResourceName(FLAG_NAME, flags[FLAG_NAME])
    51  }
    52  
    53  func (o *FlagsBackend) SelectWorkloadInstance(workloadName string) (string, schema.GroupVersionKind, error) {
    54  	selectedName, selectedKind, selectedGroup := parseServiceName(workloadName)
    55  	for _, gvk := range append(kclient.NativeWorkloadKinds, kclient.CustomWorkloadKinds...) {
    56  		if gvk.Group == selectedGroup && gvk.Kind == selectedKind {
    57  			return selectedName, gvk, nil
    58  		}
    59  	}
    60  	return "", schema.GroupVersionKind{}, fmt.Errorf("group/kind %q not found on the cluster", selectedGroup+"/"+selectedKind)
    61  }
    62  
    63  func (o *FlagsBackend) SelectNamespace(flags map[string]string) (string, error) {
    64  	return flags[FLAG_SERVICE_NAMESPACE], nil
    65  }
    66  
    67  // SelectServiceInstance parses the service's name, kind, and group from arg:serviceName,
    68  // after which it checks if the service is available in arg:serviceMap, it further checks for kind, and group
    69  // If a single service is found, it returns the service name in the form of '<name> (<kind>.<apigroup>)', else errors out.
    70  // serviceMap: a map of bindable service name with it's unstructured.Unstructured; this map is used to stay independent of the service name format.
    71  func (o *FlagsBackend) SelectServiceInstance(serviceName string, serviceMap map[string]unstructured.Unstructured) (string, error) {
    72  	selectedServiceName, selectedServiceKind, selectedServiceGroup := parseServiceName(serviceName)
    73  	// services tracks all the services that matches flags[FLAG_SERVICE]
    74  	var services []string
    75  	for option, unstructuredService := range serviceMap {
    76  		// option has format `<name> (<kind>.<apigroup>)`
    77  		if unstructuredService.GetName() == selectedServiceName {
    78  			if selectedServiceKind != "" && unstructuredService.GetKind() == selectedServiceKind {
    79  				if selectedServiceGroup != "" && unstructuredService.GroupVersionKind().Group == selectedServiceGroup {
    80  					services = append(services, option)
    81  					continue
    82  				} else if selectedServiceGroup == "" {
    83  					services = append(services, option)
    84  					continue
    85  				}
    86  			} else if selectedServiceKind == "" {
    87  				services = append(services, option)
    88  			}
    89  		}
    90  	}
    91  	if len(services) == 0 {
    92  		return "", fmt.Errorf("%q service not found", serviceName)
    93  	}
    94  	if len(services) > 1 {
    95  		return "", fmt.Errorf("Found more than one services with name %q [%+v]. Please mention <name>/<kind>.<apigroup>", serviceName, strings.Join(services, ","))
    96  	}
    97  
    98  	return services[0], nil
    99  }
   100  
   101  func (o *FlagsBackend) AskBindingName(_ string, flags map[string]string) (string, error) {
   102  	return flags[FLAG_NAME], nil
   103  }
   104  
   105  func (o *FlagsBackend) AskBindAsFiles(flags map[string]string) (bool, error) {
   106  	if flags[FLAG_BIND_AS_FILES] == "" {
   107  		// default value for bindAsFiles must be true
   108  		return true, nil
   109  	}
   110  	bindAsFiles, err := strconv.ParseBool(flags[FLAG_BIND_AS_FILES])
   111  	if err != nil {
   112  		return false, fmt.Errorf("unable to set %q to --%v, value must be a boolean", flags[FLAG_BIND_AS_FILES], FLAG_BIND_AS_FILES)
   113  	}
   114  	return bindAsFiles, nil
   115  }
   116  
   117  func (o *FlagsBackend) AskNamingStrategy(flags map[string]string) (string, error) {
   118  	return flags[FLAG_NAMING_STRATEGY], nil
   119  }
   120  
   121  func (o *FlagsBackend) SelectCreationOptions(flags map[string]string) ([]asker.CreationOption, error) {
   122  	return []asker.CreationOption{asker.OutputToStdout}, nil
   123  }
   124  
   125  func (o *FlagsBackend) AskOutputFilePath(flags map[string]string, defaultValue string) (string, error) {
   126  	return "", errors.New("this is not implemented")
   127  }
   128  
   129  // parseServiceName parses various service name formats. It supports the following formats:
   130  // - <name>
   131  // - <name>.<kind>
   132  // - <name>.<kind>.<apigroup>
   133  // - <name>/<kind>
   134  // - <name>/<kind>.<apigroup>
   135  func parseServiceName(service string) (name, kind, group string) {
   136  	if serviceNKG := strings.Split(service, "/"); len(serviceNKG) > 1 {
   137  		// Parse <name>/<kind>
   138  		name = serviceNKG[0]
   139  		kindGroup := strings.SplitN(serviceNKG[1], ".", 2)
   140  		kind = kindGroup[0]
   141  		if len(kindGroup) > 1 {
   142  			// Parse <name>/<kind>.<apigroup>
   143  			group = kindGroup[1]
   144  		}
   145  	} else if serviceNKG = strings.SplitN(service, ".", 3); len(serviceNKG) > 1 {
   146  		// Parse <name>.<kind>
   147  		name = serviceNKG[0]
   148  		kind = serviceNKG[1]
   149  		if len(serviceNKG) > 2 {
   150  			// Parse <name>.<kind>.<apigroup>
   151  			group = serviceNKG[2]
   152  		}
   153  	} else {
   154  		// Parse <name>
   155  		name = service
   156  	}
   157  	return
   158  }
   159  

View as plain text