diff --git a/api/v1/zone_types.go b/api/v1/zone_types.go index 903829ad..4660f705 100644 --- a/api/v1/zone_types.go +++ b/api/v1/zone_types.go @@ -1,6 +1,9 @@ package v1 import ( + "fmt" + "net/url" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -12,12 +15,44 @@ type Zone struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` + // Data holds the cluster specific metadata. Data ZoneData `json:"data,omitempty"` } // ZoneData holds all the Zone specific properties type ZoneData struct { - // TODO: Fill out spec + // DisplayName is a human-friendly name for the Zone. + DisplayName string `json:"displayName,omitempty"` + // Features holds a key-value dict with keys being a feature name and values being a property of that feature. + // Some features may hold a version string as property. + Features Features `json:"features,omitempty"` + // URLs holds a key-value dict with keys being a name of the URL and the values publicly accessible links. + URLs URLMap `json:"urls,omitempty"` + // CNAME is the DNS record where custom application DNS hostnames shall be pointing to when exposing an application. + CNAME string `json:"cname,omitempty"` + // DefaultAppDomain is the base DNS record where OpenShift Routes without specific hostnames are exposed. + DefaultAppDomain string `json:"defaultAppDomain,omitempty"` + // GatewayIPs holds the outgoing IP addresses of the cluster. + GatewayIPs []string `json:"gatewayIPs,omitempty"` + // CloudProvider identifies the infrastructure provider which the Zone is running on. + CloudProvider CloudProvider `json:"cloudProvider,omitempty"` +} + +// Features is a key-value dict with keys being a feature name and values being a property of that feature. +type Features map[string]string + +// URLMap is a key-value dict with keys being a name of the URL and the values publicly accessible links. +type URLMap map[string]string + +// CloudProvider identifies an infrastructure provider. +type CloudProvider struct { + // Name identifies the cloud provider. + Name string `json:"name,omitempty"` + // Zones is cloud-provider-specific zone aliases within a Region. + // If multiple entries are present, the cluster may be spanning multiple zones. + Zones []string `json:"zones,omitempty"` + // Region is the geographic location of the Zone. + Region string `json:"region,omitempty"` } // +kubebuilder:object:root=true @@ -30,6 +65,17 @@ type ZoneList struct { Items []Zone `json:"items"` } +// GetURL invokes url.Parse for the raw string from given key, if found. +func (in URLMap) GetURL(key string) (*url.URL, error) { + if in == nil { + return nil, fmt.Errorf("map is nil") + } + if raw, found := in[key]; found { + return url.Parse(raw) + } + return nil, fmt.Errorf("key not found: %s", key) +} + func init() { SchemeBuilder.Register(&Zone{}, &ZoneList{}) } diff --git a/api/v1/zone_types_test.go b/api/v1/zone_types_test.go new file mode 100644 index 00000000..307a3363 --- /dev/null +++ b/api/v1/zone_types_test.go @@ -0,0 +1,43 @@ +package v1 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUrlMap_GetURL(t *testing.T) { + tests := map[string]struct { + givenMap URLMap + givenKey string + expectedErr string + expectedValue string + }{ + "GivenNil_ThenExpectError": { + expectedErr: "map is nil", + }, + "GivenEmptyMap_WhenKeyNotPresent_ThenExpectError": { + givenMap: URLMap{}, + givenKey: "key", + expectedErr: "key not found: key", + }, + "GivenMap_WhenKeyPresent_ThenParseUrl": { + givenMap: URLMap{ + "key": "https://hostname:80/path", + }, + givenKey: "key", + expectedValue: "https://hostname:80/path", + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + result, err := tt.givenMap.GetURL(tt.givenKey) + if tt.expectedErr != "" { + require.EqualError(t, err, tt.expectedErr) + return + } + assert.Equal(t, tt.expectedValue, result.String()) + }) + } +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index cb9276b9..7a4ca881 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -9,12 +9,74 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CloudProvider) DeepCopyInto(out *CloudProvider) { + *out = *in + if in.Zones != nil { + in, out := &in.Zones, &out.Zones + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CloudProvider. +func (in *CloudProvider) DeepCopy() *CloudProvider { + if in == nil { + return nil + } + out := new(CloudProvider) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Features) DeepCopyInto(out *Features) { + { + in := &in + *out = make(Features, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Features. +func (in Features) DeepCopy() Features { + if in == nil { + return nil + } + out := new(Features) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in URLMap) DeepCopyInto(out *URLMap) { + { + in := &in + *out = make(URLMap, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new URLMap. +func (in URLMap) DeepCopy() URLMap { + if in == nil { + return nil + } + out := new(URLMap) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Zone) DeepCopyInto(out *Zone) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Data = in.Data + in.Data.DeepCopyInto(&out.Data) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Zone. @@ -38,6 +100,26 @@ func (in *Zone) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ZoneData) DeepCopyInto(out *ZoneData) { *out = *in + if in.Features != nil { + in, out := &in.Features, &out.Features + *out = make(Features, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.URLs != nil { + in, out := &in.URLs, &out.URLs + *out = make(URLMap, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.GatewayIPs != nil { + in, out := &in.GatewayIPs, &out.GatewayIPs + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.CloudProvider.DeepCopyInto(&out.CloudProvider) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ZoneData. diff --git a/config/crd/apiextensions.k8s.io/v1/base/appuio.io_zones.yaml b/config/crd/apiextensions.k8s.io/v1/base/appuio.io_zones.yaml index 9e849194..bc629ade 100644 --- a/config/crd/apiextensions.k8s.io/v1/base/appuio.io_zones.yaml +++ b/config/crd/apiextensions.k8s.io/v1/base/appuio.io_zones.yaml @@ -27,7 +27,55 @@ spec: internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string data: - description: ZoneData holds all the Zone specific properties + description: Data holds the cluster specific metadata. + properties: + cloudProvider: + description: CloudProvider identifies the infrastructure provider + which the Zone is running on. + properties: + name: + description: Name identifies the cloud provider. + type: string + region: + description: Region is the geographic location of the Zone. + type: string + zones: + description: Zones is cloud-provider-specific zone aliases within + a Region. If multiple entries are present, the cluster may be + spanning multiple zones. + items: + type: string + type: array + type: object + cname: + description: CNAME is the DNS record where custom application DNS + hostnames shall be pointing to when exposing an application. + type: string + defaultAppDomain: + description: DefaultAppDomain is the base DNS record where OpenShift + Routes without specific hostnames are exposed. + type: string + displayName: + description: DisplayName is a human-friendly name for the Zone. + type: string + features: + additionalProperties: + type: string + description: Features holds a key-value dict with keys being a feature + name and values being a property of that feature. Some features + may hold a version string as property. + type: object + gatewayIPs: + description: GatewayIPs holds the outgoing IP addresses of the cluster. + items: + type: string + type: array + urls: + additionalProperties: + type: string + description: URLs holds a key-value dict with keys being a name of + the URL and the values publicly accessible links. + type: object type: object kind: description: 'Kind is a string value representing the REST resource this