From 03e098b88c42dfa5169660c2fec0094682c92eed Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Fri, 11 Jun 2021 17:10:23 -0400 Subject: [PATCH 1/3] ignore vlan attachment API responses "422 Virtual network foo already ..." Signed-off-by: Marques Johansson --- pkg/clients/metal.go | 19 +++++++++++++++++++ pkg/controller/ports/assignment/managed.go | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/pkg/clients/metal.go b/pkg/clients/metal.go index 4e83318..13db606 100644 --- a/pkg/clients/metal.go +++ b/pkg/clients/metal.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "github.com/crossplane/crossplane-runtime/pkg/resource" "github.com/packethost/packngo" @@ -42,6 +43,11 @@ type Client struct { Client *packngo.Client } +const ( + errVirtualNetworkAlreadyContents = " already " + errVirtualNetworkAlreadyPrefix = "Virtual network" +) + // NewCredentialsFromJSON parses JSON bytes returning an Equinix Metal Credentials configuration func NewCredentialsFromJSON(j []byte) (*Credentials, error) { config := &Credentials{} @@ -113,3 +119,16 @@ func IsNotFound(err error) bool { } return false } + +// IsAlreadyDone returns true if, during VLAN assignment operations, the API +// returns an error like "422 Virtual network 1182 already assigned" or "422 +// Virtual network 1182 already unassigned" +func IsAlreadyDone(err error) bool { + if e, ok := err.(*packngo.ErrorResponse); ok && e.Response != nil { + errsInOne := strings.Join(append(e.Errors, e.SingleError), "") + return e.Response.StatusCode == http.StatusUnprocessableEntity && + strings.Contains(errsInOne, errVirtualNetworkAlreadyContents) && + strings.HasPrefix(errsInOne, errVirtualNetworkAlreadyPrefix) + } + return false +} diff --git a/pkg/controller/ports/assignment/managed.go b/pkg/controller/ports/assignment/managed.go index 479ca8c..2e15805 100644 --- a/pkg/controller/ports/assignment/managed.go +++ b/pkg/controller/ports/assignment/managed.go @@ -144,7 +144,7 @@ func (e *external) Create(ctx context.Context, mg resource.Managed) (managed.Ext } a.Status.SetConditions(xpv1.Creating()) _, _, err := e.client.Assign(&packngo.PortAssignRequest{PortID: meta.GetExternalName(a), VirtualNetworkID: a.Spec.ForProvider.VirtualNetworkID}) - return managed.ExternalCreation{}, errors.Wrap(err, errCreateAssignment) + return managed.ExternalCreation{}, errors.Wrap(resource.Ignore(packetclient.IsAlreadyDone, err), errCreateAssignment) } func (e *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { @@ -159,5 +159,5 @@ func (e *external) Delete(ctx context.Context, mg resource.Managed) error { } a.SetConditions(xpv1.Deleting()) _, _, err := e.client.Unassign(&packngo.PortAssignRequest{PortID: meta.GetExternalName(a), VirtualNetworkID: a.Spec.ForProvider.VirtualNetworkID}) - return errors.Wrap(resource.Ignore(packetclient.IsNotFound, err), errDeleteAssignment) + return errors.Wrap(resource.IgnoreAny(err, packetclient.IsNotFound, packetclient.IsAlreadyDone), errDeleteAssignment) } From f514bae874241891cabe66ea7ae71e36f621521f Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Fri, 11 Jun 2021 17:47:18 -0400 Subject: [PATCH 2/3] fix observation of vlan attachments in response to API href changes Signed-off-by: Marques Johansson --- pkg/controller/ports/assignment/managed.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/controller/ports/assignment/managed.go b/pkg/controller/ports/assignment/managed.go index 2e15805..177becc 100644 --- a/pkg/controller/ports/assignment/managed.go +++ b/pkg/controller/ports/assignment/managed.go @@ -18,7 +18,7 @@ package assignment import ( "context" - "strings" + "path" "github.com/packethost/packngo" "github.com/pkg/errors" @@ -127,7 +127,7 @@ func (e *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex } for _, net := range port.AttachedVirtualNetworks { - if strings.TrimPrefix(net.Href, "/virtual-networks/") == a.Spec.ForProvider.VirtualNetworkID { + if path.Base(net.Href) == a.Spec.ForProvider.VirtualNetworkID { a.Status.SetConditions(xpv1.Available()) o.ResourceExists = true } From 89f2b160785b17aecd8dfef74caff20bb77b926b Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Fri, 11 Jun 2021 17:47:54 -0400 Subject: [PATCH 3/3] add metro based provisioning for VLAN resources Signed-off-by: Marques Johansson --- apis/vlan/v1alpha1/virtualnetwork_types.go | 12 +++++++----- .../crds/vlan.metal.equinix.com_virtualnetworks.yaml | 11 ++++------- pkg/clients/vlan/virtualnetwork.go | 1 + 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apis/vlan/v1alpha1/virtualnetwork_types.go b/apis/vlan/v1alpha1/virtualnetwork_types.go index aab1555..562e3f1 100644 --- a/apis/vlan/v1alpha1/virtualnetwork_types.go +++ b/apis/vlan/v1alpha1/virtualnetwork_types.go @@ -39,8 +39,7 @@ type VirtualNetworkStatus struct { // +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="ID",type="string",JSONPath=".status.atProvider.id" -// +kubebuilder:printcolumn:name="HOSTNAME",type="string",JSONPath=".spec.forProvider.hostname" -// +kubebuilder:printcolumn:name="FACILITY",type="string",JSONPath=".status.atProvider.facility" +// +kubebuilder:printcolumn:name="FACILITY",type="string",JSONPath=".status.atProvider.facilityCode" // +kubebuilder:printcolumn:name="RECLAIM-POLICY",type="string",JSONPath=".spec.reclaimPolicy" // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" // +kubebuilder:subresource:status @@ -69,8 +68,11 @@ type VirtualNetworkList struct { // LateInitialization should update the parameter after creation. type VirtualNetworkParameters struct { // +immutable - // +required - Facility string `json:"facility"` + // +optional + Facility string `json:"facility,omitempty"` + + // +optional + Metro string `json:"metro,omitempty"` // +optional Description *string `json:"description,omitempty"` @@ -82,6 +84,6 @@ type VirtualNetworkObservation struct { ID string `json:"id"` Href string `json:"href,omitempty"` VXLAN int `json:"vxlan,omitempty"` - FacilityCode string `json:"facility_code,omitempty"` + FacilityCode string `json:"facilityCode,omitempty"` CreatedAt *metav1.Time `json:"createdAt,omitempty"` } diff --git a/package/crds/vlan.metal.equinix.com_virtualnetworks.yaml b/package/crds/vlan.metal.equinix.com_virtualnetworks.yaml index 4d0e96c..36fe489 100644 --- a/package/crds/vlan.metal.equinix.com_virtualnetworks.yaml +++ b/package/crds/vlan.metal.equinix.com_virtualnetworks.yaml @@ -16,10 +16,7 @@ spec: - JSONPath: .status.atProvider.id name: ID type: string - - JSONPath: .spec.forProvider.hostname - name: HOSTNAME - type: string - - JSONPath: .status.atProvider.facility + - JSONPath: .status.atProvider.facilityCode name: FACILITY type: string - JSONPath: .spec.reclaimPolicy @@ -69,8 +66,8 @@ spec: type: string facility: type: string - required: - - facility + metro: + type: string 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. @@ -115,7 +112,7 @@ spec: createdAt: format: date-time type: string - facility_code: + facilityCode: type: string href: type: string diff --git a/pkg/clients/vlan/virtualnetwork.go b/pkg/clients/vlan/virtualnetwork.go index 83e4a3d..5ff7ad2 100644 --- a/pkg/clients/vlan/virtualnetwork.go +++ b/pkg/clients/vlan/virtualnetwork.go @@ -76,6 +76,7 @@ func NewClient(ctx context.Context, config *clients.Credentials) (ClientWithDefa func CreateFromVirtualNetwork(d *v1alpha1.VirtualNetwork, projectID string) *packngo.VirtualNetworkCreateRequest { return &packngo.VirtualNetworkCreateRequest{ Facility: d.Spec.ForProvider.Facility, + Metro: d.Spec.ForProvider.Metro, Description: emptyIfNil(d.Spec.ForProvider.Description), ProjectID: projectID, }