diff --git a/Makefile b/Makefile index ebc0c8c..c30b35b 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,12 @@ render: @for file in examples/xr-*.yaml; do \ + echo ""; \ echo "Rendering $$file..."; \ crossplane beta render \ "$$file" \ apis/composition.yaml \ examples/functions.yaml; \ done + +debug: + go run . --insecure --debug \ No newline at end of file diff --git a/README.md b/README.md index b3ef38d..5e1c5e3 100644 --- a/README.md +++ b/README.md @@ -5,98 +5,119 @@ A [Crossplane](https://www.crossplane.io/) for calculating Classless Inter-Domain Routing ([CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing)) numbers. + A CIDR is an IP address allocation method that is used to improve data routing efficiency on the internet. ## Overview -This composition function offers 4 HashiCorp compatible -IP Network Functions plus one custom wrapper. Follow the -function links for detailed explanations of the function -semantics. +This composition function offers 4 HashiCorp compatible IP Network Functions +plus two custom wrappers. Follow the function links for detailed explanations of +the function semantics. + - [cidrhost](https://developer.hashicorp.com/terraform/language/functions/cidrhost) - [cidrnetmask](https://developer.hashicorp.com/terraform/language/functions/cidrnetmask) - [cidrsubnet](https://developer.hashicorp.com/terraform/language/functions/cidrsubnet) - [cidrsubnets](https://developer.hashicorp.com/terraform/language/functions/cidrsubnets) - cidrsubnetloop wraps [cidrsubnet](https://developer.hashicorp.com/terraform/language/functions/cidrsubnet) +- multiprefixloop wraps [cidrsubnets](https://developer.hashicorp.com/terraform/language/functions/cidrsubnets) To use this function, apply the following [functions.yaml](examples/functions.yaml) to your Crossplane management cluster. -``` + +```bash cat < 0 { - prefix, err = oxr.Resource.GetString(input.PrefixField) + cidrFunc := input.CidrFunc + if len(input.CidrFuncField) > 0 { + cidrFunc, err = oxr.Resource.GetString(input.CidrFuncField) if err != nil { - response.Fatal(rsp, errors.Wrapf(err, "cannot get prefix from field %s for %s", input.PrefixField, oxr.Resource.GetKind())) + response.Fatal(rsp, errors.Wrapf(err, "cannot get cidrFunc from field %s for %s", input.CidrFunc, oxr.Resource.GetKind())) return rsp, nil } } + log.Info("Running function", "cidrFunc", cidrFunc) - cidrFunc := input.CidrFunc - if len(input.CidrFunc) > 0 { - cidrFunc, err = oxr.Resource.GetString(input.CidrFunc) + var prefix string = input.Prefix + if cidrFunc != "multiprefixloop" && len(input.PrefixField) > 0 { + prefix, err = oxr.Resource.GetString(input.PrefixField) if err != nil { - response.Fatal(rsp, errors.Wrapf(err, "cannot get cidrFunc from field %s for %s", input.CidrFunc, oxr.Resource.GetKind())) + response.Fatal(rsp, errors.Wrapf(err, "cannot get prefix from field %s for %s", input.PrefixField, oxr.Resource.GetKind())) return rsp, nil } } + var field string = input.OutputField + if field == "" { + field = "status.atFunction.cidr" + } + switch cidrFunc { // cidrhost calculates the host CIDR from a prefix and a host number. // https://developer.hashicorp.com/terraform/language/functions/cidrhost @@ -99,11 +98,6 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ return rsp, nil } - field, err := oxr.Resource.GetString(input.OutputField) - if err != nil { - field = "status.atFunction.cidr.host" - } - err = dxr.Resource.SetString(field, host) if err != nil { response.Fatal(rsp, errors.Wrapf(err, "cannot set field %s to %s for %s", field, host, oxr.Resource.GetKind())) @@ -118,10 +112,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ response.Fatal(rsp, errors.Wrapf(err, "cannot calculate CIDR netmask for %s", oxr.Resource.GetKind())) return rsp, nil } - field, err := oxr.Resource.GetString(input.OutputField) - if err != nil { - field = "status.atFunction.cidr.netmask" - } + err = dxr.Resource.SetString(field, netmask) if err != nil { response.Fatal(rsp, errors.Wrapf(err, "cannot set field %s to %s for %s", field, netmask, oxr.Resource.GetKind())) @@ -154,10 +145,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ response.Fatal(rsp, errors.Wrapf(err, "cannot calculate subnet CIDR for %s", oxr.Resource.GetKind())) return rsp, nil } - field, err := oxr.Resource.GetString(input.OutputField) - if err != nil { - field = "status.atFunction.cidr.subnet" - } + err = dxr.Resource.SetString(field, string(cidr)) if err != nil { response.Fatal(rsp, errors.Wrapf(err, "cannot set field %s to %s for %s", field, string(cidr), oxr.Resource.GetKind())) @@ -187,10 +175,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ for _, cidr := range cidrs { cidrSubnetsStringArray = append(cidrSubnetsStringArray, string(cidr)) } - field, err := oxr.Resource.GetString(input.OutputField) - if err != nil { - field = "status.atFunction.cidr.subnets" - } + err = dxr.Resource.SetValue(field, cidrSubnetsStringArray) if err != nil { response.Fatal(rsp, errors.Wrapf(err, "cannot set field %s to %s for %s", field, cidrSubnetsStringArray, oxr.Resource.GetKind())) @@ -244,6 +229,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ return rsp, nil } } + for netNum = 0; netNum < netNumCount; netNum++ { cidr, cidrSubnetErr := CidrSubnet(prefix, newBits[0], netNum+offset) if cidrSubnetErr != nil { @@ -252,16 +238,66 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ } cidrSubnetLoopStringArray = append(cidrSubnetLoopStringArray, string(cidr)) } - field, err := oxr.Resource.GetString(input.OutputField) - if err != nil { - field = "status.atFunction.cidr.subnets" - } + err = dxr.Resource.SetValue(field, cidrSubnetLoopStringArray) if err != nil { response.Fatal(rsp, errors.Wrapf(err, "cannot set field %s to %s for %s", field, cidrSubnetLoopStringArray, oxr.Resource.GetKind())) return rsp, nil } + // multiprefix is a convenience wrapper around cidrsubnets that loops over a + // range of prefixes to create a list of subnets for each prefix. + case "multiprefixloop": + var subnetsByCidr map[string][]string = make(map[string][]string) + var multiPrefixes []v1beta1.MultiPrefix + + multiPrefixes = input.MultiPrefix + if len(input.MultiPrefixField) > 0 { + err = oxr.Resource.GetValueInto(input.MultiPrefixField, &multiPrefixes) + if err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot get multiprefix from field %s for %s", input.MultiPrefixField, oxr.Resource.GetKind())) + return rsp, nil + } + } + + for _, multiPrefix := range multiPrefixes { + prefix := multiPrefix.Prefix + if len(prefix) == 0 { + continue + } + + newBits := multiPrefix.NewBits + if len(newBits) == 0 { + continue + } + + if multiPrefix.Offset > 0 { + newBits = append([]int{multiPrefix.Offset}, newBits...) + } + + cidrs, err := CidrSubnets(prefix, newBits...) + if err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot calculate Subnet CIDRs for %s", oxr.Resource.GetKind())) + return rsp, nil + } + + var cidrSubnetsStringArray []string + for _, cidr := range cidrs { + cidrSubnetsStringArray = append(cidrSubnetsStringArray, string(cidr)) + } + + subnetsByCidr[prefix] = cidrSubnetsStringArray + if multiPrefix.Offset > 0 { + subnetsByCidr[prefix] = cidrSubnetsStringArray[1:] + } + } + + err = dxr.Resource.SetValue(field, subnetsByCidr) + if err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot set field %s to %s for %s", field, subnetsByCidr, oxr.Resource.GetKind())) + return rsp, nil + } + default: log.Info("internal error: sub function not supported: ", "cidrFunc", input.CidrFunc) } diff --git a/fn_test.go b/fn_test.go index 7fb969d..081d165 100644 --- a/fn_test.go +++ b/fn_test.go @@ -7,8 +7,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/structpb" "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/crossplane/function-sdk-go/resource" fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1" ) @@ -27,7 +30,382 @@ func TestRunFunction(t *testing.T) { reason string args args want want - }{} + }{ + "cidr-host": { + reason: "should return the CIDR host of the request", + args: args{ + ctx: context.Background(), + req: &fnv1beta1.RunFunctionRequest{ + Input: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "cidrFunc": { + Kind: &structpb.Value_StringValue{ + StringValue: "cidrhost", + }, + }, + "prefix": { + Kind: &structpb.Value_StringValue{ + StringValue: "127.0.0.0/24", + }, + }, + "hostNum": { + Kind: &structpb.Value_NumberValue{ + NumberValue: 111, + }, + }, + }, + }, + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(`{"apiVersion":"","kind":"","status": {"atFunction": {"cidr": "127.0.0.111"}}}`), + }, + }, + Meta: &fnv1beta1.ResponseMeta{ + Ttl: &durationpb.Duration{ + Seconds: 60, + }, + }, + }, + err: nil, + }, + }, + + "cidr-subnet": { + reason: "should return the cidr subnet of the request", + args: args{ + ctx: context.Background(), + req: &fnv1beta1.RunFunctionRequest{ + Input: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "cidrFunc": { + Kind: &structpb.Value_StringValue{ + StringValue: "cidrsubnet", + }, + }, + "prefix": { + Kind: &structpb.Value_StringValue{ + StringValue: "127.0.0.0/24", + }, + }, + "newBits": { + Kind: &structpb.Value_ListValue{ + ListValue: &structpb.ListValue{ + Values: []*structpb.Value{ + { + Kind: &structpb.Value_NumberValue{ + NumberValue: 8, + }, + }, + }, + }, + }, + }, + "netNum": { + Kind: &structpb.Value_NumberValue{ + NumberValue: 3, + }, + }, + }, + }, + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(`{"apiVersion":"","kind":"","status": {"atFunction": {"cidr": "127.0.0.3/32"}}}`), + }, + }, + Meta: &fnv1beta1.ResponseMeta{ + Ttl: &durationpb.Duration{ + Seconds: 60, + }, + }, + }, + err: nil, + }, + }, + "cidr-netmask": { + reason: "should return the CIDR netmask of the request", + args: args{ + ctx: context.Background(), + req: &fnv1beta1.RunFunctionRequest{ + Input: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "cidrFunc": { + Kind: &structpb.Value_StringValue{ + StringValue: "cidrnetmask", + }, + }, + "prefix": { + Kind: &structpb.Value_StringValue{ + StringValue: "127.0.0.0/24", + }, + }, + }, + }, + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(`{"apiVersion":"","kind":"","status": {"atFunction": {"cidr": "255.255.255.0"}}}`), + }, + }, + Meta: &fnv1beta1.ResponseMeta{ + Ttl: &durationpb.Duration{ + Seconds: 60, + }, + }, + }, + err: nil, + }, + }, + "cidr-subnets": { + reason: "should return the cidr subnet of the request", + args: args{ + ctx: context.Background(), + req: &fnv1beta1.RunFunctionRequest{ + Input: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "cidrFunc": { + Kind: &structpb.Value_StringValue{ + StringValue: "cidrsubnets", + }, + }, + "prefix": { + Kind: &structpb.Value_StringValue{ + StringValue: "127.0.0.0/24", + }, + }, + "newBits": { + Kind: &structpb.Value_ListValue{ + ListValue: &structpb.ListValue{ + Values: []*structpb.Value{ + { + Kind: &structpb.Value_NumberValue{ + NumberValue: 8, + }, + }, + { + Kind: &structpb.Value_NumberValue{ + NumberValue: 4, + }, + }, + { + Kind: &structpb.Value_NumberValue{ + NumberValue: 2, + }, + }, + }, + }, + }, + }, + "netNum": { + Kind: &structpb.Value_NumberValue{ + NumberValue: 3, + }, + }, + }, + }, + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(`{"apiVersion":"","kind":"","status": {"atFunction": {"cidr": ["127.0.0.0/32", "127.0.0.16/28", "127.0.0.64/26"]}}}`), + }, + }, + Meta: &fnv1beta1.ResponseMeta{ + Ttl: &durationpb.Duration{ + Seconds: 60, + }, + }, + }, + err: nil, + }, + }, + "cidr-subnetloop": { + reason: "should return the cidr subnet of the request", + args: args{ + ctx: context.Background(), + req: &fnv1beta1.RunFunctionRequest{ + Input: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "cidrFunc": { + Kind: &structpb.Value_StringValue{ + StringValue: "cidrsubnetloop", + }, + }, + "prefix": { + Kind: &structpb.Value_StringValue{ + StringValue: "10.0.0.0/24", + }, + }, + "newBits": { + Kind: &structpb.Value_ListValue{ + ListValue: &structpb.ListValue{ + Values: []*structpb.Value{ + { + Kind: &structpb.Value_NumberValue{ + NumberValue: 8, + }, + }, + }, + }, + }, + }, + "netNumCount": { + Kind: &structpb.Value_NumberValue{ + NumberValue: 3, + }, + }, + "offset": { + Kind: &structpb.Value_NumberValue{ + NumberValue: 48, + }, + }, + }, + }, + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(`{"apiVersion":"","kind":"","status": {"atFunction": {"cidr": ["10.0.0.48/32", "10.0.0.49/32", "10.0.0.50/32"]}}}`), + }, + }, + Meta: &fnv1beta1.ResponseMeta{ + Ttl: &durationpb.Duration{ + Seconds: 60, + }, + }, + }, + err: nil, + }, + }, + "multi-prefix-loop": { + reason: "should return multiple cidr subnets for the request", + args: args{ + ctx: context.Background(), + req: &fnv1beta1.RunFunctionRequest{ + Input: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "cidrFunc": { + Kind: &structpb.Value_StringValue{ + StringValue: "multiprefixloop", + }, + }, + "multiPrefix": { + Kind: &structpb.Value_ListValue{ + ListValue: &structpb.ListValue{ + Values: []*structpb.Value{ + { + Kind: &structpb.Value_StructValue{ + StructValue: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "prefix": { + Kind: &structpb.Value_StringValue{ + StringValue: "10.10.0.0/24", + }, + }, + "newBits": { + Kind: &structpb.Value_ListValue{ + ListValue: &structpb.ListValue{ + Values: []*structpb.Value{ + { + Kind: &structpb.Value_NumberValue{ + NumberValue: 8, + }, + }, + { + Kind: &structpb.Value_NumberValue{ + NumberValue: 4, + }, + }, + { + Kind: &structpb.Value_NumberValue{ + NumberValue: 2, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + Kind: &structpb.Value_StructValue{ + StructValue: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "prefix": { + Kind: &structpb.Value_StringValue{ + StringValue: "10.12.0.0/24", + }, + }, + "newBits": { + Kind: &structpb.Value_ListValue{ + ListValue: &structpb.ListValue{ + Values: []*structpb.Value{ + { + Kind: &structpb.Value_NumberValue{ + NumberValue: 4, + }, + }, + { + Kind: &structpb.Value_NumberValue{ + NumberValue: 4, + }, + }, + { + Kind: &structpb.Value_NumberValue{ + NumberValue: 4, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(`{"apiVersion":"","kind":"","status": {"atFunction": ` + + `{"cidr": {"10.10.0.0/24": ["10.10.0.0/32", "10.10.0.16/28", "10.10.0.64/26"],` + + `"10.12.0.0/24": ["10.12.0.0/28", "10.12.0.16/28", "10.12.0.32/28"]}}}}`), + }, + }, + Meta: &fnv1beta1.ResponseMeta{ + Ttl: &durationpb.Duration{ + Seconds: 60, + }, + }, + }, + err: nil, + }, + }, + } for name, tc := range cases { t.Run(name, func(t *testing.T) { diff --git a/input/v1beta1/parameters.go b/input/v1beta1/parameters.go index 28103e0..6f48383 100644 --- a/input/v1beta1/parameters.go +++ b/input/v1beta1/parameters.go @@ -11,7 +11,40 @@ import ( // This isn't a custom resource, in the sense that we never install its CRD. // It is a KRM-like object, so we generate a CRD to describe its schema. +// MultiPrefix defines an item in a list of CIDR blocks to NewBits mappings +type MultiPrefix struct { + // Prefix is a CIDR block that is used as input for CIDR calculations + // + // +required + // +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}/[0-9]{1,2}$" + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Required + Prefix string `json:"prefix"` + + // NewBits is a list of bits to allocate to the subnet + // + // +required + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:Required + // +listType=atomic + NewBits []int `json:"newBits"` + + // Offset is the number of bits to offset the subnet mask by when generating + // subnets. + // + // +optional + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=32 + // +kubebuilder:default=0 + Offset int `json:"offset,omitempty"` +} + // Parameters can be used to provide input to this Function. +// +// Almost all parameters can be provided as literals or as references to +// fields on the claim, allowing defaults to be set in the composition and then +// overridden by the claim. +// // +kubebuilder:object:root=true // +kubebuilder:storageversion // +kubebuilder:resource:categories=crossplane @@ -19,51 +52,127 @@ type Parameters struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // cidrfunc is one of cidrhost, cidrnetmast, cidesubnet, cidrsubnets, cidrsubnetloop + // cidrFunc is the name of the function to call + // + // +optional + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Enum={cidrhost,cidrnetmask,cidrsubnet,cidrsubnets,cidrsubnetloop,multiprefixloop} CidrFunc string `json:"cidrFunc"` - // prefix field + // cidrFuncField is a reference to a location on the claim specifying the + // cidrFunc to call + // + // +optional + // +kubebuilder:validation:Type=string + CidrFuncField string `json:"cidrFuncField,omitempty"` + + // multiPrefix is a list of CIDR blocks to NewBits mappings that are used as + // input for the `multiprefixloop` function. + // + // +optional + MultiPrefix []MultiPrefix `json:"multiPrefix,omitempty"` + + // multiPrefixField describes a location on the claim that contains the + // multiPrefix to use as input for the `multiprefixloop` function. + // + // The location referenced should contain a list of MultiPrefix objects. + // + // +optional + MultiPrefixField string `json:"multiPrefixField,omitempty"` + + // prefixField defines a location on the claim to take the prefix from + // + // +optional PrefixField string `json:"prefixField,omitempty"` // prefix is a CIDR block that is used as input for CIDR calculations - Prefix string `json:"prefix"` + // + // +optional + Prefix string `json:"prefix,omitempty"` - // hostnum field + // hostNumField points to a field on the claim that contains the hostNum + // + // +optional HostNumField string `json:"hostNumField,omitempty"` - // hostnum + // hostNum is a whole number that can be represented as a binary integer + // with no more than the number of digits remaining in the address after + // the given prefix. + // + // +optional HostNum int `json:"hostNum,omitempty"` - // newbits field + // newbitsField points to a field on the claim that contains the newBits + // + // +optional NewBitsField string `json:"newBitsField,omitempty"` - // newbits + // newbits is the number of additional bits with which to extend the prefix. + // For example, if given a prefix ending in /16 and a newbits value of 4, + // the resulting subnet address will have length /20. + // + // +optional NewBits []int `json:"newBits,omitempty"` - // netnum field + // netNumField points to a field on the claim that contains the netNum + // + // +optional NetNumField string `json:"netNumField,omitempty"` - // netnum + // netNum is a whole number that can be represented as a binary integer with + // no more than newbits binary digits, which will be used to populate the + // additional bits added to the prefix. + // + // +optional NetNum int64 `json:"netNum,omitempty"` - // netnumcount field + // netNumCountField points to a field on the claim that contains the + // netNumCount + // + // +optional NetNumCountField string `json:"netNumCountField,omitempty"` - // netnumcount + // netNumCount defines how many networks to create from the given prefix + // + // +optional NetNumCount int64 `json:"netNumCount,omitempty"` - // netnumitems field + // netNumItemsField points to a field on the claim that contains the + // netNumItems + // + // +optional NetNumItemsField string `json:"netNumItemsField,omitempty"` - // netnumitems + // netNumItems is an array of items whose length may be used to determine + // how many networks to create from the given prefix. + // + // When this field is defined, its length is compared against `netNumCount` + // and the larger of the two values is used. + // + // +optional NetNumItems []string `json:"netNumItems,omitempty"` - // offset field + // offsetField defines a location on the claim to take the offset from + // + // This field is mutually exclusive with netNumCount and netNumItems + // + // +optional OffsetField string `json:"offsetField,omitempty"` - // offset is only used by cidrsubnetloop + // offset defines a starting point in the cidr block to start allocating + // subnets from. If 0, will start from the beginning of the prefix. + // + // This field is mutually exclusive with netNumCount and netNumItems + // + // +optional Offset int `json:"offset,omitempty"` - // output field + // outputField specifies a location on the XR to patch the results of the + // function call to. + // + // If this field is not specified, the results will be patched to the status + // field `status.atFunction.cidr`. + // + // +optional OutputField string `json:"outputField,omitempty"` } diff --git a/input/v1beta1/zz_generated.deepcopy.go b/input/v1beta1/zz_generated.deepcopy.go index 6d8e5d9..3f8565e 100644 --- a/input/v1beta1/zz_generated.deepcopy.go +++ b/input/v1beta1/zz_generated.deepcopy.go @@ -8,11 +8,38 @@ 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 *MultiPrefix) DeepCopyInto(out *MultiPrefix) { + *out = *in + if in.NewBits != nil { + in, out := &in.NewBits, &out.NewBits + *out = make([]int, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MultiPrefix. +func (in *MultiPrefix) DeepCopy() *MultiPrefix { + if in == nil { + return nil + } + out := new(MultiPrefix) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Parameters) DeepCopyInto(out *Parameters) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.MultiPrefix != nil { + in, out := &in.MultiPrefix, &out.MultiPrefix + *out = make([]MultiPrefix, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.NewBits != nil { in, out := &in.NewBits, &out.NewBits *out = make([]int, len(*in)) diff --git a/package/input/cidr.fn.crossplane.io_parameters.yaml b/package/input/cidr.fn.crossplane.io_parameters.yaml index 5dcdb77..fa43cf8 100644 --- a/package/input/cidr.fn.crossplane.io_parameters.yaml +++ b/package/input/cidr.fn.crossplane.io_parameters.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: parameters.cidr.fn.crossplane.io spec: group: cidr.fn.crossplane.io @@ -19,78 +19,177 @@ spec: - name: v1beta1 schema: openAPIV3Schema: - description: Parameters can be used to provide input to this Function. + description: |- + Parameters can be used to provide input to this Function. + + + Almost all parameters can be provided as literals or as references to + fields on the claim, allowing defaults to be set in the composition and then + overridden by the claim. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string cidrFunc: - description: cidrfunc is one of cidrhost, cidrnetmast, cidesubnet, cidrsubnets, - cidrsubnetloop + description: cidrFunc is the name of the function to call + enum: + - cidrhost + - cidrnetmask + - cidrsubnet + - cidrsubnets + - cidrsubnetloop + - multiprefixloop + type: string + cidrFuncField: + description: |- + cidrFuncField is a reference to a location on the claim specifying the + cidrFunc to call type: string hostNum: - description: hostnum + description: |- + hostNum is a whole number that can be represented as a binary integer + with no more than the number of digits remaining in the address after + the given prefix. type: integer hostNumField: - description: hostnum field + description: hostNumField points to a field on the claim that contains + the hostNum type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object + multiPrefix: + description: |- + multiPrefix is a list of CIDR blocks to NewBits mappings that are used as + input for the `multiprefixloop` function. + items: + description: MultiPrefix defines an item in a list of CIDR blocks to + NewBits mappings + properties: + newBits: + description: NewBits is a list of bits to allocate to the subnet + items: + type: integer + minItems: 1 + type: array + x-kubernetes-list-type: atomic + offset: + default: 0 + description: |- + Offset is the number of bits to offset the subnet mask by when generating + subnets. + maximum: 32 + minimum: 0 + type: integer + prefix: + description: Prefix is a CIDR block that is used as input for CIDR + calculations + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}/[0-9]{1,2}$ + type: string + required: + - newBits + - prefix + type: object + type: array + multiPrefixField: + description: |- + multiPrefixField describes a location on the claim that contains the + multiPrefix to use as input for the `multiprefixloop` function. + + + The location referenced should contain a list of MultiPrefix objects. + type: string netNum: - description: netnum + description: |- + netNum is a whole number that can be represented as a binary integer with + no more than newbits binary digits, which will be used to populate the + additional bits added to the prefix. format: int64 type: integer netNumCount: - description: netnumcount + description: netNumCount defines how many networks to create from the + given prefix format: int64 type: integer netNumCountField: - description: netnumcount field + description: |- + netNumCountField points to a field on the claim that contains the + netNumCount type: string netNumField: - description: netnum field + description: netNumField points to a field on the claim that contains + the netNum type: string netNumItems: - description: netnumitems + description: |- + netNumItems is an array of items whose length may be used to determine + how many networks to create from the given prefix. + + + When this field is defined, its length is compared against `netNumCount` + and the larger of the two values is used. items: type: string type: array netNumItemsField: - description: netnumitems field + description: |- + netNumItemsField points to a field on the claim that contains the + netNumItems type: string newBits: - description: newbits + description: |- + newbits is the number of additional bits with which to extend the prefix. + For example, if given a prefix ending in /16 and a newbits value of 4, + the resulting subnet address will have length /20. items: type: integer type: array newBitsField: - description: newbits field + description: newbitsField points to a field on the claim that contains + the newBits type: string offset: - description: offset is only used by cidrsubnetloop + description: |- + offset defines a starting point in the cidr block to start allocating + subnets from. If 0, will start from the beginning of the prefix. + + + This field is mutually exclusive with netNumCount and netNumItems type: integer offsetField: - description: offset field + description: |- + offsetField defines a location on the claim to take the offset from + + + This field is mutually exclusive with netNumCount and netNumItems type: string outputField: - description: output field + description: |- + outputField specifies a location on the XR to patch the results of the + function call to. + + + If this field is not specified, the results will be patched to the status + field `status.atFunction.cidr`. type: string prefix: description: prefix is a CIDR block that is used as input for CIDR calculations type: string prefixField: - description: prefix field + description: prefixField defines a location on the claim to take the prefix + from type: string - required: - - cidrFunc - - prefix type: object served: true storage: true diff --git a/validate.go b/validate.go index 2e7765d..5198d2c 100644 --- a/validate.go +++ b/validate.go @@ -127,23 +127,59 @@ func ValidateCidrSubnetloopParameters(p *v1beta1.Parameters) *field.Error { return nil } +func ValidateMultiCidrPrefixParameter(p *v1beta1.Parameters, oxr *resource.Composite) *field.Error { + if len(p.MultiPrefix) > 0 && len(p.MultiPrefixField) > 0 { + return field.Required(field.NewPath("parameters"), "specify only one of multiPrefix or multiPrefixField to avoid ambiguous function input") + } + + if len(p.MultiPrefix) == 0 && p.MultiPrefixField == "" { + return field.Required(field.NewPath("parameters"), "either multiPrefix or multiPrefixField function input is required") + } + + var multiPrefixes []v1beta1.MultiPrefix = p.MultiPrefix + if len(p.MultiPrefix) == 0 { + err := oxr.Resource.GetValueInto(p.MultiPrefixField, &multiPrefixes) + if err != nil { + return field.Required(field.NewPath("parameters"), "cannot get multiPrefixes at multiPrefixField "+p.MultiPrefixField) + } + } + + for _, mp := range multiPrefixes { + _, _, err := net.ParseCIDR(mp.Prefix) + if err != nil { + return field.Required(field.NewPath("parameters"), "invalid CIDR prefix address "+mp.Prefix) + } + + if len(mp.NewBits) == 0 { + return field.Required(field.NewPath("parameters"), "newBits is required for each prefix in multiPrefixField") + } + } + + return nil +} + // ValidateParameters validates the Parameters object. func ValidateParameters(p *v1beta1.Parameters, oxr *resource.Composite) *field.Error { - if p.CidrFunc == "" { - return field.Required(field.NewPath("parameters"), "cidrFunc is required") - } + var cidrFunc string = p.CidrFunc + var err error - fieldError := ValidatePrefixParameter(p.Prefix, p.PrefixField, oxr) - if fieldError != nil { - return fieldError + if p.CidrFuncField != "" { + cidrFunc, err = oxr.Resource.GetString(p.CidrFuncField) + if err != nil { + return field.Required(field.NewPath("parameters"), "cannot get cidrFunc at cidrFuncField "+p.CidrFuncField) + } } - cidrFunc, err := oxr.Resource.GetString(p.CidrFunc) - if err != nil { - return field.Required(field.NewPath("parameters"), "cidrFunc is required") + if cidrFunc != "multiprefixloop" { + fieldError := ValidatePrefixParameter(p.Prefix, p.PrefixField, oxr) + if fieldError != nil { + return fieldError + } } switch cidrFunc { + case "": + return field.Required(field.NewPath("parameters"), "cidrFunc is required") case "cidrhost": return ValidateCidrHostParameters(p, *oxr) case "cidrnetmask": @@ -154,7 +190,9 @@ func ValidateParameters(p *v1beta1.Parameters, oxr *resource.Composite) *field.E return ValidateCidrSubnetsParameters(p, *oxr) case "cidrsubnetloop": return ValidateCidrSubnetloopParameters(p) + case "multiprefixloop": + return ValidateMultiCidrPrefixParameter(p, oxr) default: - return field.Required(field.NewPath("parameters"), "unexpected cidrFunc "+p.CidrFunc) + return field.Required(field.NewPath("parameters"), "unexpected cidrFunc "+cidrFunc) } }