diff --git a/apis/network/v1alpha3/types.go b/apis/network/v1alpha3/types.go index 7475d879..0926d7a6 100644 --- a/apis/network/v1alpha3/types.go +++ b/apis/network/v1alpha3/types.go @@ -230,12 +230,39 @@ type SubnetList struct { // A PublicIPAddressSpec defines the desired state of a PublicIPAddress. type PublicIPAddressSpec struct { xpv1.ResourceSpec `json:",inline"` - // +kubebuilder:validation:Required - ForProvider PublicIPAddressFormat `json:"properties"` + ForProvider PublicIPAddressProperties `json:"forProvider"` } -// PublicIPAddressFormat defines properties of the PublicIPAddress. -type PublicIPAddressFormat struct { +// PublicIPAddressDNSSettings contains FQDN of the DNS record associated with the public IP address. +type PublicIPAddressDNSSettings struct { + // DomainNameLabel -the Domain name label. + // The concatenation of the domain name label and the regionalized DNS zone + // make up the fully qualified domain name associated with + // the public IP address. If a domain name label is specified, + // an A DNS record is created for the public IP in + // the Microsoft Azure DNS system. + // +kubebuilder:validation:MinLength:=1 + DomainNameLabel string `json:"domainNameLabel"` + // ReverseFQDN - Gets or Sets the Reverse FQDN. + // A user-visible, fully qualified domain name that + // resolves to this public IP address. If the reverseFqdn + // is specified, then a PTR DNS record is created pointing + // from the IP address in the in-addr.arpa domain to + // the reverse FQDN. + // +optional + ReverseFQDN *string `json:"reverseFqdn,omitempty"` +} + +// IPTag list of tags to be assigned to this public IP +type IPTag struct { + // IPTagType - Type of the IP tag. Example: FirstPartyUsage. + IPTagType string `json:"ipTagType"` + // Tag - Value of the IpTag associated with the public IP. Example SQL, Storage etc. + Tag string `json:"tag"` +} + +// PublicIPAddressProperties defines properties of the PublicIPAddress. +type PublicIPAddressProperties struct { // ResourceGroupName - Name of the Public IP address's resource group. // +immutable ResourceGroupName string `json:"resourceGroupName,omitempty"` @@ -254,37 +281,92 @@ type PublicIPAddressFormat struct { // PublicIPAllocationMethod - The public IP address allocation method. Possible values include: 'Static', 'Dynamic' // +kubebuilder:validation:Required // +kubebuilder:validation:Enum=Static;Dynamic + // +immutable PublicIPAllocationMethod string `json:"allocationMethod"` - // PublicIPAllocationMethod - The public IP address version. Possible values include: 'IPV4', 'IPV6' + // PublicIPAllocationMethod - The public IP address version. Possible values include: 'IPv4', 'IPv6' // +kubebuilder:validation:Required - // +kubebuilder:validation:Enum=IPV4;IPV6 + // +kubebuilder:validation:Enum=IPv4;IPv6 + // +immutable PublicIPAddressVersion string `json:"version"` // Location - Resource location. - // +optional - Location *string `json:"location,omitempty"` + // +kubebuilder:validation:MinLength:=1 + // +immutable + Location string `json:"location"` // SKU of PublicIPAddress // +optional SKU *SKU `json:"sku,omitempty"` + // PublicIPPrefixID - The Public IP Prefix this Public IP Address should be allocated from. + // +optional + PublicIPPrefixID *string `json:"publicIPPrefixID,omitempty"` + + // PublicIPAddressDNSSettings - The FQDN of the DNS record associated with the public IP address. + // +optional + PublicIPAddressDNSSettings *PublicIPAddressDNSSettings `json:"dnsSettings,omitempty"` + + // TCPIdleTimeoutInMinutes - Timeout in minutes for idle TCP connections + // +kubebuilder:validation:Minimum:=0 + // +optional + TCPIdleTimeoutInMinutes *int32 `json:"tcpIdleTimeoutInMinutes,omitempty"` + + // IPTags - IP tags to be assigned to this public IP address + // +optional + IPTags []IPTag `json:"ipTags,omitempty"` + // Tags - Resource tags. // +optional - Tags map[string]*string `json:"tags,omitempty"` + Tags map[string]string `json:"tags,omitempty"` } // SKU of PublicIPAddress type SKU struct { // Name - Name of sku. Possible values include: ['Standard', 'Basic'] + // +kubebuilder:validation:Required // +kubebuilder:validation:Enum=Standard;Basic - Name string `json:"name,omitempty"` + Name string `json:"name"` } -// A PublicIPAddressStatus represents the observed state of a PublicIPAddress. -type PublicIPAddressStatus struct { - xpv1.ResourceStatus `json:",inline"` +// IPConfiguration properties of the observed IP configuration. +type IPConfiguration struct { + // PrivateIPAddress - The private IP address of the IP configuration. + PrivateIPAddress *string `json:"privateIPAddress,omitempty"` + // PrivateIPAllocationMethod - The private IP address allocation method. Possible values include: 'Static', 'Dynamic' + PrivateIPAllocationMethod string `json:"privateIPAllocationMethod"` + // ProvisioningState - Gets the provisioning state of the public IP resource. Possible values are: 'Updating', 'Deleting', and 'Failed'. + ProvisioningState string `json:"provisioningState"` +} + +// PublicIPAddressDNSSettingsObservation represents observed DNS settings of +// a public IP resource +type PublicIPAddressDNSSettingsObservation struct { + // DomainNameLabel -the Domain name label. + // The concatenation of the domain name label and the regionalized DNS zone + // make up the fully qualified domain name associated with + // the public IP address. If a domain name label is specified, + // an A DNS record is created for the public IP in + // the Microsoft Azure DNS system. + // +optional + DomainNameLabel *string `json:"domainNameLabel,omitempty"` + // ReverseFQDN - Gets or Sets the Reverse FQDN. + // A user-visible, fully qualified domain name that + // resolves to this public IP address. If the reverseFqdn + // is specified, then a PTR DNS record is created pointing + // from the IP address in the in-addr.arpa domain to + // the reverse FQDN. + // +optional + ReverseFQDN *string `json:"reverseFqdn,omitempty"` + // FQDN - Gets the FQDN, Fully qualified domain name of + // the A DNS record associated with the public IP. + // This is the concatenation of the domainNameLabel + // and the regionalized DNS zone. + FQDN *string `json:"fqdn,omitempty"` +} +// A PublicIPAddressObservation represents the observed state of a PublicIPAddress. +type PublicIPAddressObservation struct { // State of this PublicIPAddress. State string `json:"state,omitempty"` @@ -299,6 +381,21 @@ type PublicIPAddressStatus struct { // Address - A string identifying address of PublicIPAddress resource Address string `json:"address"` + + // Version observed IP version + Version string `json:"version"` + + // DNSSettings observed DNS settings of the IP address + DNSSettings *PublicIPAddressDNSSettingsObservation `json:"dnsSettings,omitempty"` + + // IPConfiguration - The IP configuration associated with the public IP address + IPConfiguration *IPConfiguration `json:"ipConfiguration,omitempty"` +} + +// A PublicIPAddressStatus represents the observed state of a SQLServer. +type PublicIPAddressStatus struct { + xpv1.ResourceStatus `json:",inline"` + AtProvider PublicIPAddressObservation `json:"atProvider,omitempty"` } // +kubebuilder:object:root=true @@ -306,9 +403,8 @@ type PublicIPAddressStatus struct { // 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="ADDRESS",type="string",JSONPath=".status.atProvider.address" +// +kubebuilder:printcolumn:name="FQDN",type="string",JSONPath=".status.atProvider.dnsSettings.fqdn" // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,azure} diff --git a/apis/network/v1alpha3/zz_generated.deepcopy.go b/apis/network/v1alpha3/zz_generated.deepcopy.go index 8a18ff9c..50ef2c2d 100644 --- a/apis/network/v1alpha3/zz_generated.deepcopy.go +++ b/apis/network/v1alpha3/zz_generated.deepcopy.go @@ -45,6 +45,41 @@ 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 *IPConfiguration) DeepCopyInto(out *IPConfiguration) { + *out = *in + if in.PrivateIPAddress != nil { + in, out := &in.PrivateIPAddress, &out.PrivateIPAddress + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPConfiguration. +func (in *IPConfiguration) DeepCopy() *IPConfiguration { + if in == nil { + return nil + } + out := new(IPConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPTag) DeepCopyInto(out *IPTag) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPTag. +func (in *IPTag) DeepCopy() *IPTag { + if in == nil { + return nil + } + out := new(IPTag) + 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 @@ -73,51 +108,51 @@ func (in *PublicIPAddress) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PublicIPAddressFormat) DeepCopyInto(out *PublicIPAddressFormat) { +func (in *PublicIPAddressDNSSettings) DeepCopyInto(out *PublicIPAddressDNSSettings) { *out = *in - if in.ResourceGroupNameRef != nil { - in, out := &in.ResourceGroupNameRef, &out.ResourceGroupNameRef - *out = new(v1.Reference) + if in.ReverseFQDN != nil { + in, out := &in.ReverseFQDN, &out.ReverseFQDN + *out = new(string) **out = **in } - if in.ResourceGroupNameSelector != nil { - in, out := &in.ResourceGroupNameSelector, &out.ResourceGroupNameSelector - *out = new(v1.Selector) - (*in).DeepCopyInto(*out) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicIPAddressDNSSettings. +func (in *PublicIPAddressDNSSettings) DeepCopy() *PublicIPAddressDNSSettings { + if in == nil { + return nil } - if in.Location != nil { - in, out := &in.Location, &out.Location + out := new(PublicIPAddressDNSSettings) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PublicIPAddressDNSSettingsObservation) DeepCopyInto(out *PublicIPAddressDNSSettingsObservation) { + *out = *in + if in.DomainNameLabel != nil { + in, out := &in.DomainNameLabel, &out.DomainNameLabel *out = new(string) **out = **in } - if in.SKU != nil { - in, out := &in.SKU, &out.SKU - *out = new(SKU) + if in.ReverseFQDN != nil { + in, out := &in.ReverseFQDN, &out.ReverseFQDN + *out = new(string) **out = **in } - 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 - } + if in.FQDN != nil { + in, out := &in.FQDN, &out.FQDN + *out = new(string) + **out = **in } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicIPAddressFormat. -func (in *PublicIPAddressFormat) DeepCopy() *PublicIPAddressFormat { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicIPAddressDNSSettingsObservation. +func (in *PublicIPAddressDNSSettingsObservation) DeepCopy() *PublicIPAddressDNSSettingsObservation { if in == nil { return nil } - out := new(PublicIPAddressFormat) + out := new(PublicIPAddressDNSSettingsObservation) in.DeepCopyInto(out) return out } @@ -154,6 +189,88 @@ func (in *PublicIPAddressList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PublicIPAddressObservation) DeepCopyInto(out *PublicIPAddressObservation) { + *out = *in + if in.DNSSettings != nil { + in, out := &in.DNSSettings, &out.DNSSettings + *out = new(PublicIPAddressDNSSettingsObservation) + (*in).DeepCopyInto(*out) + } + if in.IPConfiguration != nil { + in, out := &in.IPConfiguration, &out.IPConfiguration + *out = new(IPConfiguration) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicIPAddressObservation. +func (in *PublicIPAddressObservation) DeepCopy() *PublicIPAddressObservation { + if in == nil { + return nil + } + out := new(PublicIPAddressObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PublicIPAddressProperties) DeepCopyInto(out *PublicIPAddressProperties) { + *out = *in + 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) + } + if in.SKU != nil { + in, out := &in.SKU, &out.SKU + *out = new(SKU) + **out = **in + } + if in.PublicIPPrefixID != nil { + in, out := &in.PublicIPPrefixID, &out.PublicIPPrefixID + *out = new(string) + **out = **in + } + if in.PublicIPAddressDNSSettings != nil { + in, out := &in.PublicIPAddressDNSSettings, &out.PublicIPAddressDNSSettings + *out = new(PublicIPAddressDNSSettings) + (*in).DeepCopyInto(*out) + } + if in.TCPIdleTimeoutInMinutes != nil { + in, out := &in.TCPIdleTimeoutInMinutes, &out.TCPIdleTimeoutInMinutes + *out = new(int32) + **out = **in + } + if in.IPTags != nil { + in, out := &in.IPTags, &out.IPTags + *out = make([]IPTag, len(*in)) + copy(*out, *in) + } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicIPAddressProperties. +func (in *PublicIPAddressProperties) DeepCopy() *PublicIPAddressProperties { + if in == nil { + return nil + } + out := new(PublicIPAddressProperties) + in.DeepCopyInto(out) + return out +} + // 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 @@ -175,6 +292,7 @@ func (in *PublicIPAddressSpec) DeepCopy() *PublicIPAddressSpec { func (in *PublicIPAddressStatus) DeepCopyInto(out *PublicIPAddressStatus) { *out = *in in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + in.AtProvider.DeepCopyInto(&out.AtProvider) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicIPAddressStatus. diff --git a/examples/network/publicipaddress.yaml b/examples/network/publicipaddress.yaml index 4c3a65b9..d8b99a03 100644 --- a/examples/network/publicipaddress.yaml +++ b/examples/network/publicipaddress.yaml @@ -7,9 +7,18 @@ spec: resourceGroupNameRef: name: example-rg allocationMethod: Static - version: IPV4 + version: IPv4 sku: name: Standard location: West US 2 + tcpIdleTimeoutInMinutes: 5 + dnsSettings: + domainNameLabel: crossplane-example +# Looks like the following requires the feature "Microsoft.Network/AllowBringYourOwnPublicIpAddress" to be registered for the subscription +# ipTags: +# - ipTagType: FirstPartyUsage +# tag: Storage + tags: + application: crossplane providerConfigRef: name: example diff --git a/package/crds/network.azure.crossplane.io_publicipaddresses.yaml b/package/crds/network.azure.crossplane.io_publicipaddresses.yaml index 3f5ca3f1..5536b025 100644 --- a/package/crds/network.azure.crossplane.io_publicipaddresses.yaml +++ b/package/crds/network.azure.crossplane.io_publicipaddresses.yaml @@ -25,15 +25,12 @@ spec: - 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 + - jsonPath: .status.atProvider.address name: ADDRESS type: string + - jsonPath: .status.atProvider.dnsSettings.fqdn + name: FQDN + type: string - jsonPath: .metadata.creationTimestamp name: AGE type: date @@ -60,8 +57,8 @@ spec: - Orphan - Delete type: string - properties: - description: PublicIPAddressFormat defines properties of the PublicIPAddress. + forProvider: + description: PublicIPAddressProperties defines properties of the PublicIPAddress. properties: allocationMethod: description: 'PublicIPAllocationMethod - The public IP address allocation method. Possible values include: ''Static'', ''Dynamic''' @@ -69,8 +66,41 @@ spec: - Static - Dynamic type: string + dnsSettings: + description: PublicIPAddressDNSSettings - The FQDN of the DNS record associated with the public IP address. + properties: + domainNameLabel: + description: DomainNameLabel -the Domain name label. The concatenation of the domain name label and the regionalized DNS zone make up the fully qualified domain name associated with the public IP address. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system. + minLength: 1 + type: string + reverseFqdn: + description: ReverseFQDN - Gets or Sets the Reverse FQDN. A user-visible, fully qualified domain name that resolves to this public IP address. If the reverseFqdn is specified, then a PTR DNS record is created pointing from the IP address in the in-addr.arpa domain to the reverse FQDN. + type: string + required: + - domainNameLabel + type: object + ipTags: + description: IPTags - IP tags to be assigned to this public IP address + items: + description: IPTag list of tags to be assigned to this public IP + properties: + ipTagType: + description: 'IPTagType - Type of the IP tag. Example: FirstPartyUsage.' + type: string + tag: + description: Tag - Value of the IpTag associated with the public IP. Example SQL, Storage etc. + type: string + required: + - ipTagType + - tag + type: object + type: array location: description: Location - Resource location. + minLength: 1 + type: string + publicIPPrefixID: + description: PublicIPPrefixID - The Public IP Prefix this Public IP Address should be allocated from. type: string resourceGroupName: description: ResourceGroupName - Name of the Public IP address's resource group. @@ -105,20 +135,28 @@ spec: - Standard - Basic type: string + required: + - name type: object tags: additionalProperties: type: string description: Tags - Resource tags. type: object + tcpIdleTimeoutInMinutes: + description: TCPIdleTimeoutInMinutes - Timeout in minutes for idle TCP connections + format: int32 + minimum: 0 + type: integer version: - description: 'PublicIPAllocationMethod - The public IP address version. Possible values include: ''IPV4'', ''IPV6''' + description: 'PublicIPAllocationMethod - The public IP address version. Possible values include: ''IPv4'', ''IPv6''' enum: - - IPV4 - - IPV6 + - IPv4 + - IPv6 type: string required: - allocationMethod + - location - version type: object providerConfigRef: @@ -155,14 +193,65 @@ spec: - namespace type: object required: - - properties + - forProvider type: object status: - description: A PublicIPAddressStatus represents the observed state of a PublicIPAddress. + description: A PublicIPAddressStatus represents the observed state of a SQLServer. properties: - address: - description: Address - A string identifying address of PublicIPAddress resource - type: string + atProvider: + description: A PublicIPAddressObservation represents the observed state of a PublicIPAddress. + properties: + address: + description: Address - A string identifying address of PublicIPAddress resource + type: string + dnsSettings: + description: DNSSettings observed DNS settings of the IP address + properties: + domainNameLabel: + description: DomainNameLabel -the Domain name label. The concatenation of the domain name label and the regionalized DNS zone make up the fully qualified domain name associated with the public IP address. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system. + type: string + fqdn: + description: FQDN - Gets the FQDN, Fully qualified domain name of the A DNS record associated with the public IP. This is the concatenation of the domainNameLabel and the regionalized DNS zone. + type: string + reverseFqdn: + description: ReverseFQDN - Gets or Sets the Reverse FQDN. A user-visible, fully qualified domain name that resolves to this public IP address. If the reverseFqdn is specified, then a PTR DNS record is created pointing from the IP address in the in-addr.arpa domain to the reverse FQDN. + type: string + type: object + etag: + description: Etag - A unique string that changes whenever the resource is updated. + type: string + id: + description: ID of this PublicIPAddress. + type: string + ipConfiguration: + description: IPConfiguration - The IP configuration associated with the public IP address + properties: + privateIPAddress: + description: PrivateIPAddress - The private IP address of the IP configuration. + type: string + privateIPAllocationMethod: + description: 'PrivateIPAllocationMethod - The private IP address allocation method. Possible values include: ''Static'', ''Dynamic''' + type: string + provisioningState: + description: 'ProvisioningState - Gets the provisioning state of the public IP resource. Possible values are: ''Updating'', ''Deleting'', and ''Failed''.' + type: string + required: + - privateIPAllocationMethod + - provisioningState + type: object + message: + description: A Message providing detail about the state of this PublicIPAddress, if any. + type: string + state: + description: State of this PublicIPAddress. + type: string + version: + description: Version observed IP version + type: string + required: + - address + - version + type: object conditions: description: Conditions of the resource. items: @@ -191,20 +280,6 @@ spec: - 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 diff --git a/pkg/clients/azure.go b/pkg/clients/azure.go index 1272009a..10f66d56 100644 --- a/pkg/clients/azure.go +++ b/pkg/clients/azure.go @@ -414,6 +414,17 @@ func LateInitializeIntPtrFromInt32Ptr(in *int, from *int32) *int { return nil } +// LateInitializeInt32PtrFromInt32Ptr late-inits *int32 +func LateInitializeInt32PtrFromInt32Ptr(in *int32, from *int32) *int32 { + if in != nil { + return in + } + if from != nil { + return to.Int32Ptr(*from) + } + return nil +} + // LateInitializeStringValArrFromArrPtr late-inits []string func LateInitializeStringValArrFromArrPtr(in []string, from *[]string) []string { if in != nil { diff --git a/pkg/clients/network/network.go b/pkg/clients/network/network.go index 196e1af1..ddd011c4 100644 --- a/pkg/clients/network/network.go +++ b/pkg/clients/network/network.go @@ -18,8 +18,12 @@ package network import ( "reflect" + "sort" + "strings" networkmgmt "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/crossplane/provider-azure/apis/network/v1alpha3" azure "github.com/crossplane/provider-azure/pkg/clients" @@ -80,13 +84,53 @@ 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 { + p := s.Spec.ForProvider return networkmgmt.PublicIPAddress{ Sku: NewPublicIPAddressSKU(s.Spec.ForProvider.SKU), PublicIPAddressPropertiesFormat: &networkmgmt.PublicIPAddressPropertiesFormat{ - PublicIPAllocationMethod: networkmgmt.IPAllocationMethod(s.Spec.ForProvider.PublicIPAllocationMethod), - PublicIPAddressVersion: networkmgmt.IPVersion(s.Spec.ForProvider.PublicIPAddressVersion), + PublicIPAllocationMethod: networkmgmt.IPAllocationMethod(p.PublicIPAllocationMethod), + PublicIPAddressVersion: networkmgmt.IPVersion(p.PublicIPAddressVersion), + DNSSettings: newDNSSettings(p.PublicIPAddressDNSSettings), + PublicIPPrefix: newPublicIPPrefixRef(p.PublicIPPrefixID), + IdleTimeoutInMinutes: p.TCPIdleTimeoutInMinutes, + IPTags: newIPTags(p.IPTags), }, - Location: s.Spec.ForProvider.Location, + Location: &p.Location, + Tags: azure.ToStringPtrMap(p.Tags), + } +} + +func newPublicIPPrefixRef(ref *string) *networkmgmt.SubResource { + if ref == nil { + return nil + } + return &networkmgmt.SubResource{ + ID: ref, + } +} + +func newIPTags(t []v1alpha3.IPTag) *[]networkmgmt.IPTag { + if len(t) == 0 { + return nil + } + result := make([]networkmgmt.IPTag, len(t)) + for i, tag := range t { + tag := tag + result[i] = networkmgmt.IPTag{ + IPTagType: &tag.IPTagType, + Tag: &tag.Tag, + } + } + return &result +} + +func newDNSSettings(s *v1alpha3.PublicIPAddressDNSSettings) *networkmgmt.PublicIPAddressDNSSettings { + if s == nil { + return nil + } + return &networkmgmt.PublicIPAddressDNSSettings{ + DomainNameLabel: &s.DomainNameLabel, + ReverseFqdn: s.ReverseFQDN, } } @@ -129,11 +173,160 @@ func UpdateSubnetStatusFromAzure(v *v1alpha3.Subnet, az networkmgmt.Subnet) { 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) +// GeneratePublicIPAddressObservation returns the observation object related to the external +// Azure public IP address in the PublicIPAddressStatus +func GeneratePublicIPAddressObservation(az networkmgmt.PublicIPAddress) *v1alpha3.PublicIPAddressObservation { + v := &v1alpha3.PublicIPAddressObservation{} + v.State = azure.ToString(az.ProvisioningState) + v.Etag = azure.ToString(az.Etag) + v.ID = azure.ToString(az.ID) + v.Address = azure.ToString(az.IPAddress) + v.Version = string(az.PublicIPAddressVersion) + if az.IPConfiguration != nil { + v.IPConfiguration = &v1alpha3.IPConfiguration{ + PrivateIPAllocationMethod: string(az.IPConfiguration.PrivateIPAllocationMethod), + PrivateIPAddress: az.IPConfiguration.PrivateIPAddress, + ProvisioningState: azure.ToString(az.IPConfiguration.ProvisioningState), + } + } + if az.DNSSettings != nil { + v.DNSSettings = &v1alpha3.PublicIPAddressDNSSettingsObservation{ + DomainNameLabel: az.DNSSettings.DomainNameLabel, + FQDN: az.DNSSettings.Fqdn, + ReverseFQDN: az.DNSSettings.ReverseFqdn, + } + } + return v +} + +// LateInitializePublicIPAddress late-initilizes a PublicIPAddress resource +func LateInitializePublicIPAddress(p *v1alpha3.PublicIPAddressProperties, in *networkmgmt.PublicIPAddress) { + p.PublicIPAddressDNSSettings = lateInitializeDNSSettings(p.PublicIPAddressDNSSettings, in.DNSSettings) + p.Tags = azure.LateInitializeStringMap(p.Tags, in.Tags) + if p.SKU == nil && in.Sku != nil { + p.SKU = &v1alpha3.SKU{ + Name: string(in.Sku.Name), + } + } + if p.PublicIPPrefixID == nil && in.PublicIPPrefix != nil && in.PublicIPPrefix.ID != nil { + p.PublicIPPrefixID = in.PublicIPPrefix.ID + } + p.TCPIdleTimeoutInMinutes = azure.LateInitializeInt32PtrFromInt32Ptr(p.TCPIdleTimeoutInMinutes, in.IdleTimeoutInMinutes) + p.IPTags = lateInitializeIPTags(p.IPTags, in.IPTags) +} + +func lateInitializeIPTags(t []v1alpha3.IPTag, from *[]networkmgmt.IPTag) []v1alpha3.IPTag { + if len(t) != 0 || from == nil || len(*from) == 0 { + return t + } + t = make([]v1alpha3.IPTag, len(*from)) + for i, tag := range *from { + tag := tag + t[i] = v1alpha3.IPTag{ + IPTagType: *tag.IPTagType, + Tag: *tag.Tag, + } + } + return t +} + +func lateInitializeDNSSettings(d *v1alpha3.PublicIPAddressDNSSettings, in *networkmgmt.PublicIPAddressDNSSettings) *v1alpha3.PublicIPAddressDNSSettings { + if in == nil { + return d + } + if d == nil { + d = &v1alpha3.PublicIPAddressDNSSettings{} + } + if d.DomainNameLabel == "" { + d.DomainNameLabel = azure.ToString(in.DomainNameLabel) + } + d.ReverseFQDN = azure.LateInitializeStringPtrFromPtr(d.ReverseFQDN, in.ReverseFqdn) + return d +} + +// IsPublicIPAddressUpToDate is used to report whether given network.PublicIPAddress is in +// sync with the PublicIPAddressProperties that the user desires. +func IsPublicIPAddressUpToDate(p v1alpha3.PublicIPAddressProperties, in networkmgmt.PublicIPAddress) bool { + if !cmp.Equal(p.Tags, azure.ToStringMap(in.Tags), cmpopts.EquateEmpty()) { + return false + } + + if !isIPPrefixIDUpToDate(p.PublicIPPrefixID, in.PublicIPPrefix) { + return false + } + + if azure.ToInt(p.TCPIdleTimeoutInMinutes) != azure.ToInt(in.IdleTimeoutInMinutes) { + return false + } + + if !isIPTagsUpToDate(p.IPTags, in.IPTags) { + return false + } + + if !isSKUUpToDate(p.SKU, in.Sku) { + return false + } + + return isDNSSettingsUpToDate(p.PublicIPAddressDNSSettings, in.PublicIPAddressPropertiesFormat.DNSSettings) +} + +func isDNSSettingsUpToDate(d *v1alpha3.PublicIPAddressDNSSettings, in *networkmgmt.PublicIPAddressDNSSettings) bool { + if d == nil { + d = &v1alpha3.PublicIPAddressDNSSettings{} + } + if in == nil { + in = &networkmgmt.PublicIPAddressDNSSettings{} + } + return d.DomainNameLabel == azure.ToString(in.DomainNameLabel) && + azure.ToString(d.ReverseFQDN) == azure.ToString(in.ReverseFqdn) +} + +func isIPPrefixIDUpToDate(p *string, in *networkmgmt.SubResource) bool { + if in == nil { + in = &networkmgmt.SubResource{} + } + return azure.ToString(p) == azure.ToString(in.ID) +} + +func isIPTagsUpToDate(t []v1alpha3.IPTag, in *[]networkmgmt.IPTag) bool { + if in == nil { + in = &[]networkmgmt.IPTag{} + } + if len(t) != len(*in) { + return false + } + ct := make([]v1alpha3.IPTag, len(t)) + copy(ct, t) + sort.Slice(ct, func(i, j int) bool { + result := strings.Compare(ct[i].IPTagType, ct[j].IPTagType) + if result != 0 { + return result == -1 + } + // then compare tags + return strings.Compare(ct[i].Tag, ct[j].Tag) == -1 + }) + sort.Slice(*in, func(i, j int) bool { + result := strings.Compare(*(*in)[i].IPTagType, *(*in)[j].IPTagType) + if result != 0 { + return result == -1 + } + // then compare tags + return strings.Compare(*(*in)[i].Tag, *(*in)[j].Tag) == -1 + }) + for i, tag := range *in { + if *tag.IPTagType != ct[i].IPTagType || *tag.Tag != ct[i].Tag { + return false + } + } + return true +} + +func isSKUUpToDate(s *v1alpha3.SKU, in *networkmgmt.PublicIPAddressSku) bool { + if in == nil { + in = &networkmgmt.PublicIPAddressSku{} + } + if s == nil { + s = &v1alpha3.SKU{} + } + return s.Name == string(in.Name) } diff --git a/pkg/clients/network/network_test.go b/pkg/clients/network/network_test.go index 36faeb01..9a054e2c 100644 --- a/pkg/clients/network/network_test.go +++ b/pkg/clients/network/network_test.go @@ -46,6 +46,21 @@ var ( resourceType = "resource-type" purpose = "cool-purpose" address = "20.46.134.23" + + ipVersion = networkmgmt.IPv6 + skuName = string(networkmgmt.PublicIPAddressSkuNameStandard) + ipAllocMethod = networkmgmt.Static + prefixID = "/test-prefix-id" + dnsLabel = "test-label" + fqdn = "test.fqdn" + reverseFQDN = "fqdn.reverse" + timeout int32 = 1 + ipTagType = "FirstPartyUsage" + ipTag = "SQL" + ipTagType2 = "FirstPartyUsage2" + ipTag2 = "SQL2" + tagKey = "tagKey" + tagVal = "tagValue" ) func TestNewVirtualNetworkParameters(t *testing.T) { @@ -402,14 +417,56 @@ func TestNewPublicIPAddressParameters(t *testing.T) { r: &v1alpha3.PublicIPAddress{ ObjectMeta: metav1.ObjectMeta{UID: uid}, Spec: v1alpha3.PublicIPAddressSpec{ - ForProvider: v1alpha3.PublicIPAddressFormat{ - PublicIPAllocationMethod: "static", + ForProvider: v1alpha3.PublicIPAddressProperties{ + PublicIPAllocationMethod: string(ipAllocMethod), + PublicIPAddressVersion: string(ipVersion), + Location: location, + SKU: &v1alpha3.SKU{ + Name: skuName, + }, + PublicIPPrefixID: &prefixID, + PublicIPAddressDNSSettings: &v1alpha3.PublicIPAddressDNSSettings{ + DomainNameLabel: dnsLabel, + ReverseFQDN: &reverseFQDN, + }, + TCPIdleTimeoutInMinutes: &timeout, + IPTags: []v1alpha3.IPTag{ + { + IPTagType: ipTagType, + Tag: ipTag, + }, + }, + Tags: map[string]string{ + tagKey: tagVal, + }, }, }, }, want: networkmgmt.PublicIPAddress{ PublicIPAddressPropertiesFormat: &networkmgmt.PublicIPAddressPropertiesFormat{ - PublicIPAllocationMethod: "static", + PublicIPAllocationMethod: ipAllocMethod, + PublicIPAddressVersion: ipVersion, + PublicIPPrefix: &networkmgmt.SubResource{ + ID: &prefixID, + }, + DNSSettings: &networkmgmt.PublicIPAddressDNSSettings{ + DomainNameLabel: &dnsLabel, + ReverseFqdn: &reverseFQDN, + }, + IdleTimeoutInMinutes: &timeout, + IPTags: &[]networkmgmt.IPTag{ + { + IPTagType: &ipTagType, + Tag: &ipTag, + }, + }, + }, + Location: &location, + Sku: &networkmgmt.PublicIPAddressSku{ + Name: networkmgmt.PublicIPAddressSkuName(skuName), + }, + Tags: map[string]*string{ + tagKey: &tagVal, }, }, }, @@ -602,10 +659,12 @@ func TestUpdatePublicIPAddressStatusFromAzure(t *testing.T) { }, }, want: v1alpha3.PublicIPAddressStatus{ - State: string(networkmgmt.Succeeded), - ID: id, - Etag: etag, - Address: address, + AtProvider: v1alpha3.PublicIPAddressObservation{ + State: string(networkmgmt.Succeeded), + ID: id, + Etag: etag, + Address: address, + }, }, }, { @@ -617,8 +676,10 @@ func TestUpdatePublicIPAddressStatusFromAzure(t *testing.T) { }, }, want: v1alpha3.PublicIPAddressStatus{ - State: string(networkmgmt.Succeeded), - ID: id, + AtProvider: v1alpha3.PublicIPAddressObservation{ + State: string(networkmgmt.Succeeded), + ID: id, + }, }, }, } @@ -632,7 +693,7 @@ func TestUpdatePublicIPAddressStatusFromAzure(t *testing.T) { }, } - UpdatePublicIPAddressStatusFromAzure(v, tc.r) + v.Status.AtProvider = *GeneratePublicIPAddressObservation(tc.r) // make sure that internal resource status hasn't changed if diff := cmp.Diff(mockCondition, v.Status.ResourceStatus.Conditions[0]); diff != "" { @@ -647,3 +708,428 @@ func TestUpdatePublicIPAddressStatusFromAzure(t *testing.T) { }) } } + +func TestIsPublicIPAddressUpToDate(t *testing.T) { + tagValue2 := "value2" + type args struct { + p v1alpha3.PublicIPAddressProperties + in networkmgmt.PublicIPAddress + } + tests := map[string]struct { + args args + want bool + }{ + "NoUpdatesBothEmpty": { + args: args{ + in: newSDKPublicIPAddress(), + }, + want: true, + }, + "NoUpdatesSpecEmptyTags": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + Tags: map[string]string{}, + }, + in: newSDKPublicIPAddress(), + }, + want: true, + }, + "NoUpdatesSDKEmptyTags": { + args: args{ + in: newSDKPublicIPAddress(withTags(map[string]*string{})), + }, + want: true, + }, + "SameNonEmptyTags": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + Tags: map[string]string{tagKey: tagVal}, + }, + in: newSDKPublicIPAddress(withTags(map[string]*string{tagKey: &tagVal})), + }, + want: true, + }, + "DifferingTags": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + Tags: map[string]string{tagKey: tagVal}, + }, + in: newSDKPublicIPAddress(withTags(map[string]*string{tagKey: &tagValue2})), + }, + }, + "SameNonEmptyIPTagsInOrder": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + IPTags: []v1alpha3.IPTag{ + { + IPTagType: ipTagType2, + Tag: ipTag2, + }, + { + IPTagType: ipTagType, + Tag: ipTag, + }, + }, + }, + in: newSDKPublicIPAddress(withIPTags([]networkmgmt.IPTag{ + { + IPTagType: &ipTagType2, + Tag: &ipTag2, + }, + { + IPTagType: &ipTagType, + Tag: &ipTag, + }, + })), + }, + want: true, + }, + "SameNonEmptyIPTagsSpecNotInOrder": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + IPTags: []v1alpha3.IPTag{ + { + IPTagType: ipTagType, + Tag: ipTag, + }, + { + IPTagType: ipTagType2, + Tag: ipTag2, + }, + }, + }, + in: newSDKPublicIPAddress(withIPTags([]networkmgmt.IPTag{ + { + IPTagType: &ipTagType2, + Tag: &ipTag2, + }, + { + IPTagType: &ipTagType, + Tag: &ipTag, + }, + })), + }, + want: true, + }, + "DifferingNonEmptyIPTags": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + IPTags: []v1alpha3.IPTag{ + { + IPTagType: ipTagType, + Tag: ipTag, + }, + { + IPTagType: ipTagType2, + Tag: ipTag2, + }, + { + IPTagType: ipTagType2, + Tag: ipTag2, + }, + }, + }, + in: newSDKPublicIPAddress(withIPTags([]networkmgmt.IPTag{ + { + IPTagType: &ipTagType, + Tag: &ipTag, + }, + { + IPTagType: &ipTagType2, + Tag: &ipTag2, + }, + })), + }, + want: false, + }, + "SameKeyIPTags": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + IPTags: []v1alpha3.IPTag{ + { + IPTagType: ipTagType, + Tag: ipTag, + }, + { + IPTagType: ipTagType, + Tag: ipTag2, + }, + }, + }, + in: newSDKPublicIPAddress(withIPTags([]networkmgmt.IPTag{ + { + IPTagType: &ipTagType, + Tag: &ipTag2, + }, + { + IPTagType: &ipTagType, + Tag: &ipTag, + }, + })), + }, + want: true, + }, + "SameIdleTimeout": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + TCPIdleTimeoutInMinutes: &timeout, + }, + in: newSDKPublicIPAddress(withIdleTimeout(timeout)), + }, + want: true, + }, + "DifferingTimeout": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + TCPIdleTimeoutInMinutes: &timeout, + }, + in: newSDKPublicIPAddress(withIdleTimeout(2)), + }, + want: false, + }, + "SameDNSSettings": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + PublicIPAddressDNSSettings: &v1alpha3.PublicIPAddressDNSSettings{ + DomainNameLabel: dnsLabel, + ReverseFQDN: &reverseFQDN, + }, + }, + in: newSDKPublicIPAddress(withDNSSettings(dnsLabel, fqdn, reverseFQDN)), + }, + want: true, + }, + "DifferingDNSSettings": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + PublicIPAddressDNSSettings: &v1alpha3.PublicIPAddressDNSSettings{ + DomainNameLabel: dnsLabel, + ReverseFQDN: &reverseFQDN, + }, + }, + in: newSDKPublicIPAddress(withDNSSettings("test-label2", fqdn, reverseFQDN)), + }, + want: false, + }, + "SameIPPrefixID": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + PublicIPPrefixID: &prefixID, + }, + in: newSDKPublicIPAddress(withIPPrefix(prefixID)), + }, + want: true, + }, + "DifferingIPPrefixID": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + PublicIPPrefixID: &prefixID, + }, + in: newSDKPublicIPAddress(withIPPrefix(id)), + }, + want: false, + }, + "SameSKU": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + SKU: &v1alpha3.SKU{ + Name: skuName, + }, + }, + in: newSDKPublicIPAddress(withSKU(skuName)), + }, + want: true, + }, + "DifferingSKU": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + SKU: &v1alpha3.SKU{ + Name: skuName, + }, + }, + in: newSDKPublicIPAddress(withSKU("another-sku-basic")), + }, + want: false, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + if got := IsPublicIPAddressUpToDate(tt.args.p, tt.args.in); got != tt.want { + t.Errorf("IsPublicIPAddressUpToDate() = %v, want %v", got, tt.want) + } + }) + } +} + +type publicIPAddressOption func(*networkmgmt.PublicIPAddress) + +func newSDKPublicIPAddress(opts ...publicIPAddressOption) networkmgmt.PublicIPAddress { + result := networkmgmt.PublicIPAddress{ + PublicIPAddressPropertiesFormat: &networkmgmt.PublicIPAddressPropertiesFormat{}, + } + for _, o := range opts { + o(&result) + } + return result +} + +func withTags(tags map[string]*string) publicIPAddressOption { + return func(in *networkmgmt.PublicIPAddress) { + in.Tags = tags + } +} + +func withIPTags(ipTags []networkmgmt.IPTag) publicIPAddressOption { + return func(in *networkmgmt.PublicIPAddress) { + if ipTags == nil { + return + } + in.IPTags = &ipTags + } +} + +func withIdleTimeout(to int32) publicIPAddressOption { + return func(in *networkmgmt.PublicIPAddress) { + in.IdleTimeoutInMinutes = &to + } +} + +func withIPPrefix(id string) publicIPAddressOption { + return func(in *networkmgmt.PublicIPAddress) { + in.PublicIPPrefix = &networkmgmt.SubResource{ID: &id} + } +} + +func withDNSSettings(label, fqdn, revFQDN string) publicIPAddressOption { + return func(in *networkmgmt.PublicIPAddress) { + in.DNSSettings = &networkmgmt.PublicIPAddressDNSSettings{ + DomainNameLabel: &label, + Fqdn: &fqdn, + ReverseFqdn: &revFQDN, + } + } +} + +func withSKU(name string) publicIPAddressOption { + return func(in *networkmgmt.PublicIPAddress) { + in.Sku = &networkmgmt.PublicIPAddressSku{ + Name: networkmgmt.PublicIPAddressSkuName(name), + } + } +} + +func TestLateInitializePublicIPAddress(t *testing.T) { + tagVal2 := "tagValue2" + type args struct { + p v1alpha3.PublicIPAddressProperties + in networkmgmt.PublicIPAddress + } + tests := map[string]struct { + args args + want v1alpha3.PublicIPAddressProperties + }{ + "LateInitializeEmptySpec": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{}, + in: newSDKPublicIPAddress( + withTags(map[string]*string{tagKey: &tagVal}), + withIPTags([]networkmgmt.IPTag{ + { + IPTagType: &ipTagType, + Tag: &ipTag, + }, + }), + withIdleTimeout(timeout), + withIPPrefix(prefixID), + withDNSSettings(dnsLabel, fqdn, reverseFQDN), + withSKU(skuName)), + }, + want: v1alpha3.PublicIPAddressProperties{ + PublicIPPrefixID: &prefixID, + PublicIPAddressDNSSettings: &v1alpha3.PublicIPAddressDNSSettings{ + DomainNameLabel: dnsLabel, + ReverseFQDN: &reverseFQDN, + }, + TCPIdleTimeoutInMinutes: &timeout, + IPTags: []v1alpha3.IPTag{ + { + IPTagType: ipTagType, + Tag: ipTag, + }, + }, + Tags: map[string]string{ + tagKey: tagVal, + }, + SKU: &v1alpha3.SKU{ + Name: skuName, + }, + }, + }, + "LateInitializeNonEmptySpec": { + args: args{ + p: v1alpha3.PublicIPAddressProperties{ + // SKU: nil, + PublicIPPrefixID: &prefixID, + PublicIPAddressDNSSettings: &v1alpha3.PublicIPAddressDNSSettings{ + DomainNameLabel: dnsLabel, + ReverseFQDN: &reverseFQDN, + }, + TCPIdleTimeoutInMinutes: &timeout, + IPTags: []v1alpha3.IPTag{ + { + IPTagType: ipTagType, + Tag: ipTag, + }, + }, + Tags: map[string]string{ + tagKey: tagVal, + }, + SKU: &v1alpha3.SKU{ + Name: skuName, + }, + }, + in: newSDKPublicIPAddress( + withTags(map[string]*string{"tagKey2": &tagVal2}), + withIPTags([]networkmgmt.IPTag{ + { + IPTagType: &ipTagType2, + Tag: &ipTag2, + }, + }), + withIdleTimeout(3), + withIPPrefix(id), + withDNSSettings("label2", "fqdn2", "fqdn.reverse2"), + withSKU("another-sku-basic")), + }, + want: v1alpha3.PublicIPAddressProperties{ + // SKU: nil, + PublicIPPrefixID: &prefixID, + PublicIPAddressDNSSettings: &v1alpha3.PublicIPAddressDNSSettings{ + DomainNameLabel: dnsLabel, + ReverseFQDN: &reverseFQDN, + }, + TCPIdleTimeoutInMinutes: &timeout, + IPTags: []v1alpha3.IPTag{ + { + IPTagType: ipTagType, + Tag: ipTag, + }, + }, + Tags: map[string]string{ + tagKey: tagVal, + }, + SKU: &v1alpha3.SKU{ + Name: skuName, + }, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + LateInitializePublicIPAddress(&tt.args.p, &tt.args.in) + if diff := cmp.Diff(tt.want, tt.args.p); diff != "" { + t.Errorf("LateInitializePublicIPAddress(tt.args.p, tt.args.in): -want, +got\n%s", diff) + } + }) + } +} diff --git a/pkg/controller/network/publicipaddress/managed.go b/pkg/controller/network/publicipaddress/managed.go index 33c43064..e8e6bde4 100644 --- a/pkg/controller/network/publicipaddress/managed.go +++ b/pkg/controller/network/publicipaddress/managed.go @@ -1,5 +1,5 @@ /* -Copyright 2019 The Crossplane Authors. +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. @@ -43,8 +43,10 @@ import ( // Error strings. const ( + errUpdateCR = "cannot update PublicIPAddress custom resource" errNotPublicIPAddress = "managed resource is not a PublicIPAddress" errCreatePublicIPAddress = "cannot create PublicIPAddress" + errUpdatePublicIPAddress = "cannot update PublicIPAddress" errGetPublicIPAddress = "cannot get PublicIPAddress" errDeletePublicIPAddress = "cannot delete PublicIPAddress" ) @@ -80,10 +82,11 @@ func (c *connecter) Connect(ctx context.Context, mg resource.Managed) (managed.E } cl := azurenetwork.NewPublicIPAddressesClient(creds[azureclients.CredentialsKeySubscriptionID]) cl.Authorizer = auth - return &external{client: cl}, nil + return &external{kube: c.client, client: cl}, nil } type external struct { + kube client.Client client networkapi.PublicIPAddressesClientAPI } @@ -98,12 +101,17 @@ func (e *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex return managed.ExternalObservation{}, errors.Wrap(resource.Ignore(azureclients.IsNotFound, err), errGetPublicIPAddress) } - network.UpdatePublicIPAddressStatusFromAzure(s, az) + network.LateInitializePublicIPAddress(&s.Spec.ForProvider, &az) + if err := e.kube.Update(ctx, s); err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errUpdateCR) + } + + s.Status.AtProvider = *network.GeneratePublicIPAddressObservation(az) s.SetConditions(xpv1.Available()) return managed.ExternalObservation{ ResourceExists: true, - ResourceUpToDate: true, + ResourceUpToDate: network.IsPublicIPAddressUpToDate(s.Spec.ForProvider, az), }, nil } @@ -122,8 +130,14 @@ func (e *external) Create(ctx context.Context, mg resource.Managed) (managed.Ext } func (e *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { - // todo: support PublicIPAddress updates - return managed.ExternalUpdate{}, nil + cr, ok := mg.(*v1alpha3.PublicIPAddress) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotPublicIPAddress) + } + + snet := network.NewPublicIPAddressParameters(cr) + _, err := e.client.CreateOrUpdate(ctx, cr.Spec.ForProvider.ResourceGroupName, meta.GetExternalName(cr), snet) + return managed.ExternalUpdate{}, errors.Wrap(err, errUpdatePublicIPAddress) } func (e *external) Delete(ctx context.Context, mg resource.Managed) error { diff --git a/pkg/controller/network/publicipaddress/managed_test.go b/pkg/controller/network/publicipaddress/managed_test.go index b88f9df3..57926389 100644 --- a/pkg/controller/network/publicipaddress/managed_test.go +++ b/pkg/controller/network/publicipaddress/managed_test.go @@ -49,7 +49,7 @@ func withConditions(c ...xpv1.Condition) publicIPAddressModifier { } func withState(s string) publicIPAddressModifier { - return func(r *v1alpha3.PublicIPAddress) { r.Status.State = s } + return func(r *v1alpha3.PublicIPAddress) { r.Status.AtProvider.State = s } } func publicIPAddress(sm ...publicIPAddressModifier) *v1alpha3.PublicIPAddress { @@ -60,9 +60,9 @@ func publicIPAddress(sm ...publicIPAddressModifier) *v1alpha3.PublicIPAddress { Finalizers: []string{}, }, Spec: v1alpha3.PublicIPAddressSpec{ - ForProvider: v1alpha3.PublicIPAddressFormat{ + ForProvider: v1alpha3.PublicIPAddressProperties{ ResourceGroupName: resourceGroupName, - Tags: make(map[string]*string), + Tags: make(map[string]string), }, }, Status: v1alpha3.PublicIPAddressStatus{}, @@ -152,16 +152,20 @@ func TestObserve(t *testing.T) { }, { 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 + e: &external{ + kube: &test.MockClient{ + MockUpdate: test.NewMockUpdateFn(nil), }, - }}, + 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()), @@ -254,3 +258,50 @@ func TestDelete(t *testing.T) { }) } } + +func TestUpdate(t *testing.T) { + cases := []testCase{ + { + name: "NotPublicIPAddress", + e: &external{client: &fake.MockPublicIPAddressClient{}}, + r: &v1alpha3.Subnet{}, + want: &v1alpha3.Subnet{}, + wantErr: errors.New(errNotPublicIPAddress), + }, + { + name: "SuccessfulUpdate", + 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(), + }, + { + name: "FailedUpdate", + 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(), + wantErr: errors.Wrap(errorBoom, errUpdatePublicIPAddress), + }, + } + + 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.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) + } + }) + } +}