package cluster import ( "errors" "fmt" "sort" "strings" wh "edge-infra.dev/pkg/f8n/warehouse" "edge-infra.dev/pkg/f8n/warehouse/oci" ) // Public errors var ( // ErrNoProviders occurs when no providers can be parsed from the value // of AnnotationProviders or an empty set of cluster providers are passed // to a packaging function. ErrNoProviders = errors.New("at least one cluster provider is required") // ErrUnsupportedProvider occurs when an invalid set of providers are passed // to a packaging function or parsed from the value of AnnotationProviders. ErrUnsupportedProvider = errors.New("provider is not supported by artifact") ) // Provider represents a K8s cluster provider, e.g., GKE. type ( Provider string Providers []Provider ) // Built-in cluster providers const ( Generic Provider = "generic" GKE Provider = "gke" ) // BuiltInProviders returns the cluster providers that Warehouse understands // out-of-the-box. User-provided values are merged with these. func BuiltInProviders() Providers { return []Provider{GKE, Generic} } // String is a convenience function for casting to a string func (p Provider) String() string { return string(p) } // Set implements flag.Value. func (p *Provider) Set(v string) error { np := Provider(v) *p = np return nil } // Set implements [flag.Getter] func (p *Provider) Get() any { return p } // Type implements [edge-infra.dev/pkg/lib/cli/rags.TypedValue] func (p *Provider) Type() string { return "provider(s)" } // IsValid returns an error if the input string is not one of the configured // providers or the Providers is empty. Empty strings are not checked. func (p Providers) IsValid(provider Provider) error { if len(p) == 0 { return ErrNoProviders } if provider == "" { return nil } for _, p := range p { if p == provider { return nil } } return fmt.Errorf( "invalid cluster provider %s: expected one of %s", provider, p, ) } // OCIAnnotations converts the set of cluster providers to the OCI annotation // added to Pallet artifacts. // // Note: Callers are expected to validate the Providers. func (p Providers) OCIAnnotations() map[string]string { return map[string]string{wh.AnnotationClusterProviders: p.String()} } func (p Providers) StrArray() []string { s := make([]string, len(p)) for i, provider := range p { s[i] = string(provider) } sort.Strings(s) return s } func (p Providers) String() string { return strings.Join(p.StrArray(), ",") } // Set implements flag.Value. It accepts comma separated strings and accumulates // values from repeated occurrences of the flag. It also handles edge cases like // trailing and leading commas. func (p *Providers) Set(v string) error { v = strings.TrimSuffix(v, ",") for _, provider := range strings.Split(strings.Trim(v, ","), ",") { *p = append(*p, Provider(provider)) } return nil } func (p *Providers) Get() any { return p } // Assert that Capabilities implements the sort interface var _ sort.Interface = Providers{} func (p Providers) Len() int { return len(p) } func (p Providers) Less(i, j int) bool { return p[i] < p[j] } func (p Providers) Swap(i, j int) { p[i], p[j] = p[j], p[i] } // ProvidersFromAnnotations parses the K8s cluster providers from an OCI annotation // map. It returns an error if the annotation is not present, is invalid // or is empty. func ProvidersFromAnnotations(a map[string]string) (Providers, error) { str, ok := a[wh.AnnotationClusterProviders] if !ok { return nil, fmt.Errorf("%w: cluster providers annotation (%s) not present", oci.ErrInvalidArtifact, wh.AnnotationClusterProviders) } providers := ProvidersFromString(str) if len(providers) == 0 { return nil, ErrNoProviders } return providers, nil } // ProvidersFromString parses K8s cluster providers from a comma-separated // string value. func ProvidersFromString(s string) Providers { tokens := strings.Split(strings.Trim(s, ","), ",") p := make([]Provider, 0, len(tokens)) for _, cap := range strings.Split(strings.Trim(s, ","), ",") { p = append(p, Provider(cap)) } return p }