From e6e9620be44454589c9325e872607bfa63cc483a Mon Sep 17 00:00:00 2001 From: vaspahomov Date: Tue, 22 Jun 2021 14:26:50 +0500 Subject: [PATCH] NetworkInterface support Signed-off-by: vaspahomov --- apis/network/v1alpha3/referencers.go | 65 ++++ apis/network/v1alpha3/register.go | 9 + apis/network/v1alpha3/types.go | 106 ++++++ .../network/v1alpha3/zz_generated.deepcopy.go | 178 ++++++++++ apis/network/v1alpha3/zz_generated.managed.go | 56 ++++ .../v1alpha3/zz_generated.managedlist.go | 9 + examples/network/networkinterface.yaml | 18 + ...azure.crossplane.io_networkinterfaces.yaml | 253 ++++++++++++++ pkg/clients/network/fake/fake.go | 32 ++ pkg/clients/network/network.go | 38 +++ pkg/clients/network/network_test.go | 101 ++++++ pkg/controller/azure.go | 2 + .../network/networkinterface/managed.go | 147 +++++++++ .../network/networkinterface/managed_test.go | 311 ++++++++++++++++++ 14 files changed, 1325 insertions(+) create mode 100644 examples/network/networkinterface.yaml create mode 100644 package/crds/network.azure.crossplane.io_networkinterfaces.yaml create mode 100644 pkg/controller/network/networkinterface/managed.go create mode 100644 pkg/controller/network/networkinterface/managed_test.go diff --git a/apis/network/v1alpha3/referencers.go b/apis/network/v1alpha3/referencers.go index eb680cf5..0907b29d 100644 --- a/apis/network/v1alpha3/referencers.go +++ b/apis/network/v1alpha3/referencers.go @@ -40,6 +40,18 @@ func SubnetID() reference.ExtractValueFn { } } +// PublicIPAddressID extracts status.ID from the supplied managed resource, which must be +// a PublicIPAddress. +func PublicIPAddressID() reference.ExtractValueFn { + return func(mg resource.Managed) string { + s, ok := mg.(*PublicIPAddress) + if !ok { + return "" + } + return s.Status.ID + } +} + // ResolveReferences of this VirtualNetwork func (mg *VirtualNetwork) ResolveReferences(ctx context.Context, c client.Reader) error { r := reference.NewAPIResolver(c, mg) @@ -116,3 +128,56 @@ func (mg *PublicIPAddress) ResolveReferences(ctx context.Context, c client.Reade return nil } + +// ResolveReferences of this PublicIPAddress +func (mg *NetworkInterface) ResolveReferences(ctx context.Context, c client.Reader) error { + r := reference.NewAPIResolver(c, mg) + + // Resolve spec.resourceGroupName + rsp, err := r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: mg.Spec.ResourceGroupName, + Reference: mg.Spec.ResourceGroupNameRef, + Selector: mg.Spec.ResourceGroupNameSelector, + To: reference.To{Managed: &v1alpha3.ResourceGroup{}, List: &v1alpha3.ResourceGroupList{}}, + Extract: reference.ExternalName(), + }) + if err != nil { + return errors.Wrap(err, "spec.resourceGroupName") + } + mg.Spec.ResourceGroupName = rsp.ResolvedValue + mg.Spec.ResourceGroupNameRef = rsp.ResolvedReference + + // Resolve spec.properties.interfaceIPConfigurations[].publicIPAddress + for i, iface := range mg.Spec.NetworkInterfaceFormat.IPConfigurations { + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: iface.PublicIPAddressID, + Reference: iface.PublicIPAddressIDRef, + Selector: iface.PublicIPAddressIDSelector, + To: reference.To{Managed: &PublicIPAddress{}, List: &PublicIPAddressList{}}, + Extract: PublicIPAddressID(), + }) + if err != nil { + return errors.Wrap(err, "spec.properties.interfaceIPConfigurations[].publicIPAddress") + } + mg.Spec.NetworkInterfaceFormat.IPConfigurations[i].PublicIPAddressID = rsp.ResolvedValue + mg.Spec.NetworkInterfaceFormat.IPConfigurations[i].PublicIPAddressIDRef = rsp.ResolvedReference + } + + // Resolve spec.properties.interfaceIPConfigurations[].subnet + for i, iface := range mg.Spec.NetworkInterfaceFormat.IPConfigurations { + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: iface.SubnetID, + Reference: iface.SubnetIDRef, + Selector: iface.SubnetIDSelector, + To: reference.To{Managed: &Subnet{}, List: &SubnetList{}}, + Extract: SubnetID(), + }) + if err != nil { + return errors.Wrap(err, "spec.properties.interfaceIPConfigurations[].subnet") + } + mg.Spec.NetworkInterfaceFormat.IPConfigurations[i].SubnetID = rsp.ResolvedValue + mg.Spec.NetworkInterfaceFormat.IPConfigurations[i].SubnetIDRef = rsp.ResolvedReference + } + + return nil +} diff --git a/apis/network/v1alpha3/register.go b/apis/network/v1alpha3/register.go index 0e8f4d2a..46109da1 100644 --- a/apis/network/v1alpha3/register.go +++ b/apis/network/v1alpha3/register.go @@ -61,8 +61,17 @@ var ( PublicIPAddressGroupVersionKind = SchemeGroupVersion.WithKind(PublicIPAddressKind) ) +// PublicIpAddress type metadata. +var ( + NetworkInterfaceKind = reflect.TypeOf(NetworkInterface{}).Name() + NetworkInterfaceGroupKind = schema.GroupKind{Group: Group, Kind: NetworkInterfaceKind}.String() + NetworkInterfaceKindAPIVersion = NetworkInterfaceKind + "." + SchemeGroupVersion.String() + NetworkInterfaceGroupVersionKind = SchemeGroupVersion.WithKind(NetworkInterfaceKind) +) + func init() { SchemeBuilder.Register(&VirtualNetwork{}, &VirtualNetworkList{}) SchemeBuilder.Register(&Subnet{}, &SubnetList{}) SchemeBuilder.Register(&PublicIPAddress{}, &PublicIPAddressList{}) + SchemeBuilder.Register(&NetworkInterface{}, &NetworkInterfaceList{}) } diff --git a/apis/network/v1alpha3/types.go b/apis/network/v1alpha3/types.go index 315e19e1..5ab2d877 100644 --- a/apis/network/v1alpha3/types.go +++ b/apis/network/v1alpha3/types.go @@ -318,3 +318,109 @@ type PublicIPAddressList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []PublicIPAddress `json:"items"` } + +// A NetworkInterfaceSpec defines the desired state of a NetworkInterface. +type NetworkInterfaceSpec struct { + xpv1.ResourceSpec `json:",inline"` + + // ResourceGroupName - Name of the Public IP address's resource group. + ResourceGroupName string `json:"resourceGroupName,omitempty"` + + // ResourceGroupNameRef - A reference to the the Public IP address's resource + // group. + ResourceGroupNameRef *xpv1.Reference `json:"resourceGroupNameRef,omitempty"` + + // ResourceGroupNameSelector - Select a reference to the the Public IP address's + // resource group. + ResourceGroupNameSelector *xpv1.Selector `json:"resourceGroupNameSelector,omitempty"` + + // NetworkInterfaceFormat - Properties of the NetworkInterface. + NetworkInterfaceFormat NetworkInterfaceFormat `json:"properties"` + + // Tags - Resource tags. + // +optional + Tags map[string]*string `json:"tags,omitempty"` +} + +// NetworkInterfaceFormat defines properties of the NetworkInterface. +type NetworkInterfaceFormat struct { + // Location - Resource location. + Location string `json:"location"` + + // IPConfigurations - A list of IPConfigurations of the network interface. + IPConfigurations []*InterfaceIPConfiguration `json:"iPConfigurations"` +} + +// A NetworkInterfaceStatus represents the observed state of a NetworkInterface. +type NetworkInterfaceStatus struct { + xpv1.ResourceStatus `json:",inline"` + + // State of this PublicIPAddress. + State string `json:"state,omitempty"` + + // A Message providing detail about the state of this PublicIPAddress, if any. + Message string `json:"message,omitempty"` + + // Etag - A unique string that changes whenever the resource is updated. + Etag string `json:"etag,omitempty"` + + // ID of this PublicIPAddress. + ID string `json:"id,omitempty"` +} + +// +kubebuilder:object:root=true + +// A NetworkInterface is a managed resource that represents an Azure NetworkInterface. +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="STATE",type="string",JSONPath=".status.state" +// +kubebuilder:printcolumn:name="LOCATION",type="string",JSONPath=".spec.properties.location" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,azure} +type NetworkInterface struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NetworkInterfaceSpec `json:"spec"` + Status NetworkInterfaceStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// NetworkInterfaceList contains a list of NetworkInterface items +type NetworkInterfaceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NetworkInterface `json:"items"` +} + +// InterfaceIPConfiguration defines properties of a service endpoint. +type InterfaceIPConfiguration struct { + Name string `json:"name"` + + // PublicIPAddressID - Name of the Network Interface's Public IP address. + PublicIPAddressID string `json:"publicIPAddressID,omitempty"` + + // PublicIPAddressIDRef - A reference to the the Network Interface's Public IP address. + PublicIPAddressIDRef *xpv1.Reference `json:"publicIPAddressIDRef,omitempty"` + + // PublicIPAddressIDSelector - Select a reference to the Network Interface's Public IP address. + PublicIPAddressIDSelector *xpv1.Selector `json:"publicIPAddressIDSelector,omitempty"` + + // SubnetID - Name of the Network Interface's Subnet. + SubnetID string `json:"subnetID,omitempty"` + + // SubnetIDRef - A reference to the the Network Interface's Subnet. + SubnetIDRef *xpv1.Reference `json:"subnetIDRef,omitempty"` + + // SubnetIDSelector - Select a reference to the Network Interface's Subnet. + SubnetIDSelector *xpv1.Selector `json:"subnetIDSelector,omitempty"` + + // Primary - Gets whether this is a primary customer address on the network interface. + Primary bool `json:"primary,omitempty"` + + // ProvisioningState - The provisioning state of the resource. + // +optional + ProvisioningState string `json:"provisioningState,omitempty"` +} diff --git a/apis/network/v1alpha3/zz_generated.deepcopy.go b/apis/network/v1alpha3/zz_generated.deepcopy.go index 1fdaa5e1..95b726c6 100644 --- a/apis/network/v1alpha3/zz_generated.deepcopy.go +++ b/apis/network/v1alpha3/zz_generated.deepcopy.go @@ -45,6 +45,184 @@ func (in *AddressSpace) DeepCopy() *AddressSpace { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InterfaceIPConfiguration) DeepCopyInto(out *InterfaceIPConfiguration) { + *out = *in + if in.PublicIPAddressIDRef != nil { + in, out := &in.PublicIPAddressIDRef, &out.PublicIPAddressIDRef + *out = new(v1.Reference) + **out = **in + } + if in.PublicIPAddressIDSelector != nil { + in, out := &in.PublicIPAddressIDSelector, &out.PublicIPAddressIDSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } + if in.SubnetIDRef != nil { + in, out := &in.SubnetIDRef, &out.SubnetIDRef + *out = new(v1.Reference) + **out = **in + } + if in.SubnetIDSelector != nil { + in, out := &in.SubnetIDSelector, &out.SubnetIDSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InterfaceIPConfiguration. +func (in *InterfaceIPConfiguration) DeepCopy() *InterfaceIPConfiguration { + if in == nil { + return nil + } + out := new(InterfaceIPConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkInterface) DeepCopyInto(out *NetworkInterface) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkInterface. +func (in *NetworkInterface) DeepCopy() *NetworkInterface { + if in == nil { + return nil + } + out := new(NetworkInterface) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NetworkInterface) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkInterfaceFormat) DeepCopyInto(out *NetworkInterfaceFormat) { + *out = *in + if in.IPConfigurations != nil { + in, out := &in.IPConfigurations, &out.IPConfigurations + *out = make([]*InterfaceIPConfiguration, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(InterfaceIPConfiguration) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkInterfaceFormat. +func (in *NetworkInterfaceFormat) DeepCopy() *NetworkInterfaceFormat { + if in == nil { + return nil + } + out := new(NetworkInterfaceFormat) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkInterfaceList) DeepCopyInto(out *NetworkInterfaceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NetworkInterface, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkInterfaceList. +func (in *NetworkInterfaceList) DeepCopy() *NetworkInterfaceList { + if in == nil { + return nil + } + out := new(NetworkInterfaceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NetworkInterfaceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkInterfaceSpec) DeepCopyInto(out *NetworkInterfaceSpec) { + *out = *in + in.ResourceSpec.DeepCopyInto(&out.ResourceSpec) + if in.ResourceGroupNameRef != nil { + in, out := &in.ResourceGroupNameRef, &out.ResourceGroupNameRef + *out = new(v1.Reference) + **out = **in + } + if in.ResourceGroupNameSelector != nil { + in, out := &in.ResourceGroupNameSelector, &out.ResourceGroupNameSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } + in.NetworkInterfaceFormat.DeepCopyInto(&out.NetworkInterfaceFormat) + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make(map[string]*string, len(*in)) + for key, val := range *in { + var outVal *string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(string) + **out = **in + } + (*out)[key] = outVal + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkInterfaceSpec. +func (in *NetworkInterfaceSpec) DeepCopy() *NetworkInterfaceSpec { + if in == nil { + return nil + } + out := new(NetworkInterfaceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkInterfaceStatus) DeepCopyInto(out *NetworkInterfaceStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkInterfaceStatus. +func (in *NetworkInterfaceStatus) DeepCopy() *NetworkInterfaceStatus { + if in == nil { + return nil + } + out := new(NetworkInterfaceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PublicIPAddress) DeepCopyInto(out *PublicIPAddress) { *out = *in diff --git a/apis/network/v1alpha3/zz_generated.managed.go b/apis/network/v1alpha3/zz_generated.managed.go index 222bcd86..c782471d 100644 --- a/apis/network/v1alpha3/zz_generated.managed.go +++ b/apis/network/v1alpha3/zz_generated.managed.go @@ -20,6 +20,62 @@ package v1alpha3 import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" +// GetCondition of this NetworkInterface. +func (mg *NetworkInterface) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetDeletionPolicy of this NetworkInterface. +func (mg *NetworkInterface) GetDeletionPolicy() xpv1.DeletionPolicy { + return mg.Spec.DeletionPolicy +} + +// GetProviderConfigReference of this NetworkInterface. +func (mg *NetworkInterface) GetProviderConfigReference() *xpv1.Reference { + return mg.Spec.ProviderConfigReference +} + +/* +GetProviderReference of this NetworkInterface. +Deprecated: Use GetProviderConfigReference. +*/ +func (mg *NetworkInterface) GetProviderReference() *xpv1.Reference { + return mg.Spec.ProviderReference +} + +// GetWriteConnectionSecretToReference of this NetworkInterface. +func (mg *NetworkInterface) GetWriteConnectionSecretToReference() *xpv1.SecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this NetworkInterface. +func (mg *NetworkInterface) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetDeletionPolicy of this NetworkInterface. +func (mg *NetworkInterface) SetDeletionPolicy(r xpv1.DeletionPolicy) { + mg.Spec.DeletionPolicy = r +} + +// SetProviderConfigReference of this NetworkInterface. +func (mg *NetworkInterface) SetProviderConfigReference(r *xpv1.Reference) { + mg.Spec.ProviderConfigReference = r +} + +/* +SetProviderReference of this NetworkInterface. +Deprecated: Use SetProviderConfigReference. +*/ +func (mg *NetworkInterface) SetProviderReference(r *xpv1.Reference) { + mg.Spec.ProviderReference = r +} + +// SetWriteConnectionSecretToReference of this NetworkInterface. +func (mg *NetworkInterface) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} + // GetCondition of this PublicIPAddress. func (mg *PublicIPAddress) GetCondition(ct xpv1.ConditionType) xpv1.Condition { return mg.Status.GetCondition(ct) diff --git a/apis/network/v1alpha3/zz_generated.managedlist.go b/apis/network/v1alpha3/zz_generated.managedlist.go index ef02beac..dd28bcf4 100644 --- a/apis/network/v1alpha3/zz_generated.managedlist.go +++ b/apis/network/v1alpha3/zz_generated.managedlist.go @@ -20,6 +20,15 @@ package v1alpha3 import resource "github.com/crossplane/crossplane-runtime/pkg/resource" +// GetItems of this NetworkInterfaceList. +func (l *NetworkInterfaceList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} + // GetItems of this PublicIPAddressList. func (l *PublicIPAddressList) GetItems() []resource.Managed { items := make([]resource.Managed, len(l.Items)) diff --git a/examples/network/networkinterface.yaml b/examples/network/networkinterface.yaml new file mode 100644 index 00000000..b914daf8 --- /dev/null +++ b/examples/network/networkinterface.yaml @@ -0,0 +1,18 @@ +apiVersion: network.azure.crossplane.io/v1alpha3 +kind: NetworkInterface +metadata: + name: example-network-interface +spec: + resourceGroupNameRef: + name: example-rg + properties: + interfaceIPConfigurations: + - name: example-ip-conf + primary: true + subnetIDRef: + name: example-sub + publicIPAddressIDRef: + name: example-public-ip-address + location: West US 2 + providerConfigRef: + name: example diff --git a/package/crds/network.azure.crossplane.io_networkinterfaces.yaml b/package/crds/network.azure.crossplane.io_networkinterfaces.yaml new file mode 100644 index 00000000..baa33370 --- /dev/null +++ b/package/crds/network.azure.crossplane.io_networkinterfaces.yaml @@ -0,0 +1,253 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: networkinterfaces.network.azure.crossplane.io +spec: + group: network.azure.crossplane.io + names: + categories: + - crossplane + - managed + - azure + kind: NetworkInterface + listKind: NetworkInterfaceList + plural: networkinterfaces + singular: networkinterface + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .status.state + name: STATE + type: string + - jsonPath: .spec.properties.location + name: LOCATION + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha3 + schema: + openAPIV3Schema: + description: A NetworkInterface is a managed resource that represents an Azure NetworkInterface. + 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' + 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' + type: string + metadata: + type: object + spec: + description: A NetworkInterfaceSpec defines the desired state of a NetworkInterface. + properties: + deletionPolicy: + description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + enum: + - Orphan + - Delete + type: string + properties: + description: NetworkInterfaceFormat - Properties of the NetworkInterface. + properties: + iPConfigurations: + description: IPConfigurations - A list of IPConfigurations of the network interface. + items: + description: InterfaceIPConfiguration defines properties of a service endpoint. + properties: + name: + type: string + primary: + description: Primary - Gets whether this is a primary customer address on the network interface. + type: boolean + provisioningState: + description: ProvisioningState - The provisioning state of the resource. + type: string + publicIPAddressID: + description: PublicIPAddressID - Name of the Network Interface's Public IP address. + type: string + publicIPAddressIDRef: + description: PublicIPAddressIDRef - A reference to the the Network Interface's Public IP address. + properties: + name: + description: Name of the referenced object. + type: string + required: + - name + type: object + publicIPAddressIDSelector: + description: PublicIPAddressIDSelector - Select a reference to the Network Interface's Public IP address. + properties: + matchControllerRef: + description: MatchControllerRef ensures an object with the same controller reference as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels is selected. + type: object + type: object + subnetID: + description: SubnetID - Name of the Network Interface's Subnet. + type: string + subnetIDRef: + description: SubnetIDRef - A reference to the the Network Interface's Subnet. + properties: + name: + description: Name of the referenced object. + type: string + required: + - name + type: object + subnetIDSelector: + description: SubnetIDSelector - Select a reference to the Network Interface's Subnet. + properties: + matchControllerRef: + description: MatchControllerRef ensures an object with the same controller reference as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels is selected. + type: object + type: object + required: + - name + type: object + type: array + location: + description: Location - Resource location. + type: string + required: + - iPConfigurations + - location + type: object + providerConfigRef: + description: ProviderConfigReference specifies how the provider that will be used to create, observe, update, and delete this managed resource should be configured. + properties: + name: + description: Name of the referenced object. + type: string + required: + - name + type: object + providerRef: + description: 'ProviderReference specifies the provider that will be used to create, observe, update, and delete this managed resource. Deprecated: Please use ProviderConfigReference, i.e. `providerConfigRef`' + properties: + name: + description: Name of the referenced object. + type: string + required: + - name + type: object + resourceGroupName: + description: ResourceGroupName - Name of the Public IP address's resource group. + type: string + resourceGroupNameRef: + description: ResourceGroupNameRef - A reference to the the Public IP address's resource group. + properties: + name: + description: Name of the referenced object. + type: string + required: + - name + type: object + resourceGroupNameSelector: + description: ResourceGroupNameSelector - Select a reference to the the Public IP address's resource group. + properties: + matchControllerRef: + description: MatchControllerRef ensures an object with the same controller reference as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels is selected. + type: object + type: object + tags: + additionalProperties: + type: string + description: Tags - Resource tags. + type: object + writeConnectionSecretToRef: + description: WriteConnectionSecretToReference specifies the namespace and name of a Secret to which any connection details for this managed resource should be written. Connection details frequently include the endpoint, username, and password required to connect to the managed resource. + properties: + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - name + - namespace + type: object + required: + - properties + type: object + status: + description: A NetworkInterfaceStatus represents the observed state of a NetworkInterface. + properties: + conditions: + description: Conditions of the resource. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: LastTransitionTime is the last time this condition transitioned from one status to another. + format: date-time + type: string + message: + description: A Message containing details about this condition's last transition from one status to another, if any. + type: string + reason: + description: A Reason for this condition's last transition from one status to another. + type: string + status: + description: Status of this condition; is it currently True, False, or Unknown? + type: string + type: + description: Type of this condition. At most one of each condition type may apply to a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + etag: + description: Etag - A unique string that changes whenever the resource is updated. + type: string + id: + description: ID of this PublicIPAddress. + type: string + message: + description: A Message providing detail about the state of this PublicIPAddress, if any. + type: string + state: + description: State of this PublicIPAddress. + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/pkg/clients/network/fake/fake.go b/pkg/clients/network/fake/fake.go index 20e9e8dc..9810bec4 100644 --- a/pkg/clients/network/fake/fake.go +++ b/pkg/clients/network/fake/fake.go @@ -118,3 +118,35 @@ func (c *MockPublicIPAddressClient) Get(ctx context.Context, resourceGroupName s func (c *MockPublicIPAddressClient) List(ctx context.Context, resourceGroupName string) (result network.PublicIPAddressListResultPage, err error) { return c.MockList(ctx, resourceGroupName) } + +var _ networkapi.InterfacesClientAPI = &MockNetworkInterfaceClient{} + +// MockNetworkInterfaceClient is a fake implementation of network.PublicIPAddressClient. +type MockNetworkInterfaceClient struct { + networkapi.InterfacesClientAPI + + MockCreateOrUpdate func(ctx context.Context, resourceGroupName string, networkInterfaceName string, parameters network.Interface) (result network.InterfacesCreateOrUpdateFuture, err error) + MockDelete func(ctx context.Context, resourceGroupName string, networkInterfaceName string) (result network.InterfacesDeleteFuture, err error) + MockGet func(ctx context.Context, resourceGroupName string, networkInterfaceName string, expand string) (result network.Interface, err error) + MockList func(ctx context.Context, resourceGroupName string) (result network.InterfaceListResultPage, err error) +} + +// CreateOrUpdate calls the MockNetworkInterfaceClient's MockCreateOrUpdate method. +func (c *MockNetworkInterfaceClient) CreateOrUpdate(ctx context.Context, resourceGroupName string, networkInterfaceName string, parameters network.Interface) (result network.InterfacesCreateOrUpdateFuture, err error) { + return c.MockCreateOrUpdate(ctx, resourceGroupName, networkInterfaceName, parameters) +} + +// Delete calls the MockNetworkInterfaceClient's MockDelete method. +func (c *MockNetworkInterfaceClient) Delete(ctx context.Context, resourceGroupName string, networkInterfaceName string) (result network.InterfacesDeleteFuture, err error) { + return c.MockDelete(ctx, resourceGroupName, networkInterfaceName) +} + +// Get calls the MockNetworkInterfaceClient's MockGet method. +func (c *MockNetworkInterfaceClient) Get(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (result network.Interface, err error) { + return c.MockGet(ctx, resourceGroupName, publicIPAddressName, expand) +} + +// List calls the MockNetworkInterfaceClient's MockListKeys method. +func (c *MockNetworkInterfaceClient) List(ctx context.Context, resourceGroupName string) (result network.InterfaceListResultPage, err error) { + return c.MockList(ctx, resourceGroupName) +} diff --git a/pkg/clients/network/network.go b/pkg/clients/network/network.go index f18ad212..c5b6d02c 100644 --- a/pkg/clients/network/network.go +++ b/pkg/clients/network/network.go @@ -89,6 +89,17 @@ func NewPublicIPAddressParameters(s *v1alpha3.PublicIPAddress) networkmgmt.Publi } } +// NewNetworkInterfaceParameters returns an Azure NetworkInterface object from a network interface +func NewNetworkInterfaceParameters(s *v1alpha3.NetworkInterface) networkmgmt.Interface { + return networkmgmt.Interface{ + InterfacePropertiesFormat: &networkmgmt.InterfacePropertiesFormat{ + Primary: azure.ToBoolPtr(true), + IPConfigurations: NewInterfaceIPConfiguration(s), + }, + Location: azure.ToStringPtr(s.Spec.NetworkInterfaceFormat.Location), + } +} + // NewServiceEndpoints converts to Azure ServiceEndpointPropertiesFormat func NewServiceEndpoints(e []v1alpha3.ServiceEndpointPropertiesFormat) *[]networkmgmt.ServiceEndpointPropertiesFormat { endpoints := make([]networkmgmt.ServiceEndpointPropertiesFormat, len(e)) @@ -102,6 +113,25 @@ func NewServiceEndpoints(e []v1alpha3.ServiceEndpointPropertiesFormat) *[]networ return &endpoints } +// NewInterfaceIPConfiguration converts to Azure InterfaceIPConfiguration +func NewInterfaceIPConfiguration(s *v1alpha3.NetworkInterface) *[]networkmgmt.InterfaceIPConfiguration { + ifaces := s.Spec.NetworkInterfaceFormat.IPConfigurations + interfaces := make([]networkmgmt.InterfaceIPConfiguration, len(ifaces)) + + for i, iface := range ifaces { + interfaces[i] = networkmgmt.InterfaceIPConfiguration{ + Name: azure.ToStringPtr(iface.Name), + InterfaceIPConfigurationPropertiesFormat: &networkmgmt.InterfaceIPConfigurationPropertiesFormat{ + Primary: azure.ToBoolPtr(iface.Primary), + Subnet: &networkmgmt.Subnet{ID: azure.ToStringPtr(iface.SubnetID)}, + PublicIPAddress: &networkmgmt.PublicIPAddress{ID: azure.ToStringPtr(iface.PublicIPAddressID)}, + }, + } + } + + return &interfaces +} + // SubnetNeedsUpdate determines if a virtual network need to be updated func SubnetNeedsUpdate(kube *v1alpha3.Subnet, az networkmgmt.Subnet) bool { up := NewSubnetParameters(kube) @@ -126,3 +156,11 @@ func UpdatePublicIPAddressStatusFromAzure(v *v1alpha3.PublicIPAddress, az networ v.Status.ID = azure.ToString(az.ID) v.Status.Address = azure.ToString(az.IPAddress) } + +// UpdateNetworkInterfaceStatusFromAzure updates the status related to the external +// Azure network interface in the NetworkInterfaceStatus +func UpdateNetworkInterfaceStatusFromAzure(v *v1alpha3.NetworkInterface, az networkmgmt.Interface) { + v.Status.State = azure.ToString(az.InterfacePropertiesFormat.ProvisioningState) + v.Status.Etag = azure.ToString(az.Etag) + v.Status.ID = azure.ToString(az.ID) +} diff --git a/pkg/clients/network/network_test.go b/pkg/clients/network/network_test.go index da138fd1..c2e9275d 100644 --- a/pkg/clients/network/network_test.go +++ b/pkg/clients/network/network_test.go @@ -425,6 +425,43 @@ func TestNewPublicIPAddressParameters(t *testing.T) { } } +func TestNewNetworkInterfaceParameters(t *testing.T) { + cases := []struct { + name string + r *v1alpha3.NetworkInterface + want networkmgmt.Interface + }{ + { + name: "Successful", + r: &v1alpha3.NetworkInterface{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1alpha3.NetworkInterfaceSpec{ + NetworkInterfaceFormat: v1alpha3.NetworkInterfaceFormat{ + Location: "West US 2", + IPConfigurations: make([]*v1alpha3.InterfaceIPConfiguration, 0), + }, + }, + }, + want: networkmgmt.Interface{ + InterfacePropertiesFormat: &networkmgmt.InterfacePropertiesFormat{ + IPConfigurations: &[]networkmgmt.InterfaceIPConfiguration{}, + Primary: azure.ToBoolPtr(true), + }, + Location: azure.ToStringPtr("West US 2"), + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := NewNetworkInterfaceParameters(tc.r) + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("NewNetworkInterfaceParameters(...): -want, +got\n%s", diff) + } + }) + } +} + func TestNewServiceEndpoints(t *testing.T) { cases := []struct { name string @@ -647,3 +684,67 @@ func TestUpdatePublicIPAddressStatusFromAzure(t *testing.T) { }) } } + +func TestUpdateNetworkInterfaceStatusFromAzure(t *testing.T) { + mockCondition := xpv1.Condition{Message: "mockMessage"} + resourceStatus := xpv1.ResourceStatus{ + ConditionedStatus: xpv1.ConditionedStatus{ + Conditions: []xpv1.Condition{mockCondition}, + }, + } + + cases := []struct { + name string + r networkmgmt.Interface + want v1alpha3.NetworkInterfaceStatus + }{ + { + name: "SuccessfulFull", + r: networkmgmt.Interface{ + InterfacePropertiesFormat: &networkmgmt.InterfacePropertiesFormat{ProvisioningState: azure.ToStringPtr("Succeeded")}, + Etag: azure.ToStringPtr(etag), + ID: azure.ToStringPtr(id), + }, + want: v1alpha3.NetworkInterfaceStatus{ + State: string(networkmgmt.Succeeded), + ID: id, + Etag: etag, + }, + }, + { + name: "SuccessfulPartial", + r: networkmgmt.Interface{ + InterfacePropertiesFormat: &networkmgmt.InterfacePropertiesFormat{ProvisioningState: azure.ToStringPtr("Succeeded")}, + ID: azure.ToStringPtr(id), + }, + want: v1alpha3.NetworkInterfaceStatus{ + State: string(networkmgmt.Succeeded), + ID: id, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + v := &v1alpha3.NetworkInterface{ + Status: v1alpha3.NetworkInterfaceStatus{ + ResourceStatus: resourceStatus, + }, + } + + UpdateNetworkInterfaceStatusFromAzure(v, tc.r) + + // make sure that internal resource status hasn't changed + if diff := cmp.Diff(mockCondition, v.Status.ResourceStatus.Conditions[0]); diff != "" { + t.Errorf("UpdateNetworkInterfaceStatusFromAzure(...): -want, +got\n%s", diff) + } + + // make sure that other resource parameters are updated + tc.want.ResourceStatus = resourceStatus + if diff := cmp.Diff(tc.want, v.Status); diff != "" { + t.Errorf("UpdateNetworkInterfaceStatusFromAzure(...): -want, +got\n%s", diff) + } + }) + } +} diff --git a/pkg/controller/azure.go b/pkg/controller/azure.go index b356dbd6..876eb7a8 100644 --- a/pkg/controller/azure.go +++ b/pkg/controller/azure.go @@ -32,6 +32,7 @@ import ( "github.com/crossplane/provider-azure/pkg/controller/database/postgresqlserver" "github.com/crossplane/provider-azure/pkg/controller/database/postgresqlserverfirewallrule" "github.com/crossplane/provider-azure/pkg/controller/database/postgresqlservervirtualnetworkrule" + "github.com/crossplane/provider-azure/pkg/controller/network/networkinterface" "github.com/crossplane/provider-azure/pkg/controller/network/publicipaddress" "github.com/crossplane/provider-azure/pkg/controller/network/subnet" "github.com/crossplane/provider-azure/pkg/controller/network/virtualnetwork" @@ -54,6 +55,7 @@ func Setup(mgr ctrl.Manager, l logging.Logger, rl workqueue.RateLimiter) error { postgresqlservervirtualnetworkrule.Setup, cosmosdb.Setup, publicipaddress.Setup, + networkinterface.Setup, virtualnetwork.Setup, subnet.Setup, resourcegroup.Setup, diff --git a/pkg/controller/network/networkinterface/managed.go b/pkg/controller/network/networkinterface/managed.go new file mode 100644 index 00000000..a6941edd --- /dev/null +++ b/pkg/controller/network/networkinterface/managed.go @@ -0,0 +1,147 @@ +/* +Copyright 2021 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package networkinterface + +import ( + "context" + + azurenetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network" + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network/networkapi" + "github.com/pkg/errors" + "k8s.io/client-go/util/workqueue" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/pkg/event" + "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/crossplane/crossplane-runtime/pkg/meta" + "github.com/crossplane/crossplane-runtime/pkg/ratelimiter" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/pkg/resource" + + "github.com/crossplane/provider-azure/apis/network/v1alpha3" + azureclients "github.com/crossplane/provider-azure/pkg/clients" + "github.com/crossplane/provider-azure/pkg/clients/network" +) + +// Error strings. +const ( + errNotNetworkInterface = "managed resource is not an NetworkInterface" + errCreateNetworkInterface = "cannot create NetworkInterface" + errGetNetworkInterface = "cannot get NetworkInterface" + errDeleteNetworkInterface = "cannot delete NetworkInterface" +) + +// Setup adds a controller that reconciles Public Ip Address. +func Setup(mgr ctrl.Manager, l logging.Logger, rl workqueue.RateLimiter) error { + name := managed.ControllerName(v1alpha3.NetworkInterfaceGroupKind) + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + WithOptions(controller.Options{ + RateLimiter: ratelimiter.NewDefaultManagedRateLimiter(rl), + }). + For(&v1alpha3.NetworkInterface{}). + Complete(managed.NewReconciler(mgr, + resource.ManagedKind(v1alpha3.NetworkInterfaceGroupVersionKind), + managed.WithConnectionPublishers(), + managed.WithExternalConnecter(&connecter{client: mgr.GetClient()}), + managed.WithReferenceResolver(managed.NewAPISimpleReferenceResolver(mgr.GetClient())), + managed.WithLogger(l.WithValues("controller", name)), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))))) +} + +type connecter struct { + client client.Client +} + +func (c *connecter) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { + creds, auth, err := azureclients.GetAuthInfo(ctx, c.client, mg) + if err != nil { + return nil, err + } + cl := azurenetwork.NewInterfacesClient(creds[azureclients.CredentialsKeySubscriptionID]) + cl.Authorizer = auth + return &external{client: cl}, nil +} + +type external struct { + client networkapi.InterfacesClientAPI +} + +func (e *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + s, ok := mg.(*v1alpha3.NetworkInterface) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotNetworkInterface) + } + + az, err := e.client.Get(ctx, s.Spec.ResourceGroupName, meta.GetExternalName(s), "") + if azureclients.IsNotFound(err) { + return managed.ExternalObservation{ResourceExists: false}, nil + } + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errGetNetworkInterface) + } + + network.UpdateNetworkInterfaceStatusFromAzure(s, az) + s.SetConditions(xpv1.Available()) + + o := managed.ExternalObservation{ + ResourceExists: true, + ConnectionDetails: managed.ConnectionDetails{}, + } + + return o, nil +} + +func (e *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + s, ok := mg.(*v1alpha3.NetworkInterface) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotNetworkInterface) + } + + s.Status.SetConditions(xpv1.Creating()) + + snet := network.NewNetworkInterfaceParameters(s) + if _, err := e.client.CreateOrUpdate(ctx, s.Spec.ResourceGroupName, meta.GetExternalName(s), snet); err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, errCreateNetworkInterface) + } + + return managed.ExternalCreation{}, nil +} + +func (e *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + _, ok := mg.(*v1alpha3.NetworkInterface) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotNetworkInterface) + } + return managed.ExternalUpdate{}, nil +} + +func (e *external) Delete(ctx context.Context, mg resource.Managed) error { + s, ok := mg.(*v1alpha3.NetworkInterface) + if !ok { + return errors.New(errNotNetworkInterface) + } + + mg.SetConditions(xpv1.Deleting()) + + _, err := e.client.Delete(ctx, s.Spec.ResourceGroupName, meta.GetExternalName(s)) + return errors.Wrap(resource.Ignore(azureclients.IsNotFound, err), errDeleteNetworkInterface) +} diff --git a/pkg/controller/network/networkinterface/managed_test.go b/pkg/controller/network/networkinterface/managed_test.go new file mode 100644 index 00000000..eaf8478b --- /dev/null +++ b/pkg/controller/network/networkinterface/managed_test.go @@ -0,0 +1,311 @@ +/* +Copyright 2021 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package networkinterface + +import ( + "context" + "net/http" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network" + "github.com/Azure/go-autorest/autorest" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/pkg/meta" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/crossplane-runtime/pkg/test" + + "github.com/crossplane/provider-azure/apis/network/v1alpha3" + azure "github.com/crossplane/provider-azure/pkg/clients" + "github.com/crossplane/provider-azure/pkg/clients/network/fake" +) + +const ( + name = "coolNetworkInterface" + uid = types.UID("definitely-a-uuid") + resourceGroupName = "coolRG" +) + +var ( + ctx = context.Background() + errorBoom = errors.New("boom") +) + +type testCase struct { + name string + e managed.ExternalClient + r resource.Managed + want resource.Managed + wantErr error +} + +type networkInterfaceModifier func(address *v1alpha3.NetworkInterface) + +func withConditions(c ...xpv1.Condition) networkInterfaceModifier { + return func(r *v1alpha3.NetworkInterface) { r.Status.ConditionedStatus.Conditions = c } +} + +func withState(s string) networkInterfaceModifier { + return func(r *v1alpha3.NetworkInterface) { r.Status.State = s } +} + +func networkInterfaceAddress(sm ...networkInterfaceModifier) *v1alpha3.NetworkInterface { + r := &v1alpha3.NetworkInterface{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + UID: uid, + Finalizers: []string{}, + }, + Spec: v1alpha3.NetworkInterfaceSpec{ + ResourceGroupName: resourceGroupName, + NetworkInterfaceFormat: v1alpha3.NetworkInterfaceFormat{ + Location: "", + IPConfigurations: make([]*v1alpha3.InterfaceIPConfiguration, 0), + }, + Tags: make(map[string]*string), + }, + Status: v1alpha3.NetworkInterfaceStatus{}, + } + meta.SetExternalName(r, name) + for _, m := range sm { + m(r) + } + return r +} + +// Test that our Reconciler implementation satisfies the Reconciler interface. +var _ managed.ExternalClient = &external{} +var _ managed.ExternalConnecter = &connecter{} + +func TestCreate(t *testing.T) { + cases := []testCase{ + { + name: "NotNetworkInterface", + e: &external{client: &fake.MockNetworkInterfaceClient{}}, + r: &v1alpha3.Subnet{}, + want: &v1alpha3.Subnet{}, + wantErr: errors.New(errNotNetworkInterface), + }, + { + name: "SuccessfulCreate", + e: &external{client: &fake.MockNetworkInterfaceClient{ + MockCreateOrUpdate: func(ctx context.Context, resourceGroupName string, networkInterfaceName string, parameters network.Interface) (result network.InterfacesCreateOrUpdateFuture, err error) { + return network.InterfacesCreateOrUpdateFuture{}, nil + }, + }}, + r: networkInterfaceAddress(), + want: networkInterfaceAddress( + withConditions(xpv1.Creating()), + ), + }, + { + name: "FailedCreate", + e: &external{client: &fake.MockNetworkInterfaceClient{ + MockCreateOrUpdate: func(ctx context.Context, resourceGroupName string, networkInterfaceName string, parameters network.Interface) (result network.InterfacesCreateOrUpdateFuture, err error) { + return network.InterfacesCreateOrUpdateFuture{}, errorBoom + }, + }}, + r: networkInterfaceAddress(), + want: networkInterfaceAddress( + withConditions(xpv1.Creating()), + ), + wantErr: errors.Wrap(errorBoom, errCreateNetworkInterface), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := tc.e.Create(ctx, tc.r) + + if diff := cmp.Diff(tc.wantErr, err, test.EquateErrors()); diff != "" { + t.Errorf("tc.e.Create(...): want error != got error:\n%s", diff) + } + + if diff := cmp.Diff(tc.want, tc.r, test.EquateConditions()); diff != "" { + t.Errorf("r: -want, +got:\n%s", diff) + } + }) + } +} + +func TestObserve(t *testing.T) { + cases := []testCase{ + { + name: "NotNetworkInterface", + e: &external{client: &fake.MockNetworkInterfaceClient{}}, + r: &v1alpha3.VirtualNetwork{}, + want: &v1alpha3.VirtualNetwork{}, + wantErr: errors.New(errNotNetworkInterface), + }, + { + name: "SuccessfulObserveNotExist", + e: &external{client: &fake.MockNetworkInterfaceClient{ + MockGet: func(ctx context.Context, resourceGroupName string, networkInterfaceName string, expand string) (result network.Interface, err error) { + return network.Interface{InterfacePropertiesFormat: &network.InterfacePropertiesFormat{}}, autorest.DetailedError{StatusCode: http.StatusNotFound} + }, + }}, + r: networkInterfaceAddress(), + want: networkInterfaceAddress(), + }, + { + name: "SuccessfulObserveExists", + e: &external{client: &fake.MockNetworkInterfaceClient{ + MockGet: func(ctx context.Context, resourceGroupName string, networkInterfaceName string, expand string) (result network.Interface, err error) { + return network.Interface{ + InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ProvisioningState: azure.ToStringPtr(string(network.Available))}, + }, nil + }, + }}, + r: networkInterfaceAddress(), + want: networkInterfaceAddress( + withConditions(xpv1.Available()), + withState(string(network.Available)), + ), + }, + { + name: "FailedObserve", + e: &external{client: &fake.MockNetworkInterfaceClient{ + MockGet: func(ctx context.Context, resourceGroupName string, networkInterfaceName string, expand string) (result network.Interface, err error) { + return network.Interface{}, errorBoom + }, + }}, + r: networkInterfaceAddress(), + want: networkInterfaceAddress(), + wantErr: errors.Wrap(errorBoom, errGetNetworkInterface), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := tc.e.Observe(ctx, tc.r) + + if diff := cmp.Diff(tc.wantErr, err, test.EquateErrors()); diff != "" { + t.Errorf("tc.e.Observe(...): want error != got error:\n%s", diff) + } + + if diff := cmp.Diff(tc.want, tc.r, test.EquateConditions()); diff != "" { + t.Errorf("r: -want, +got:\n%s", diff) + } + }) + } +} + +func TestUpdate(t *testing.T) { + cases := []testCase{ + { + name: "NotNetworkInterface", + e: &external{client: &fake.MockNetworkInterfaceClient{}}, + r: &v1alpha3.VirtualNetwork{}, + want: &v1alpha3.VirtualNetwork{}, + wantErr: errors.New(errNotNetworkInterface), + }, + { + name: "SuccessfulDoesNotNeedUpdate", + e: &external{client: &fake.MockNetworkInterfaceClient{ + MockGet: func(ctx context.Context, resourceGroupName string, networkInterfaceName string, expand string) (result network.Interface, err error) { + return network.Interface{}, autorest.DetailedError{StatusCode: http.StatusNotFound} + }, + }}, + r: networkInterfaceAddress(), + want: networkInterfaceAddress(), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := tc.e.Update(ctx, tc.r) + + if diff := cmp.Diff(tc.wantErr, err, test.EquateErrors()); diff != "" { + t.Errorf("tc.e.Update(...): want error != got error:\n%s", diff) + } + + if diff := cmp.Diff(tc.want, tc.r, test.EquateConditions()); diff != "" { + t.Errorf("r: -want, +got:\n%s", diff) + } + }) + } +} + +func TestDelete(t *testing.T) { + cases := []testCase{ + { + name: "NotNetworkInterface", + e: &external{client: &fake.MockNetworkInterfaceClient{}}, + r: &v1alpha3.VirtualNetwork{}, + want: &v1alpha3.VirtualNetwork{}, + wantErr: errors.New(errNotNetworkInterface), + }, + { + name: "Successful", + e: &external{client: &fake.MockNetworkInterfaceClient{ + MockDelete: func(ctx context.Context, resourceGroupName string, networkInterfaceName string) (result network.InterfacesDeleteFuture, err error) { + return network.InterfacesDeleteFuture{}, nil + }, + }}, + r: networkInterfaceAddress(), + want: networkInterfaceAddress( + withConditions(xpv1.Deleting()), + ), + }, + { + name: "SuccessfulNotFound", + e: &external{client: &fake.MockNetworkInterfaceClient{ + MockDelete: func(ctx context.Context, resourceGroupName string, networkInterfaceName string) (result network.InterfacesDeleteFuture, err error) { + return network.InterfacesDeleteFuture{}, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + } + }, + }}, + r: networkInterfaceAddress(), + want: networkInterfaceAddress( + withConditions(xpv1.Deleting()), + ), + }, + { + name: "Failed", + e: &external{client: &fake.MockNetworkInterfaceClient{ + MockDelete: func(ctx context.Context, resourceGroupName string, networkInterfaceName string) (result network.InterfacesDeleteFuture, err error) { + return network.InterfacesDeleteFuture{}, errorBoom + }, + }}, + r: networkInterfaceAddress(), + want: networkInterfaceAddress( + withConditions(xpv1.Deleting()), + ), + wantErr: errors.Wrap(errorBoom, errDeleteNetworkInterface), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.e.Delete(ctx, tc.r) + + if diff := cmp.Diff(tc.wantErr, err, test.EquateErrors()); diff != "" { + t.Errorf("tc.e.Delete(...): want error != got error:\n%s", diff) + } + + if diff := cmp.Diff(tc.want, tc.r, test.EquateConditions()); diff != "" { + t.Errorf("r: -want, +got:\n%s", diff) + } + }) + } +}