diff --git a/apis/network/v1alpha3/referencers.go b/apis/network/v1alpha3/referencers.go index 80a9d9a5..eb680cf5 100644 --- a/apis/network/v1alpha3/referencers.go +++ b/apis/network/v1alpha3/referencers.go @@ -95,3 +95,24 @@ func (mg *Subnet) ResolveReferences(ctx context.Context, c client.Reader) error return nil } + +// ResolveReferences of this PublicIPAddress +func (mg *PublicIPAddress) 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 + + return nil +} diff --git a/apis/network/v1alpha3/register.go b/apis/network/v1alpha3/register.go index a169787d..0e8f4d2a 100644 --- a/apis/network/v1alpha3/register.go +++ b/apis/network/v1alpha3/register.go @@ -53,7 +53,16 @@ var ( SubnetGroupVersionKind = SchemeGroupVersion.WithKind(SubnetKind) ) +// PublicIpAddress type metadata. +var ( + PublicIPAddressKind = reflect.TypeOf(PublicIPAddress{}).Name() + PublicIPAddressGroupKind = schema.GroupKind{Group: Group, Kind: PublicIPAddressKind}.String() + PublicIPAddressKindAPIVersion = PublicIPAddressKind + "." + SchemeGroupVersion.String() + PublicIPAddressGroupVersionKind = SchemeGroupVersion.WithKind(PublicIPAddressKind) +) + func init() { SchemeBuilder.Register(&VirtualNetwork{}, &VirtualNetworkList{}) SchemeBuilder.Register(&Subnet{}, &SubnetList{}) + SchemeBuilder.Register(&PublicIPAddress{}, &PublicIPAddressList{}) } diff --git a/apis/network/v1alpha3/types.go b/apis/network/v1alpha3/types.go index cc8b8f43..315e19e1 100644 --- a/apis/network/v1alpha3/types.go +++ b/apis/network/v1alpha3/types.go @@ -226,3 +226,95 @@ type SubnetList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []Subnet `json:"items"` } + +// A PublicIPAddressSpec defines the desired state of a PublicIPAddress. +type PublicIPAddressSpec 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"` + + // PublicIPAddressFormat - Properties of the PublicIPAddress. + PublicIPAddressFormat PublicIPAddressFormat `json:"properties"` + + // Tags - Resource tags. + // +optional + Tags map[string]*string `json:"tags,omitempty"` +} + +// PublicIPAddressFormat defines properties of the PublicIPAddress. +type PublicIPAddressFormat struct { + // PublicIPAllocationMethod - The public IP address allocation method. Possible values include: 'Static', 'Dynamic' + PublicIPAllocationMethod string `json:"allocationMethod"` + + // PublicIPAllocationMethod - The public IP address version. Possible values include: 'IPV4', 'IPV6' + PublicIPAddressVersion string `json:"version"` + + // Location - Resource location. + Location string `json:"location"` + + // SKU of PublicIPAddress + SKU SKU `json:"sku"` +} + +// SKU of PublicIPAddress +type SKU struct { + // Name - Name of sku. Possible values include: 'Standard' + Name string `json:"name"` +} + +// A PublicIPAddressStatus represents the observed state of a PublicIPAddress. +type PublicIPAddressStatus 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"` + + // Address - A string identifying address of PublicIPAddress resource + Address string `json:"address"` +} + +// +kubebuilder:object:root=true + +// A PublicIPAddress is a managed resource that represents an Azure PublicIPAddress. +// +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="ADDRESS",type="string",JSONPath=".status.address" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,azure} +type PublicIPAddress struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec PublicIPAddressSpec `json:"spec"` + Status PublicIPAddressStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// PublicIPAddressList contains a list of PublicIPAddress items +type PublicIPAddressList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []PublicIPAddress `json:"items"` +} diff --git a/apis/network/v1alpha3/zz_generated.deepcopy.go b/apis/network/v1alpha3/zz_generated.deepcopy.go index ef171c5a..1fdaa5e1 100644 --- a/apis/network/v1alpha3/zz_generated.deepcopy.go +++ b/apis/network/v1alpha3/zz_generated.deepcopy.go @@ -45,6 +45,154 @@ 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 *PublicIPAddress) DeepCopyInto(out *PublicIPAddress) { + *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 PublicIPAddress. +func (in *PublicIPAddress) DeepCopy() *PublicIPAddress { + if in == nil { + return nil + } + out := new(PublicIPAddress) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PublicIPAddress) 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 *PublicIPAddressFormat) DeepCopyInto(out *PublicIPAddressFormat) { + *out = *in + out.SKU = in.SKU +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicIPAddressFormat. +func (in *PublicIPAddressFormat) DeepCopy() *PublicIPAddressFormat { + if in == nil { + return nil + } + out := new(PublicIPAddressFormat) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PublicIPAddressList) DeepCopyInto(out *PublicIPAddressList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PublicIPAddress, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicIPAddressList. +func (in *PublicIPAddressList) DeepCopy() *PublicIPAddressList { + if in == nil { + return nil + } + out := new(PublicIPAddressList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PublicIPAddressList) 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 *PublicIPAddressSpec) DeepCopyInto(out *PublicIPAddressSpec) { + *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) + } + out.PublicIPAddressFormat = in.PublicIPAddressFormat + 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 PublicIPAddressSpec. +func (in *PublicIPAddressSpec) DeepCopy() *PublicIPAddressSpec { + if in == nil { + return nil + } + out := new(PublicIPAddressSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PublicIPAddressStatus) DeepCopyInto(out *PublicIPAddressStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicIPAddressStatus. +func (in *PublicIPAddressStatus) DeepCopy() *PublicIPAddressStatus { + if in == nil { + return nil + } + out := new(PublicIPAddressStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SKU) DeepCopyInto(out *SKU) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SKU. +func (in *SKU) DeepCopy() *SKU { + if in == nil { + return nil + } + out := new(SKU) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceEndpointPropertiesFormat) DeepCopyInto(out *ServiceEndpointPropertiesFormat) { *out = *in diff --git a/apis/network/v1alpha3/zz_generated.managed.go b/apis/network/v1alpha3/zz_generated.managed.go index f1c3e456..222bcd86 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 PublicIPAddress. +func (mg *PublicIPAddress) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetDeletionPolicy of this PublicIPAddress. +func (mg *PublicIPAddress) GetDeletionPolicy() xpv1.DeletionPolicy { + return mg.Spec.DeletionPolicy +} + +// GetProviderConfigReference of this PublicIPAddress. +func (mg *PublicIPAddress) GetProviderConfigReference() *xpv1.Reference { + return mg.Spec.ProviderConfigReference +} + +/* +GetProviderReference of this PublicIPAddress. +Deprecated: Use GetProviderConfigReference. +*/ +func (mg *PublicIPAddress) GetProviderReference() *xpv1.Reference { + return mg.Spec.ProviderReference +} + +// GetWriteConnectionSecretToReference of this PublicIPAddress. +func (mg *PublicIPAddress) GetWriteConnectionSecretToReference() *xpv1.SecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this PublicIPAddress. +func (mg *PublicIPAddress) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetDeletionPolicy of this PublicIPAddress. +func (mg *PublicIPAddress) SetDeletionPolicy(r xpv1.DeletionPolicy) { + mg.Spec.DeletionPolicy = r +} + +// SetProviderConfigReference of this PublicIPAddress. +func (mg *PublicIPAddress) SetProviderConfigReference(r *xpv1.Reference) { + mg.Spec.ProviderConfigReference = r +} + +/* +SetProviderReference of this PublicIPAddress. +Deprecated: Use SetProviderConfigReference. +*/ +func (mg *PublicIPAddress) SetProviderReference(r *xpv1.Reference) { + mg.Spec.ProviderReference = r +} + +// SetWriteConnectionSecretToReference of this PublicIPAddress. +func (mg *PublicIPAddress) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} + // GetCondition of this Subnet. func (mg *Subnet) 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 d10ff992..ef02beac 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 PublicIPAddressList. +func (l *PublicIPAddressList) 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 SubnetList. func (l *SubnetList) GetItems() []resource.Managed { items := make([]resource.Managed, len(l.Items)) diff --git a/examples/network/publiipaddress.yaml b/examples/network/publiipaddress.yaml new file mode 100644 index 00000000..d1876e99 --- /dev/null +++ b/examples/network/publiipaddress.yaml @@ -0,0 +1,15 @@ +apiVersion: network.azure.crossplane.io/v1alpha3 +kind: PublicIPAddress +metadata: + name: example-public-ip-address +spec: + resourceGroupNameRef: + name: example-rg + properties: + allocationMethod: static + version: IPV4 + sku: + name: Standard + location: West US 2 + providerConfigRef: + name: example diff --git a/package/crds/network.azure.crossplane.io_publicipaddresses.yaml b/package/crds/network.azure.crossplane.io_publicipaddresses.yaml new file mode 100644 index 00000000..446f0fe8 --- /dev/null +++ b/package/crds/network.azure.crossplane.io_publicipaddresses.yaml @@ -0,0 +1,213 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: publicipaddresses.network.azure.crossplane.io +spec: + group: network.azure.crossplane.io + names: + categories: + - crossplane + - managed + - azure + kind: PublicIPAddress + listKind: PublicIPAddressList + plural: publicipaddresses + singular: publicipaddress + 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: .status.address + name: ADDRESS + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha3 + schema: + openAPIV3Schema: + description: A PublicIPAddress is a managed resource that represents an Azure PublicIPAddress. + 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 PublicIPAddressSpec defines the desired state of a PublicIPAddress. + 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: PublicIPAddressFormat - Properties of the PublicIPAddress. + properties: + allocationMethod: + description: 'PublicIPAllocationMethod - The public IP address allocation method. Possible values include: ''Static'', ''Dynamic''' + type: string + location: + description: Location - Resource location. + type: string + sku: + description: SKU of PublicIPAddress + properties: + name: + description: 'Name - Name of sku. Possible values include: ''Standard''' + type: string + required: + - name + type: object + version: + description: 'PublicIPAllocationMethod - The public IP address version. Possible values include: ''IPV4'', ''IPV6''' + type: string + required: + - allocationMethod + - location + - sku + - version + 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 PublicIPAddressStatus represents the observed state of a PublicIPAddress. + properties: + address: + description: Address - A string identifying address of PublicIPAddress resource + type: string + 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 + required: + - address + 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 65f2b0fa..20e9e8dc 100644 --- a/pkg/clients/network/fake/fake.go +++ b/pkg/clients/network/fake/fake.go @@ -86,3 +86,35 @@ func (c *MockSubnetsClient) Get(ctx context.Context, resourceGroupName string, v func (c *MockSubnetsClient) List(ctx context.Context, resourceGroupName string, virtualNetworkName string) (result network.SubnetListResultPage, err error) { return c.MockList(ctx, resourceGroupName, virtualNetworkName) } + +var _ networkapi.PublicIPAddressesClientAPI = &MockPublicIPAddressClient{} + +// MockPublicIPAddressClient is a fake implementation of network.PublicIPAddressClient. +type MockPublicIPAddressClient struct { + networkapi.PublicIPAddressesClientAPI + + MockCreateOrUpdate func(ctx context.Context, resourceGroupName string, publicIPAddressName string, parameters network.PublicIPAddress) (result network.PublicIPAddressesCreateOrUpdateFuture, err error) + MockDelete func(ctx context.Context, resourceGroupName string, publicIPAddressName string) (result network.PublicIPAddressesDeleteFuture, err error) + MockGet func(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (result network.PublicIPAddress, err error) + MockList func(ctx context.Context, resourceGroupName string) (result network.PublicIPAddressListResultPage, err error) +} + +// CreateOrUpdate calls the MockPublicIPAddressClient's MockCreateOrUpdate method. +func (c *MockPublicIPAddressClient) CreateOrUpdate(ctx context.Context, resourceGroupName string, publicIPAddressName string, parameters network.PublicIPAddress) (result network.PublicIPAddressesCreateOrUpdateFuture, err error) { + return c.MockCreateOrUpdate(ctx, resourceGroupName, publicIPAddressName, parameters) +} + +// Delete calls the MockPublicIPAddressClient's MockDelete method. +func (c *MockPublicIPAddressClient) Delete(ctx context.Context, resourceGroupName string, publicIPAddressName string) (result network.PublicIPAddressesDeleteFuture, err error) { + return c.MockDelete(ctx, resourceGroupName, publicIPAddressName) +} + +// Get calls the MockPublicIPAddressClient's MockGet method. +func (c *MockPublicIPAddressClient) Get(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (result network.PublicIPAddress, err error) { + return c.MockGet(ctx, resourceGroupName, publicIPAddressName, expand) +} + +// List calls the MockPublicIPAddressClient's MockListKeys method. +func (c *MockPublicIPAddressClient) List(ctx context.Context, resourceGroupName string) (result network.PublicIPAddressListResultPage, err error) { + return c.MockList(ctx, resourceGroupName) +} diff --git a/pkg/clients/network/network.go b/pkg/clients/network/network.go index a5f2a895..f18ad212 100644 --- a/pkg/clients/network/network.go +++ b/pkg/clients/network/network.go @@ -78,6 +78,17 @@ func NewSubnetParameters(s *v1alpha3.Subnet) networkmgmt.Subnet { } } +// NewPublicIPAddressParameters returns an Azure PublicIPAddress object from a public ip address spec +func NewPublicIPAddressParameters(s *v1alpha3.PublicIPAddress) networkmgmt.PublicIPAddress { + return networkmgmt.PublicIPAddress{ + PublicIPAddressPropertiesFormat: &networkmgmt.PublicIPAddressPropertiesFormat{ + PublicIPAllocationMethod: networkmgmt.IPAllocationMethod(s.Spec.PublicIPAddressFormat.PublicIPAllocationMethod), + PublicIPAddressVersion: networkmgmt.IPVersion(s.Spec.PublicIPAddressFormat.PublicIPAddressVersion), + }, + Location: azure.ToStringPtr(s.Spec.PublicIPAddressFormat.Location), + } +} + // NewServiceEndpoints converts to Azure ServiceEndpointPropertiesFormat func NewServiceEndpoints(e []v1alpha3.ServiceEndpointPropertiesFormat) *[]networkmgmt.ServiceEndpointPropertiesFormat { endpoints := make([]networkmgmt.ServiceEndpointPropertiesFormat, len(e)) @@ -106,3 +117,12 @@ func UpdateSubnetStatusFromAzure(v *v1alpha3.Subnet, az networkmgmt.Subnet) { v.Status.ID = azure.ToString(az.ID) v.Status.Purpose = azure.ToString(az.Purpose) } + +// UpdatePublicIPAddressStatusFromAzure updates the status related to the external +// Azure public ip address in the PublicIPAddressStatus +func UpdatePublicIPAddressStatusFromAzure(v *v1alpha3.PublicIPAddress, az networkmgmt.PublicIPAddress) { + v.Status.State = azure.ToString(az.ProvisioningState) + v.Status.Etag = azure.ToString(az.Etag) + v.Status.ID = azure.ToString(az.ID) + v.Status.Address = azure.ToString(az.IPAddress) +} diff --git a/pkg/clients/network/network_test.go b/pkg/clients/network/network_test.go index 9b184452..da138fd1 100644 --- a/pkg/clients/network/network_test.go +++ b/pkg/clients/network/network_test.go @@ -45,6 +45,7 @@ var ( etag = "a-very-cool-etag" resourceType = "resource-type" purpose = "cool-purpose" + address = "20.46.134.23" ) func TestNewVirtualNetworkParameters(t *testing.T) { @@ -390,6 +391,40 @@ func TestNewSubnetParameters(t *testing.T) { } } +func TestNewPublicIPAddressParameters(t *testing.T) { + cases := []struct { + name string + r *v1alpha3.PublicIPAddress + want networkmgmt.PublicIPAddress + }{ + { + name: "Successful", + r: &v1alpha3.PublicIPAddress{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1alpha3.PublicIPAddressSpec{ + PublicIPAddressFormat: v1alpha3.PublicIPAddressFormat{ + PublicIPAllocationMethod: "static", + }, + }, + }, + want: networkmgmt.PublicIPAddress{ + PublicIPAddressPropertiesFormat: &networkmgmt.PublicIPAddressPropertiesFormat{ + PublicIPAllocationMethod: "static", + }, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := NewPublicIPAddressParameters(tc.r) + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("NewSubnetParameters(...): -want, +got\n%s", diff) + } + }) + } +} + func TestNewServiceEndpoints(t *testing.T) { cases := []struct { name string @@ -542,3 +577,73 @@ func TestUpdateSubnetStatusFromAzure(t *testing.T) { }) } } + +func TestUpdatePublicIPAddressStatusFromAzure(t *testing.T) { + mockCondition := xpv1.Condition{Message: "mockMessage"} + resourceStatus := xpv1.ResourceStatus{ + ConditionedStatus: xpv1.ConditionedStatus{ + Conditions: []xpv1.Condition{mockCondition}, + }, + } + + cases := []struct { + name string + r networkmgmt.PublicIPAddress + want v1alpha3.PublicIPAddressStatus + }{ + { + name: "SuccessfulFull", + r: networkmgmt.PublicIPAddress{ + Etag: azure.ToStringPtr(etag), + ID: azure.ToStringPtr(id), + PublicIPAddressPropertiesFormat: &networkmgmt.PublicIPAddressPropertiesFormat{ + IPAddress: azure.ToStringPtr(address), + ProvisioningState: azure.ToStringPtr("Succeeded"), + }, + }, + want: v1alpha3.PublicIPAddressStatus{ + State: string(networkmgmt.Succeeded), + ID: id, + Etag: etag, + Address: address, + }, + }, + { + name: "SuccessfulPartial", + r: networkmgmt.PublicIPAddress{ + ID: azure.ToStringPtr(id), + PublicIPAddressPropertiesFormat: &networkmgmt.PublicIPAddressPropertiesFormat{ + ProvisioningState: azure.ToStringPtr("Succeeded"), + }, + }, + want: v1alpha3.PublicIPAddressStatus{ + State: string(networkmgmt.Succeeded), + ID: id, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + v := &v1alpha3.PublicIPAddress{ + Status: v1alpha3.PublicIPAddressStatus{ + ResourceStatus: resourceStatus, + }, + } + + UpdatePublicIPAddressStatusFromAzure(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("UpdateSubnetStatusFromAzure(...): -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("UpdateSubnetStatusFromAzure(...): -want, +got\n%s", diff) + } + }) + } +} diff --git a/pkg/controller/azure.go b/pkg/controller/azure.go index dea46eef..b356dbd6 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/publicipaddress" "github.com/crossplane/provider-azure/pkg/controller/network/subnet" "github.com/crossplane/provider-azure/pkg/controller/network/virtualnetwork" "github.com/crossplane/provider-azure/pkg/controller/resourcegroup" @@ -52,6 +53,7 @@ func Setup(mgr ctrl.Manager, l logging.Logger, rl workqueue.RateLimiter) error { postgresqlserverfirewallrule.Setup, postgresqlservervirtualnetworkrule.Setup, cosmosdb.Setup, + publicipaddress.Setup, virtualnetwork.Setup, subnet.Setup, resourcegroup.Setup, diff --git a/pkg/controller/network/publicipaddress/managed.go b/pkg/controller/network/publicipaddress/managed.go new file mode 100644 index 00000000..d2887ca2 --- /dev/null +++ b/pkg/controller/network/publicipaddress/managed.go @@ -0,0 +1,147 @@ +/* +Copyright 2019 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 publicipaddress + +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 ( + errNotPublicIPAddress = "managed resource is not an PublicIPAddress" + errCreatePublicIPAddress = "cannot create PublicIPAddress" + errGetPublicIPAddress = "cannot get PublicIPAddress" + errDeletePublicIPAddress = "cannot delete PublicIPAddress" +) + +// 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.PublicIPAddressGroupKind) + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + WithOptions(controller.Options{ + RateLimiter: ratelimiter.NewDefaultManagedRateLimiter(rl), + }). + For(&v1alpha3.PublicIPAddress{}). + Complete(managed.NewReconciler(mgr, + resource.ManagedKind(v1alpha3.PublicIPAddressGroupVersionKind), + 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.NewPublicIPAddressesClient(creds[azureclients.CredentialsKeySubscriptionID]) + cl.Authorizer = auth + return &external{client: cl}, nil +} + +type external struct { + client networkapi.PublicIPAddressesClientAPI +} + +func (e *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + s, ok := mg.(*v1alpha3.PublicIPAddress) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotPublicIPAddress) + } + + 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, errGetPublicIPAddress) + } + + network.UpdatePublicIPAddressStatusFromAzure(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.PublicIPAddress) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotPublicIPAddress) + } + + s.Status.SetConditions(xpv1.Creating()) + + snet := network.NewPublicIPAddressParameters(s) + if _, err := e.client.CreateOrUpdate(ctx, s.Spec.ResourceGroupName, meta.GetExternalName(s), snet); err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, errCreatePublicIPAddress) + } + + return managed.ExternalCreation{}, nil +} + +func (e *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + _, ok := mg.(*v1alpha3.PublicIPAddress) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotPublicIPAddress) + } + return managed.ExternalUpdate{}, nil +} + +func (e *external) Delete(ctx context.Context, mg resource.Managed) error { + s, ok := mg.(*v1alpha3.PublicIPAddress) + if !ok { + return errors.New(errNotPublicIPAddress) + } + + mg.SetConditions(xpv1.Deleting()) + + _, err := e.client.Delete(ctx, s.Spec.ResourceGroupName, meta.GetExternalName(s)) + return errors.Wrap(resource.Ignore(azureclients.IsNotFound, err), errDeletePublicIPAddress) +} diff --git a/pkg/controller/network/publicipaddress/managed_test.go b/pkg/controller/network/publicipaddress/managed_test.go new file mode 100644 index 00000000..8ae1fbb0 --- /dev/null +++ b/pkg/controller/network/publicipaddress/managed_test.go @@ -0,0 +1,305 @@ +package publicipaddress + +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 = "coolPublicIPAddress" + 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 publicIPAddressModifier func(address *v1alpha3.PublicIPAddress) + +func withConditions(c ...xpv1.Condition) publicIPAddressModifier { + return func(r *v1alpha3.PublicIPAddress) { r.Status.ConditionedStatus.Conditions = c } +} + +func withState(s string) publicIPAddressModifier { + return func(r *v1alpha3.PublicIPAddress) { r.Status.State = s } +} + +func publicIPAddress(sm ...publicIPAddressModifier) *v1alpha3.PublicIPAddress { + r := &v1alpha3.PublicIPAddress{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + UID: uid, + Finalizers: []string{}, + }, + Spec: v1alpha3.PublicIPAddressSpec{ + ResourceGroupName: resourceGroupName, + PublicIPAddressFormat: v1alpha3.PublicIPAddressFormat{}, + Tags: make(map[string]*string), + }, + Status: v1alpha3.PublicIPAddressStatus{}, + } + 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: "NotPublicIPAddress", + e: &external{client: &fake.MockPublicIPAddressClient{}}, + r: &v1alpha3.Subnet{}, + want: &v1alpha3.Subnet{}, + wantErr: errors.New(errNotPublicIPAddress), + }, + { + name: "SuccessfulCreate", + e: &external{client: &fake.MockPublicIPAddressClient{ + MockCreateOrUpdate: func(ctx context.Context, resourceGroupName string, publicIPAddressName string, parameters network.PublicIPAddress) (result network.PublicIPAddressesCreateOrUpdateFuture, err error) { + return network.PublicIPAddressesCreateOrUpdateFuture{}, nil + }, + }}, + r: publicIPAddress(), + want: publicIPAddress( + withConditions(xpv1.Creating()), + ), + }, + { + name: "FailedCreate", + e: &external{client: &fake.MockPublicIPAddressClient{ + MockCreateOrUpdate: func(ctx context.Context, resourceGroupName string, publicIPAddressName string, parameters network.PublicIPAddress) (result network.PublicIPAddressesCreateOrUpdateFuture, err error) { + return network.PublicIPAddressesCreateOrUpdateFuture{}, errorBoom + }, + }}, + r: publicIPAddress(), + want: publicIPAddress( + withConditions(xpv1.Creating()), + ), + wantErr: errors.Wrap(errorBoom, errCreatePublicIPAddress), + }, + } + + 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: "NotPublicIPAddress", + e: &external{client: &fake.MockPublicIPAddressClient{}}, + r: &v1alpha3.VirtualNetwork{}, + want: &v1alpha3.VirtualNetwork{}, + wantErr: errors.New(errNotPublicIPAddress), + }, + { + name: "SuccessfulObserveNotExist", + e: &external{client: &fake.MockPublicIPAddressClient{ + MockGet: func(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (result network.PublicIPAddress, err error) { + return network.PublicIPAddress{ + PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{ + PublicIPAllocationMethod: "static", + }, + }, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + } + }, + }}, + r: publicIPAddress(), + want: publicIPAddress(), + }, + { + name: "SuccessfulObserveExists", + e: &external{client: &fake.MockPublicIPAddressClient{ + MockGet: func(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (result network.PublicIPAddress, err error) { + return network.PublicIPAddress{ + PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{ + PublicIPAllocationMethod: "static", + ProvisioningState: azure.ToStringPtr(string(network.Available)), + }, + }, nil + }, + }}, + r: publicIPAddress(), + want: publicIPAddress( + withConditions(xpv1.Available()), + withState(string(network.Available)), + ), + }, + { + name: "FailedObserve", + e: &external{client: &fake.MockPublicIPAddressClient{ + MockGet: func(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (result network.PublicIPAddress, err error) { + return network.PublicIPAddress{}, errorBoom + }, + }}, + r: publicIPAddress(), + want: publicIPAddress(), + wantErr: errors.Wrap(errorBoom, errGetPublicIPAddress), + }, + } + + 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: "NotPublicIPAddress", + e: &external{client: &fake.MockPublicIPAddressClient{}}, + r: &v1alpha3.VirtualNetwork{}, + want: &v1alpha3.VirtualNetwork{}, + wantErr: errors.New(errNotPublicIPAddress), + }, + { + name: "SuccessfulDoesNotNeedUpdate", + e: &external{client: &fake.MockPublicIPAddressClient{ + MockGet: func(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (result network.PublicIPAddress, err error) { + return network.PublicIPAddress{ + PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{ + PublicIPAllocationMethod: "static", + }, + }, nil + }, + }}, + r: publicIPAddress(), + want: publicIPAddress(), + }, + } + + 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: "NotPublicIPAddress", + e: &external{client: &fake.MockPublicIPAddressClient{}}, + r: &v1alpha3.VirtualNetwork{}, + want: &v1alpha3.VirtualNetwork{}, + wantErr: errors.New(errNotPublicIPAddress), + }, + { + name: "Successful", + e: &external{client: &fake.MockPublicIPAddressClient{ + MockDelete: func(ctx context.Context, resourceGroupName string, publicIPAddressName string) (result network.PublicIPAddressesDeleteFuture, err error) { + return network.PublicIPAddressesDeleteFuture{}, nil + }, + }}, + r: publicIPAddress(), + want: publicIPAddress( + withConditions(xpv1.Deleting()), + ), + }, + { + name: "SuccessfulNotFound", + e: &external{client: &fake.MockPublicIPAddressClient{ + MockDelete: func(ctx context.Context, resourceGroupName string, publicIPAddressName string) (result network.PublicIPAddressesDeleteFuture, err error) { + return network.PublicIPAddressesDeleteFuture{}, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + } + }, + }}, + r: publicIPAddress(), + want: publicIPAddress( + withConditions(xpv1.Deleting()), + ), + }, + { + name: "Failed", + e: &external{client: &fake.MockPublicIPAddressClient{ + MockDelete: func(ctx context.Context, resourceGroupName string, publicIPAddressName string) (result network.PublicIPAddressesDeleteFuture, err error) { + return network.PublicIPAddressesDeleteFuture{}, errorBoom + }, + }}, + r: publicIPAddress(), + want: publicIPAddress( + withConditions(xpv1.Deleting()), + ), + wantErr: errors.Wrap(errorBoom, errDeletePublicIPAddress), + }, + } + + 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) + } + }) + } +}