sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/services/privateendpoints/spec_test.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package privateendpoints
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	asonetworkv1 "github.com/Azure/azure-service-operator/v2/api/network/v1api20220701"
    24  	"github.com/Azure/azure-service-operator/v2/pkg/genruntime"
    25  	. "github.com/onsi/gomega"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/utils/ptr"
    28  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    29  )
    30  
    31  var (
    32  	fakePrivateEndpoint = PrivateEndpointSpec{
    33  		Name:                       "test_private_endpoint_1",
    34  		ResourceGroup:              "test_rg",
    35  		Location:                   "test_location",
    36  		CustomNetworkInterfaceName: "test_if_name",
    37  		PrivateIPAddresses:         []string{"1.2.3.4", "5.6.7.8"},
    38  		SubnetID:                   "test_subnet_id",
    39  		ApplicationSecurityGroups:  []string{"test_asg1", "test_asg2"},
    40  		ManualApproval:             false,
    41  		PrivateLinkServiceConnections: []PrivateLinkServiceConnection{
    42  			{
    43  				Name:                 "test_plsc_1",
    44  				PrivateLinkServiceID: "test_pl",
    45  				RequestMessage:       "Please approve my connection.",
    46  				GroupIDs:             []string{"aa", "bb"}},
    47  		},
    48  		AdditionalTags: infrav1.Tags{"test_tag1": "test_value1", "test_tag2": "test_value2"},
    49  		ClusterName:    "test_cluster",
    50  	}
    51  
    52  	fakeExtendedLocation = asonetworkv1.ExtendedLocation{
    53  		Name: ptr.To("extended_location_name"),
    54  		Type: ptr.To(asonetworkv1.ExtendedLocationType_EdgeZone),
    55  	}
    56  
    57  	fakeASOPrivateEndpoint = asonetworkv1.PrivateEndpoint{
    58  		ObjectMeta: metav1.ObjectMeta{
    59  			Name: fakePrivateEndpoint.Name,
    60  		},
    61  		Spec: asonetworkv1.PrivateEndpoint_Spec{
    62  			ApplicationSecurityGroups: []asonetworkv1.ApplicationSecurityGroupSpec_PrivateEndpoint_SubResourceEmbedded{
    63  				{
    64  					Reference: &genruntime.ResourceReference{
    65  						ARMID: fakePrivateEndpoint.ApplicationSecurityGroups[0],
    66  					},
    67  				},
    68  				{
    69  					Reference: &genruntime.ResourceReference{
    70  						ARMID: fakePrivateEndpoint.ApplicationSecurityGroups[1],
    71  					},
    72  				},
    73  			},
    74  			AzureName: fakePrivateEndpoint.Name,
    75  			PrivateLinkServiceConnections: []asonetworkv1.PrivateLinkServiceConnection{{
    76  				Name: ptr.To(fakePrivateEndpoint.PrivateLinkServiceConnections[0].Name),
    77  				PrivateLinkServiceReference: &genruntime.ResourceReference{
    78  					ARMID: fakePrivateEndpoint.PrivateLinkServiceConnections[0].PrivateLinkServiceID,
    79  				},
    80  				GroupIds:       fakePrivateEndpoint.PrivateLinkServiceConnections[0].GroupIDs,
    81  				RequestMessage: ptr.To(fakePrivateEndpoint.PrivateLinkServiceConnections[0].RequestMessage),
    82  			}},
    83  			Owner: &genruntime.KnownResourceReference{
    84  				Name: fakePrivateEndpoint.ResourceGroup,
    85  			},
    86  			Subnet: &asonetworkv1.Subnet_PrivateEndpoint_SubResourceEmbedded{
    87  				Reference: &genruntime.ResourceReference{
    88  					ARMID: fakePrivateEndpoint.SubnetID,
    89  				},
    90  			},
    91  			IpConfigurations: []asonetworkv1.PrivateEndpointIPConfiguration{
    92  				{
    93  					PrivateIPAddress: ptr.To(fakePrivateEndpoint.PrivateIPAddresses[0]),
    94  				},
    95  				{
    96  					PrivateIPAddress: ptr.To(fakePrivateEndpoint.PrivateIPAddresses[1]),
    97  				},
    98  			},
    99  			CustomNetworkInterfaceName: ptr.To(fakePrivateEndpoint.CustomNetworkInterfaceName),
   100  			Location:                   ptr.To(fakePrivateEndpoint.Location),
   101  			Tags:                       map[string]string{"sigs.k8s.io_cluster-api-provider-azure_cluster_test_cluster": "owned", "Name": "test_private_endpoint_1", "test_tag1": "test_value1", "test_tag2": "test_value2"},
   102  		},
   103  	}
   104  
   105  	fakeASOPrivateEndpointsStatus = asonetworkv1.PrivateEndpoint_STATUS_PrivateEndpoint_SubResourceEmbedded{
   106  		ApplicationSecurityGroups: []asonetworkv1.ApplicationSecurityGroup_STATUS_PrivateEndpoint_SubResourceEmbedded{
   107  			{
   108  				Id: ptr.To(fakePrivateEndpoint.ApplicationSecurityGroups[0]),
   109  			},
   110  			{
   111  				Id: ptr.To(fakePrivateEndpoint.ApplicationSecurityGroups[1]),
   112  			},
   113  		},
   114  		Name: ptr.To(fakePrivateEndpoint.Name),
   115  		// ... other fields truncated for brevity
   116  	}
   117  )
   118  
   119  func getASOPrivateEndpoint(changes ...func(*asonetworkv1.PrivateEndpoint)) *asonetworkv1.PrivateEndpoint {
   120  	privateEndpoint := fakeASOPrivateEndpoint.DeepCopy()
   121  	for _, change := range changes {
   122  		change(privateEndpoint)
   123  	}
   124  	return privateEndpoint
   125  }
   126  
   127  func TestParameters(t *testing.T) {
   128  	testcases := []struct {
   129  		name          string
   130  		spec          *PrivateEndpointSpec
   131  		existing      *asonetworkv1.PrivateEndpoint
   132  		expect        func(g *WithT, result asonetworkv1.PrivateEndpoint)
   133  		expectedError string
   134  	}{
   135  		{
   136  			name:     "Creating a new PrivateEndpoint",
   137  			spec:     ptr.To(fakePrivateEndpoint),
   138  			existing: nil,
   139  			expect: func(g *WithT, result asonetworkv1.PrivateEndpoint) {
   140  				g.Expect(result).To(Not(BeNil()))
   141  
   142  				// ObjectMeta is populated later in the codeflow
   143  				g.Expect(result.ObjectMeta).To(Equal(metav1.ObjectMeta{}))
   144  
   145  				// Spec is populated from the spec passed in
   146  				g.Expect(result.Spec).To(Equal(getASOPrivateEndpoint().Spec))
   147  			},
   148  		},
   149  		{
   150  			name: "user updates to private endpoints Extended Location should be accepted",
   151  			spec: ptr.To(fakePrivateEndpoint),
   152  			existing: getASOPrivateEndpoint(
   153  				// user added ExtendedLocation
   154  				func(endpoint *asonetworkv1.PrivateEndpoint) {
   155  					endpoint.Spec.ExtendedLocation = fakeExtendedLocation.DeepCopy()
   156  				},
   157  				// user added Status
   158  				func(endpoint *asonetworkv1.PrivateEndpoint) {
   159  					endpoint.Status = fakeASOPrivateEndpointsStatus
   160  				},
   161  			),
   162  			expect: func(g *WithT, result asonetworkv1.PrivateEndpoint) {
   163  				g.Expect(result).To(Not(BeNil()))
   164  				resultantASOPrivateEndpoint := getASOPrivateEndpoint(
   165  					func(endpoint *asonetworkv1.PrivateEndpoint) {
   166  						endpoint.Spec.ExtendedLocation = fakeExtendedLocation.DeepCopy()
   167  					},
   168  				)
   169  
   170  				// ObjectMeta should be carried over from existing private endpoint.
   171  				g.Expect(result.ObjectMeta).To(Equal(resultantASOPrivateEndpoint.ObjectMeta))
   172  
   173  				// Extended location addition is accepted.
   174  				g.Expect(result.Spec).To(Equal(resultantASOPrivateEndpoint.Spec))
   175  
   176  				// Status should be carried over.
   177  				g.Expect(result.Status).To(Equal(fakeASOPrivateEndpointsStatus))
   178  			},
   179  		},
   180  		{
   181  			name: "user updates ASO's private endpoint resource and capz should overwrite it",
   182  			spec: ptr.To(fakePrivateEndpoint),
   183  			existing: getASOPrivateEndpoint(
   184  				// add ExtendedLocation
   185  				func(endpoint *asonetworkv1.PrivateEndpoint) {
   186  					endpoint.Spec.ExtendedLocation = fakeExtendedLocation.DeepCopy()
   187  				},
   188  
   189  				// User also updates private IP addresses and location.
   190  				// This change should be overwritten by CAPZ.
   191  				func(endpoint *asonetworkv1.PrivateEndpoint) {
   192  					endpoint.Spec.IpConfigurations = []asonetworkv1.PrivateEndpointIPConfiguration{
   193  						{
   194  							PrivateIPAddress: ptr.To("9.9.9.9"),
   195  						},
   196  					}
   197  					endpoint.Spec.Location = ptr.To("new_location")
   198  				},
   199  				// add Status
   200  				func(endpoint *asonetworkv1.PrivateEndpoint) {
   201  					endpoint.Status = fakeASOPrivateEndpointsStatus
   202  				},
   203  			),
   204  			expect: func(g *WithT, result asonetworkv1.PrivateEndpoint) {
   205  				g.Expect(result).NotTo(BeNil())
   206  				resultantASOPrivateEndpoint := getASOPrivateEndpoint(
   207  					func(endpoint *asonetworkv1.PrivateEndpoint) {
   208  						endpoint.Spec.ExtendedLocation = fakeExtendedLocation.DeepCopy()
   209  					},
   210  				)
   211  
   212  				// user changes except ExtendedLocation should be overwritten by CAPZ.
   213  				g.Expect(result.ObjectMeta).To(Equal(resultantASOPrivateEndpoint.ObjectMeta))
   214  
   215  				// Extended location addition is accepted.
   216  				g.Expect(result.Spec).To(Equal(resultantASOPrivateEndpoint.Spec))
   217  
   218  				// Status should be carried over.
   219  				g.Expect(result.Status).To(Equal(fakeASOPrivateEndpointsStatus))
   220  			},
   221  		},
   222  	}
   223  
   224  	for _, tc := range testcases {
   225  		tc := tc
   226  		t.Run(tc.name, func(t *testing.T) {
   227  			g := NewWithT(t)
   228  			t.Parallel()
   229  
   230  			result, err := tc.spec.Parameters(context.TODO(), tc.existing)
   231  			if tc.expectedError != "" {
   232  				g.Expect(err).To(HaveOccurred())
   233  				g.Expect(err).To(MatchError(tc.expectedError))
   234  			} else {
   235  				g.Expect(err).NotTo(HaveOccurred())
   236  			}
   237  			tc.expect(g, *result)
   238  		})
   239  	}
   240  }