diff --git a/cmd/kops/integration_test.go b/cmd/kops/integration_test.go index 80533f55746f6..eecc309c1aa96 100644 --- a/cmd/kops/integration_test.go +++ b/cmd/kops/integration_test.go @@ -453,11 +453,10 @@ func TestMinimalGCEDNSNone(t *testing.T) { // TestMinimalScaleway runs tests on a minimal Scaleway cluster with gossip DNS func TestMinimalScaleway(t *testing.T) { t.Setenv("SCW_PROFILE", "REDACTED") - newIntegrationTest("scw-minimal.k8s.local", "minimal_scaleway"). + newIntegrationTest("scw-minimal.example.com", "minimal_scaleway"). withAddons( scwCCMAddon, scwCSIAddon, - dnsControllerAddon, ). runTestTerraformScaleway(t) } diff --git a/dnsprovider/pkg/dnsprovider/providers/scaleway/dns.go b/dnsprovider/pkg/dnsprovider/providers/scaleway/dns.go index 5edd7e7f34da0..98d942b3d02a7 100644 --- a/dnsprovider/pkg/dnsprovider/providers/scaleway/dns.go +++ b/dnsprovider/pkg/dnsprovider/providers/scaleway/dns.go @@ -113,6 +113,9 @@ func (z *zones) List() ([]dnsprovider.Zone, error) { zonesList := []dnsprovider.Zone(nil) for _, dnsZone := range dnsZones.DNSZones { + if dnsZone.Domain == "privatedns" { + continue + } newZone := &zone{ name: dnsZone.Domain, domainAPI: z.domainAPI, diff --git a/pkg/model/scalewaymodel/api_loadbalancer.go b/pkg/model/scalewaymodel/api_loadbalancer.go index e50ddb7fdd035..d4e59fe4e1927 100644 --- a/pkg/model/scalewaymodel/api_loadbalancer.go +++ b/pkg/model/scalewaymodel/api_loadbalancer.go @@ -78,6 +78,7 @@ func (b *APILoadBalancerModelBuilder) Build(c *fi.CloudupModelBuilderContext) er Tags: lbTags, Description: "Load-balancer for kops cluster " + b.ClusterName(), SslCompatibilityLevel: string(lb.SSLCompatibilityLevelSslCompatibilityLevelUnknown), + PrivateNetwork: b.LinkToNetwork(), } c.AddTask(loadBalancer) diff --git a/pkg/model/scalewaymodel/context.go b/pkg/model/scalewaymodel/context.go index 799f0b1e0b4f6..fa67f7eeb156d 100644 --- a/pkg/model/scalewaymodel/context.go +++ b/pkg/model/scalewaymodel/context.go @@ -18,8 +18,14 @@ package scalewaymodel import ( "k8s.io/kops/pkg/model" + "k8s.io/kops/upup/pkg/fi/cloudup/scalewaytasks" ) type ScwModelContext struct { *model.KopsModelContext } + +func (b *ScwModelContext) LinkToNetwork() *scalewaytasks.PrivateNetwork { + name := b.ClusterName() + return &scalewaytasks.PrivateNetwork{Name: &name} +} diff --git a/pkg/model/scalewaymodel/dns.go b/pkg/model/scalewaymodel/dns.go index 5ca1b75082f8f..b4712fbd25be8 100644 --- a/pkg/model/scalewaymodel/dns.go +++ b/pkg/model/scalewaymodel/dns.go @@ -25,7 +25,6 @@ import ( ) const ( - placeholderIP = "203.0.113.123" kopsControllerInternalRecordPrefix = "kops-controller.internal." defaultTTL = uint32(60) ) @@ -45,12 +44,14 @@ func (b *DNSModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { if !b.UseLoadBalancerForAPI() { recordShortName := strings.TrimSuffix(b.Cluster.Spec.API.PublicName, "."+b.Cluster.Spec.DNSZone) dnsAPIExternal := &scalewaytasks.DNSRecord{ - Name: fi.PtrTo(recordShortName), - Data: fi.PtrTo(placeholderIP), - DNSZone: fi.PtrTo(b.Cluster.Spec.DNSZone), - Type: fi.PtrTo(domain.RecordTypeA.String()), - TTL: fi.PtrTo(defaultTTL), - Lifecycle: b.Lifecycle, + Name: fi.PtrTo(recordShortName), + Data: fi.PtrTo(scalewaytasks.PlaceholderIP), + DNSZone: fi.PtrTo(b.Cluster.Spec.DNSZone), + Type: fi.PtrTo(domain.RecordTypeA.String()), + TTL: fi.PtrTo(defaultTTL), + Lifecycle: b.Lifecycle, + IsInternal: fi.PtrTo(false), + ClusterName: fi.PtrTo(b.Cluster.Name), } c.AddTask(dnsAPIExternal) } @@ -58,12 +59,14 @@ func (b *DNSModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { if !b.UseLoadBalancerForInternalAPI() { recordShortName := strings.TrimSuffix(b.Cluster.APIInternalName(), "."+b.Cluster.Spec.DNSZone) dnsAPIInternal := &scalewaytasks.DNSRecord{ - Name: fi.PtrTo(recordShortName), - Data: fi.PtrTo(placeholderIP), - DNSZone: fi.PtrTo(b.Cluster.Spec.DNSZone), - Type: fi.PtrTo(domain.RecordTypeA.String()), - TTL: fi.PtrTo(defaultTTL), - Lifecycle: b.Lifecycle, + Name: fi.PtrTo(recordShortName), + Data: fi.PtrTo(scalewaytasks.PlaceholderIP), + DNSZone: fi.PtrTo(b.Cluster.Spec.DNSZone), + Type: fi.PtrTo(domain.RecordTypeA.String()), + TTL: fi.PtrTo(defaultTTL), + IsInternal: fi.PtrTo(true), + ClusterName: fi.PtrTo(b.Cluster.Name), + Lifecycle: b.Lifecycle, } c.AddTask(dnsAPIInternal) } @@ -71,12 +74,14 @@ func (b *DNSModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { recordSuffix := strings.TrimSuffix(b.Cluster.ObjectMeta.Name, "."+b.Cluster.Spec.DNSZone) recordShortName := kopsControllerInternalRecordPrefix + recordSuffix kopsControllerInternal := &scalewaytasks.DNSRecord{ - Name: fi.PtrTo(recordShortName), - Data: fi.PtrTo(placeholderIP), - DNSZone: fi.PtrTo(b.Cluster.Spec.DNSZone), - Type: fi.PtrTo(domain.RecordTypeA.String()), - TTL: fi.PtrTo(defaultTTL), - Lifecycle: b.Lifecycle, + Name: fi.PtrTo(recordShortName), + Data: fi.PtrTo(scalewaytasks.PlaceholderIP), + DNSZone: fi.PtrTo(b.Cluster.Spec.DNSZone), + Type: fi.PtrTo(domain.RecordTypeA.String()), + TTL: fi.PtrTo(defaultTTL), + IsInternal: fi.PtrTo(true), + ClusterName: fi.PtrTo(b.Cluster.Name), + Lifecycle: b.Lifecycle, } c.AddTask(kopsControllerInternal) diff --git a/pkg/model/scalewaymodel/instances.go b/pkg/model/scalewaymodel/instances.go index 3c53663634143..689794572d6f2 100644 --- a/pkg/model/scalewaymodel/instances.go +++ b/pkg/model/scalewaymodel/instances.go @@ -45,6 +45,7 @@ var _ fi.CloudupModelBuilder = &InstanceModelBuilder{} func (b *InstanceModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { for _, ig := range b.InstanceGroups { name := ig.Name + count := int(fi.ValueOf(ig.Spec.MinSize)) zone, err := scw.ParseZone(ig.Spec.Subnets[0]) if err != nil { return fmt.Errorf("error building instance task for %q: %w", name, err) @@ -63,8 +64,8 @@ func (b *InstanceModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { instanceTags = append(instanceTags, fmt.Sprintf("%s=%s", k, v)) } - instance := scalewaytasks.Instance{ - Count: int(fi.ValueOf(ig.Spec.MinSize)), + instance := &scalewaytasks.Instance{ + Count: count, Name: fi.PtrTo(name), Lifecycle: b.Lifecycle, Zone: fi.PtrTo(string(zone)), @@ -72,12 +73,14 @@ func (b *InstanceModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { Image: fi.PtrTo(ig.Spec.Image), UserData: &userData, Tags: instanceTags, + PrivateNetwork: b.LinkToNetwork(), } if ig.IsControlPlane() { instance.Tags = append(instance.Tags, scaleway.TagNameRolePrefix+"="+scaleway.TagRoleControlPlane) instance.Role = fi.PtrTo(scaleway.TagRoleControlPlane) } else { + instance.Tags = append(instance.Tags, scaleway.TagNameRolePrefix+"="+scaleway.TagRoleWorker) instance.Role = fi.PtrTo(scaleway.TagRoleWorker) } @@ -94,7 +97,19 @@ func (b *InstanceModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { } } - c.AddTask(&instance) + c.AddTask(instance) + + // For each individual server of the instance group, we add a PrivateNIC task to link the server to the private network. + privateNIC := &scalewaytasks.PrivateNIC{ + Name: &name, + Zone: fi.PtrTo(string(zone)), + Tags: instanceTags, + Count: count, + Lifecycle: b.Lifecycle, + Instance: instance, + PrivateNetwork: b.LinkToNetwork(), + } + c.AddTask(privateNIC) } return nil } diff --git a/pkg/model/scalewaymodel/network.go b/pkg/model/scalewaymodel/network.go new file mode 100644 index 0000000000000..2f1aa48110518 --- /dev/null +++ b/pkg/model/scalewaymodel/network.go @@ -0,0 +1,85 @@ +/* +Copyright 2024 The Kubernetes 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 scalewaymodel + +import ( + "fmt" + + "github.com/scaleway/scaleway-sdk-go/scw" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" + "k8s.io/kops/upup/pkg/fi/cloudup/scalewaytasks" +) + +// NetworkModelBuilder configures network objects +type NetworkModelBuilder struct { + *ScwModelContext + Lifecycle fi.Lifecycle +} + +func (b *NetworkModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { + clusterNameTag := scaleway.TagClusterName + "=" + b.ClusterName() + resourceName := b.ClusterName() + zone := scw.Zone(b.Cluster.Spec.Networking.Subnets[0].Zone) + region, err := zone.Region() + if err != nil { + return fmt.Errorf("building network task: %w", err) + } + + vpc := &scalewaytasks.VPC{ + Name: fi.PtrTo(resourceName), + Region: fi.PtrTo(region.String()), + Tags: []string{clusterNameTag}, + Lifecycle: b.Lifecycle, + } + c.AddTask(vpc) + + ipRange := b.Cluster.Spec.Networking.NetworkCIDR + if ipRange == "" { + ipRange = "192.168.1.0/24" + } + + privateNetwork := &scalewaytasks.PrivateNetwork{ + Name: fi.PtrTo(resourceName), + Region: fi.PtrTo(region.String()), + Tags: []string{clusterNameTag}, + Lifecycle: b.Lifecycle, + IPRange: fi.PtrTo(ipRange), + VPC: vpc, + } + c.AddTask(privateNetwork) + + gateway := &scalewaytasks.Gateway{ + Name: fi.PtrTo(resourceName), + Zone: fi.PtrTo(zone.String()), + Tags: []string{clusterNameTag}, + Lifecycle: b.Lifecycle, + //PrivateNetwork: privateNetwork, + } + c.AddTask(gateway) + + gatewayNetwork := &scalewaytasks.GatewayNetwork{ + Name: fi.PtrTo(resourceName), + Zone: fi.PtrTo(zone.String()), + Lifecycle: b.Lifecycle, + Gateway: gateway, + PrivateNetwork: privateNetwork, + } + c.AddTask(gatewayNetwork) + + return nil +} diff --git a/pkg/resources/scaleway/resources.go b/pkg/resources/scaleway/resources.go index bff7d23a83ad1..14e98c0458142 100644 --- a/pkg/resources/scaleway/resources.go +++ b/pkg/resources/scaleway/resources.go @@ -17,27 +17,29 @@ limitations under the License. package scaleway import ( - "fmt" "strings" domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1" - "github.com/scaleway/scaleway-sdk-go/scw" - "k8s.io/kops/pkg/resources" - "k8s.io/kops/upup/pkg/fi" - "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" - iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1" "github.com/scaleway/scaleway-sdk-go/api/instance/v1" "github.com/scaleway/scaleway-sdk-go/api/lb/v1" + "github.com/scaleway/scaleway-sdk-go/api/vpc/v2" + "github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1" + "k8s.io/kops/pkg/resources" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" ) const ( - resourceTypeDNSRecord = "dns-record" - resourceTypeLoadBalancer = "load-balancer" - resourceTypeServer = "server" - resourceTypeServerIP = "server-IP" - resourceTypeSSHKey = "ssh-key" - resourceTypeVolume = "volume" + resourceTypeDNSRecord = "dns-record" + resourceTypeGateway = "gateway" + resourceTypeGatewayNetwork = "gateway-network" + resourceTypeLoadBalancer = "load-balancer" + resourceTypePrivateNetwork = "private-network" + resourceTypeServer = "server" + resourceTypeSSHKey = "ssh-key" + resourceTypeVolume = "volume" + resourceTypeVPC = "vpc" ) type listFn func(fi.Cloud, string) ([]*resources.Resource, error) @@ -47,11 +49,14 @@ func ListResources(cloud scaleway.ScwCloud, clusterInfo resources.ClusterInfo) ( clusterName := clusterInfo.Name listFunctions := []listFn{ + listGateways, + listGatewayNetworks, listLoadBalancers, + listPrivateNetworks, listServers, - listServerIPs, listSSHKeys, listVolumes, + listVPCs, } if !strings.HasSuffix(clusterName, ".k8s.local") && !clusterInfo.UsesNoneDNS { listFunctions = append(listFunctions, listDNSRecords) @@ -94,6 +99,70 @@ func listDNSRecords(cloud fi.Cloud, clusterName string) ([]*resources.Resource, return resourceTrackers, nil } +func listGateways(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) { + c := cloud.(scaleway.ScwCloud) + gws, err := c.GetClusterGateways(clusterName) + if err != nil { + if strings.Contains(err.Error(), "501 Not Implemented") { + return nil, nil + } + return nil, err + } + + resourceTrackers := []*resources.Resource(nil) + for _, gw := range gws { + resourceTracker := &resources.Resource{ + Name: gw.Name, + ID: gw.ID, + Type: resourceTypeGateway, + Deleter: func(cloud fi.Cloud, tracker *resources.Resource) error { + return deleteGateway(cloud, tracker) + }, + Obj: gw, + } + resourceTrackers = append(resourceTrackers, resourceTracker) + } + + return resourceTrackers, nil +} + +func listGatewayNetworks(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) { + c := cloud.(scaleway.ScwCloud) + pns, err := c.GetClusterPrivateNetworks(clusterName) + if err != nil { + return nil, err + } + + resourceTrackers := []*resources.Resource(nil) + for _, pn := range pns { + gwns, err := c.GetClusterGatewayNetworks(pn.ID) + if err != nil { + if strings.Contains(err.Error(), "501 Not Implemented") { + return nil, nil + } + return nil, err + } + + for _, gwn := range gwns { + resourceTracker := &resources.Resource{ + Name: clusterName, + ID: gwn.ID, + Type: resourceTypeGatewayNetwork, + Deleter: func(cloud fi.Cloud, tracker *resources.Resource) error { + return deleteGatewayNetwork(cloud, tracker) + }, + Obj: gwn, + Blocks: []string{ + resourceTypePrivateNetwork + ":" + gwn.PrivateNetworkID, + resourceTypeGateway + ":" + gwn.GatewayID, + }, + } + resourceTrackers = append(resourceTrackers, resourceTracker) + } + } + return resourceTrackers, nil +} + func listLoadBalancers(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) { c := cloud.(scaleway.ScwCloud) lbs, err := c.GetClusterLoadBalancers(clusterName) @@ -118,51 +187,53 @@ func listLoadBalancers(cloud fi.Cloud, clusterName string) ([]*resources.Resourc return resourceTrackers, nil } -func listServers(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) { +func listPrivateNetworks(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) { c := cloud.(scaleway.ScwCloud) - servers, err := c.GetClusterServers(clusterName, nil) + pns, err := c.GetClusterPrivateNetworks(clusterName) if err != nil { + if strings.Contains(err.Error(), "501 Not Implemented") { + return nil, nil + } return nil, err } resourceTrackers := []*resources.Resource(nil) - for _, server := range servers { + for _, pn := range pns { resourceTracker := &resources.Resource{ - Name: server.Name, - ID: server.ID, - Type: resourceTypeServer, + Name: pn.Name, + ID: pn.ID, + Type: resourceTypePrivateNetwork, Deleter: func(cloud fi.Cloud, tracker *resources.Resource) error { - return deleteServer(cloud, tracker) + return deletePrivateNetwork(cloud, tracker) }, - Obj: server, + Obj: pn, + Blocks: []string{resourceTypeVPC + ":" + pn.VpcID}, } resourceTrackers = append(resourceTrackers, resourceTracker) } - return resourceTrackers, nil } -func listServerIPs(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) { +func listServers(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) { c := cloud.(scaleway.ScwCloud) - - ips, err := c.InstanceService().ListIPs(&instance.ListIPsRequest{ - Zone: scw.Zone(c.Zone()), - Tags: []string{fmt.Sprintf("%s=%s", scaleway.TagClusterName, clusterName)}, - }, scw.WithAllPages()) + servers, err := c.GetClusterServers(clusterName, nil) if err != nil { - return nil, fmt.Errorf("listing IPs for deletion: %w", err) + return nil, err } resourceTrackers := []*resources.Resource(nil) - for _, ip := range ips.IPs { + for _, server := range servers { resourceTracker := &resources.Resource{ - Name: ip.Address.String(), - ID: ip.ID, - Type: resourceTypeServerIP, + Name: server.Name, + ID: server.ID, + Type: resourceTypeServer, Deleter: func(cloud fi.Cloud, tracker *resources.Resource) error { - return deleteServerIP(cloud, tracker) + return deleteServer(cloud, tracker) }, - Obj: ip, + Obj: server, + } + for _, privateNic := range server.PrivateNics { + resourceTracker.Blocks = append(resourceTracker.Blocks, resourceTypePrivateNetwork+":"+privateNic.PrivateNetworkID) } resourceTrackers = append(resourceTrackers, resourceTracker) } @@ -221,6 +292,33 @@ func listVolumes(cloud fi.Cloud, clusterName string) ([]*resources.Resource, err return resourceTrackers, nil } +func listVPCs(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) { + c := cloud.(scaleway.ScwCloud) + vpcs, err := c.GetClusterVPCs(clusterName) + if err != nil { + if strings.Contains(err.Error(), "501 Not Implemented") { + return nil, nil + } + return nil, err + } + + resourceTrackers := []*resources.Resource(nil) + for _, v := range vpcs { + resourceTracker := &resources.Resource{ + Name: v.Name, + ID: v.ID, + Type: resourceTypeVPC, + Deleter: func(cloud fi.Cloud, tracker *resources.Resource) error { + return deleteVPC(cloud, tracker) + }, + Obj: v, + } + resourceTrackers = append(resourceTrackers, resourceTracker) + } + + return resourceTrackers, nil +} + func deleteDNSRecord(cloud fi.Cloud, tracker *resources.Resource, domainName string) error { c := cloud.(scaleway.ScwCloud) record := tracker.Obj.(*domain.Record) @@ -228,6 +326,20 @@ func deleteDNSRecord(cloud fi.Cloud, tracker *resources.Resource, domainName str return c.DeleteDNSRecord(record, domainName) } +func deleteGateway(cloud fi.Cloud, tracker *resources.Resource) error { + c := cloud.(scaleway.ScwCloud) + gateway := tracker.Obj.(*vpcgw.Gateway) + + return c.DeleteGateway(gateway) +} + +func deleteGatewayNetwork(cloud fi.Cloud, tracker *resources.Resource) error { + c := cloud.(scaleway.ScwCloud) + gatewayNetwork := tracker.Obj.(*vpcgw.GatewayNetwork) + + return c.DeleteGatewayNetwork(gatewayNetwork) +} + func deleteLoadBalancer(cloud fi.Cloud, tracker *resources.Resource) error { c := cloud.(scaleway.ScwCloud) loadBalancer := tracker.Obj.(*lb.LB) @@ -235,26 +347,18 @@ func deleteLoadBalancer(cloud fi.Cloud, tracker *resources.Resource) error { return c.DeleteLoadBalancer(loadBalancer) } -func deleteServer(cloud fi.Cloud, tracker *resources.Resource) error { +func deletePrivateNetwork(cloud fi.Cloud, tracker *resources.Resource) error { c := cloud.(scaleway.ScwCloud) - server := tracker.Obj.(*instance.Server) + privateNetwork := tracker.Obj.(*vpc.PrivateNetwork) - return c.DeleteServer(server) + return c.DeletePrivateNetwork(privateNetwork) } -func deleteServerIP(cloud fi.Cloud, tracker *resources.Resource) error { +func deleteServer(cloud fi.Cloud, tracker *resources.Resource) error { c := cloud.(scaleway.ScwCloud) - ip := tracker.Obj.(*instance.IP) - - err := c.InstanceService().DeleteIP(&instance.DeleteIPRequest{ - Zone: ip.Zone, - IP: ip.ID, - }) - if err != nil { - return fmt.Errorf("failed to delete instance IP %s: %w", ip.Address.String(), err) - } + server := tracker.Obj.(*instance.Server) - return nil + return c.DeleteServer(server) } func deleteSSHKey(cloud fi.Cloud, tracker *resources.Resource) error { @@ -270,3 +374,10 @@ func deleteVolume(cloud fi.Cloud, tracker *resources.Resource) error { return c.DeleteVolume(volume) } + +func deleteVPC(cloud fi.Cloud, tracker *resources.Resource) error { + c := cloud.(scaleway.ScwCloud) + v := tracker.Obj.(*vpc.VPC) + + return c.DeleteVPC(v) +} diff --git a/protokube/pkg/gossip/scaleway/seeds.go b/protokube/pkg/gossip/scaleway/seeds.go index 7e554967c3622..9804fbcbeb162 100644 --- a/protokube/pkg/gossip/scaleway/seeds.go +++ b/protokube/pkg/gossip/scaleway/seeds.go @@ -68,7 +68,7 @@ func (p *SeedProvider) GetSeeds() ([]string, error) { } for _, server := range servers { - ip, err := scwCloud.GetServerIP(server.ID, server.Zone) + ip, err := scwCloud.GetServerPublicIP(server.ID, server.Zone) if err != nil { return nil, fmt.Errorf("getting server IP: %w", err) } diff --git a/protokube/pkg/protokube/scaleway_volumes.go b/protokube/pkg/protokube/scaleway_volumes.go index fd9749eea59e5..81b26da451833 100644 --- a/protokube/pkg/protokube/scaleway_volumes.go +++ b/protokube/pkg/protokube/scaleway_volumes.go @@ -21,13 +21,11 @@ import ( "net" "github.com/scaleway/scaleway-sdk-go/api/instance/v1" - ipam "github.com/scaleway/scaleway-sdk-go/api/ipam/v1alpha1" "github.com/scaleway/scaleway-sdk-go/scw" "k8s.io/klog/v2" kopsv "k8s.io/kops" "k8s.io/kops/protokube/pkg/gossip" gossipscw "k8s.io/kops/protokube/pkg/gossip/scaleway" - "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" ) @@ -88,35 +86,16 @@ func NewScwCloudProvider() (*ScwCloudProvider, error) { server := serverResponse.Server klog.V(4).Infof("Found the running server: %q", server.Name) - ips, err := ipam.NewAPI(scwClient).ListIPs(&ipam.ListIPsRequest{ - Region: region, - ResourceID: fi.PtrTo(serverID), - IsIPv6: fi.PtrTo(false), - Zonal: fi.PtrTo(zone.String()), - }, scw.WithAllPages()) + privateIP, err := scaleway.GetPrivateIP(scwClient, server.ID, zone) if err != nil { - return nil, fmt.Errorf("listing server's IPs: %w", err) + return nil, fmt.Errorf("failed to get the private IP of the running server: %w", err) } - if ips.TotalCount < 1 { - return nil, fmt.Errorf("expected at least 1 IP attached to the server %s", server.ID) - } - - var ipToReturn string - for _, ipFound := range ips.IPs { - if ipFound.Address.IP.IsPrivate() == true { - ipToReturn = ipFound.Address.IP.String() - break - } - } - if ipToReturn == "" { - ipToReturn = ips.IPs[0].Address.IP.String() - } - klog.V(4).Infof("Found first private net IP of the running server: %q", ipToReturn) + klog.V(4).Infof("Found first private net IP of the running server: %q", privateIP) s := &ScwCloudProvider{ scwClient: scwClient, server: server, - serverIP: net.IP(ipToReturn), + serverIP: net.IP(privateIP), } return s, nil @@ -126,7 +105,7 @@ func (s *ScwCloudProvider) InstanceID() string { return fmt.Sprintf("%s-%s", s.server.Name, s.server.ID) } -func (s ScwCloudProvider) InstanceInternalIP() net.IP { +func (s *ScwCloudProvider) InstanceInternalIP() net.IP { return s.serverIP } diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_cluster-completed.spec_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_cluster-completed.spec_content index 3e529f9cd5641..f5a3e08cbcc72 100644 --- a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_cluster-completed.spec_content +++ b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_cluster-completed.spec_content @@ -2,7 +2,7 @@ apiVersion: kops.k8s.io/v1alpha2 kind: Cluster metadata: creationTimestamp: "2023-01-01T00:00:00Z" - name: scw-minimal.k8s.local + name: scw-minimal.example.com spec: api: loadBalancer: @@ -14,15 +14,15 @@ spec: manageStorageClasses: true cloudProvider: scaleway clusterDNSDomain: cluster.local - configBase: memfs://tests/scw-minimal.k8s.local + configBase: memfs://tests/scw-minimal.example.com containerd: logLevel: info runc: - version: 1.1.5 - version: 1.6.20 + version: 1.1.12 + version: 1.7.13 etcdClusters: - backups: - backupStore: memfs://tests/scw-minimal.k8s.local/backups/etcd/main + backupStore: memfs://tests/scw-minimal.example.com/backups/etcd/main cpuRequest: 200m etcdMembers: - instanceGroup: control-plane-fr-par-1 @@ -31,9 +31,9 @@ spec: backupRetentionDays: 90 memoryRequest: 100Mi name: main - version: 3.5.13 + version: 3.5.9 - backups: - backupStore: memfs://tests/scw-minimal.k8s.local/backups/etcd/events + backupStore: memfs://tests/scw-minimal.example.com/backups/etcd/events cpuRequest: 100m etcdMembers: - instanceGroup: control-plane-fr-par-1 @@ -42,13 +42,11 @@ spec: backupRetentionDays: 90 memoryRequest: 100Mi name: events - version: 3.5.13 - externalDns: - provider: dns-controller + version: 3.5.9 iam: allowContainerRegistry: true legacy: false - keyStore: memfs://tests/scw-minimal.k8s.local/pki + keyStore: memfs://tests/scw-minimal.example.com/pki kubeAPIServer: allowPrivileged: true anonymousAuth: false @@ -72,7 +70,7 @@ spec: - https://127.0.0.1:4001 etcdServersOverrides: - /events#https://127.0.0.1:4002 - image: registry.k8s.io/kube-apiserver:v1.25.5 + image: registry.k8s.io/kube-apiserver:v1.28.6 kubeletPreferredAddressTypes: - InternalIP - Hostname @@ -87,8 +85,8 @@ spec: requestheaderUsernameHeaders: - X-Remote-User securePort: 443 - serviceAccountIssuer: https://api.internal.scw-minimal.k8s.local - serviceAccountJWKSURI: https://api.internal.scw-minimal.k8s.local/openid/v1/jwks + serviceAccountIssuer: https://api.internal.scw-minimal.example.com + serviceAccountJWKSURI: https://api.internal.scw-minimal.example.com/openid/v1/jwks serviceClusterIPRange: 100.64.0.0/13 storageBackend: etcd3 kubeControllerManager: @@ -96,9 +94,9 @@ spec: attachDetachReconcileSyncPeriod: 1m0s cloudProvider: external clusterCIDR: 100.96.0.0/11 - clusterName: scw-minimal.k8s.local + clusterName: scw-minimal.example.com configureCloudRoutes: false - image: registry.k8s.io/kube-controller-manager:v1.25.5 + image: registry.k8s.io/kube-controller-manager:v1.28.6 leaderElection: leaderElect: true logLevel: 2 @@ -113,7 +111,7 @@ spec: nodeLocalDNS: cpuRequest: 25m enabled: false - image: registry.k8s.io/dns/k8s-dns-node-cache:1.23.0 + image: registry.k8s.io/dns/k8s-dns-node-cache:1.22.20 memoryRequest: 5Mi provider: CoreDNS serverIP: 100.64.0.10 @@ -121,10 +119,10 @@ spec: clusterCIDR: 100.96.0.0/11 cpuRequest: 100m enabled: false - image: registry.k8s.io/kube-proxy:v1.25.5 + image: registry.k8s.io/kube-proxy:v1.28.6 logLevel: 2 kubeScheduler: - image: registry.k8s.io/kube-scheduler:v1.25.5 + image: registry.k8s.io/kube-scheduler:v1.28.6 leaderElection: leaderElect: true logLevel: 2 @@ -148,7 +146,7 @@ spec: kubernetesApiAccess: - 0.0.0.0/0 - ::/0 - kubernetesVersion: 1.25.5 + kubernetesVersion: 1.28.6 masterKubelet: anonymousAuth: false cgroupDriver: systemd @@ -199,10 +197,10 @@ spec: sidecarIstioProxyImage: cilium/istio_proxy toFqdnsDnsRejectResponseCode: refused tunnel: vxlan - version: v1.15.4 + version: v1.15.1 nonMasqueradeCIDR: 100.64.0.0/10 podCIDR: 100.96.0.0/11 - secretStore: memfs://tests/scw-minimal.k8s.local/secrets + secretStore: memfs://tests/scw-minimal.example.com/secrets serviceClusterIPRange: 100.64.0.0/13 sshAccess: - 0.0.0.0/0 @@ -213,4 +211,4 @@ spec: zone: fr-par-1 topology: dns: - type: Private + type: None diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_manifests-etcdmanager-events-control-plane-fr-par-1_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_manifests-etcdmanager-events-control-plane-fr-par-1_content index 930bbed9b200c..f806f3bfbef0c 100644 --- a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_manifests-etcdmanager-events-control-plane-fr-par-1_content +++ b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_manifests-etcdmanager-events-control-plane-fr-par-1_content @@ -12,11 +12,11 @@ spec: - /bin/sh - -c - mkfifo /tmp/pipe; (tee -a /var/log/etcd.log < /tmp/pipe & ) ; exec /etcd-manager - --backup-store=memfs://tests/scw-minimal.k8s.local/backups/etcd/events --client-urls=https://__name__:4002 - --cluster-name=etcd-events --containerized=true --dns-suffix=.internal.scw-minimal.k8s.local + --backup-store=memfs://tests/scw-minimal.example.com/backups/etcd/events --client-urls=https://__name__:4002 + --cluster-name=etcd-events --containerized=true --dns-suffix=.internal.scw-minimal.example.com --grpc-port=3997 --peer-urls=https://__name__:2381 --quarantine-client-urls=https://__name__:3995 --v=6 --volume-name-tag=noprefix=kops.k8s.io/instance-group=control-plane-fr-par-1 - --volume-provider=scaleway --volume-tag=noprefix=kops.k8s.io/cluster=scw-minimal.k8s.local + --volume-provider=scaleway --volume-tag=noprefix=kops.k8s.io/cluster=scw-minimal.example.com --volume-tag=noprefix=kops.k8s.io/etcd=events --volume-tag=noprefix=kops.k8s.io/role=ControlPlane > /tmp/pipe 2>&1 env: @@ -25,7 +25,7 @@ spec: - name: SCW_SECRET_KEY - name: ETCD_MANAGER_DAILY_BACKUPS_RETENTION value: 90d - image: registry.k8s.io/etcdadm/etcd-manager-slim:v3.0.20230925 + image: rg.nl-ams.scw.cloud/kops-v2/etcd-manager:latest name: etcd-manager resources: requests: diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_manifests-etcdmanager-main-control-plane-fr-par-1_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_manifests-etcdmanager-main-control-plane-fr-par-1_content index f1d189553d092..6d7c2dd51ca42 100644 --- a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_manifests-etcdmanager-main-control-plane-fr-par-1_content +++ b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_manifests-etcdmanager-main-control-plane-fr-par-1_content @@ -12,11 +12,11 @@ spec: - /bin/sh - -c - mkfifo /tmp/pipe; (tee -a /var/log/etcd.log < /tmp/pipe & ) ; exec /etcd-manager - --backup-store=memfs://tests/scw-minimal.k8s.local/backups/etcd/main --client-urls=https://__name__:4001 - --cluster-name=etcd --containerized=true --dns-suffix=.internal.scw-minimal.k8s.local + --backup-store=memfs://tests/scw-minimal.example.com/backups/etcd/main --client-urls=https://__name__:4001 + --cluster-name=etcd --containerized=true --dns-suffix=.internal.scw-minimal.example.com --grpc-port=3996 --peer-urls=https://__name__:2380 --quarantine-client-urls=https://__name__:3994 --v=6 --volume-name-tag=noprefix=kops.k8s.io/instance-group=control-plane-fr-par-1 - --volume-provider=scaleway --volume-tag=noprefix=kops.k8s.io/cluster=scw-minimal.k8s.local + --volume-provider=scaleway --volume-tag=noprefix=kops.k8s.io/cluster=scw-minimal.example.com --volume-tag=noprefix=kops.k8s.io/etcd=main --volume-tag=noprefix=kops.k8s.io/role=ControlPlane > /tmp/pipe 2>&1 env: @@ -25,7 +25,7 @@ spec: - name: SCW_SECRET_KEY - name: ETCD_MANAGER_DAILY_BACKUPS_RETENTION value: 90d - image: registry.k8s.io/etcdadm/etcd-manager-slim:v3.0.20230925 + image: rg.nl-ams.scw.cloud/kops-v2/etcd-manager:latest name: etcd-manager resources: requests: diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_nodeupconfig-control-plane-fr-par-1_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_nodeupconfig-control-plane-fr-par-1_content index 15a251a51631b..e80352609b1ed 100644 --- a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_nodeupconfig-control-plane-fr-par-1_content +++ b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_nodeupconfig-control-plane-fr-par-1_content @@ -24,7 +24,7 @@ APIServerConfig: - https://127.0.0.1:4001 etcdServersOverrides: - /events#https://127.0.0.1:4002 - image: registry.k8s.io/kube-apiserver:v1.25.5 + image: registry.k8s.io/kube-apiserver:v1.28.6 kubeletPreferredAddressTypes: - InternalIP - Hostname @@ -39,8 +39,8 @@ APIServerConfig: requestheaderUsernameHeaders: - X-Remote-User securePort: 443 - serviceAccountIssuer: https://api.internal.scw-minimal.k8s.local - serviceAccountJWKSURI: https://api.internal.scw-minimal.k8s.local/openid/v1/jwks + serviceAccountIssuer: https://api.internal.scw-minimal.example.com + serviceAccountJWKSURI: https://api.internal.scw-minimal.example.com/openid/v1/jwks serviceClusterIPRange: 100.64.0.0/13 storageBackend: etcd3 ServiceAccountPublicKeys: | @@ -54,21 +54,21 @@ APIServerConfig: -----END RSA PUBLIC KEY----- Assets: amd64: - - 16b23e1254830805b892cfccf2687eb3edb4ea54ffbadb8cc2eee6d3b1fab8e6@https://dl.k8s.io/release/v1.25.5/bin/linux/amd64/kubelet,https://cdn.dl.k8s.io/release/v1.25.5/bin/linux/amd64/kubelet - - 6a660cd44db3d4bfe1563f6689cbe2ffb28ee4baf3532e04fff2d7b909081c29@https://dl.k8s.io/release/v1.25.5/bin/linux/amd64/kubectl,https://cdn.dl.k8s.io/release/v1.25.5/bin/linux/amd64/kubectl - - 962100bbc4baeaaa5748cdbfce941f756b1531c2eadb290129401498bfac21e7@https://storage.googleapis.com/k8s-artifacts-cni/release/v0.9.1/cni-plugins-linux-amd64-v0.9.1.tgz - - bb9a9ccd6517e2a54da748a9f60dc9aa9d79d19d4724663f2386812f083968e2@https://github.com/containerd/containerd/releases/download/v1.6.20/containerd-1.6.20-linux-amd64.tar.gz - - f00b144e86f8c1db347a2e8f22caade07d55382c5f76dd5c0a5b1ab64eaec8bb@https://github.com/opencontainers/runc/releases/download/v1.1.5/runc.amd64 + - 8506df1f20a5f8bba0592f5a4cf5d0cc541047708e664cb88580735400d0b26f@https://dl.k8s.io/release/v1.28.6/bin/linux/amd64/kubelet,https://cdn.dl.k8s.io/release/v1.28.6/bin/linux/amd64/kubelet + - c8351fe0611119fd36634dd3f53eb94ec1a2d43ef9e78b92b4846df5cc7aa7e3@https://dl.k8s.io/release/v1.28.6/bin/linux/amd64/kubectl,https://cdn.dl.k8s.io/release/v1.28.6/bin/linux/amd64/kubectl + - f3a841324845ca6bf0d4091b4fc7f97e18a623172158b72fc3fdcdb9d42d2d37@https://storage.googleapis.com/k8s-artifacts-cni/release/v1.2.0/cni-plugins-linux-amd64-v1.2.0.tgz + - c2371c009dd8b7738663333d91e5ab50d204f8bcae24201f45d59060d12c3a23@https://github.com/containerd/containerd/releases/download/v1.7.13/containerd-1.7.13-linux-amd64.tar.gz + - aadeef400b8f05645768c1476d1023f7875b78f52c7ff1967a6dbce236b8cbd8@https://github.com/opencontainers/runc/releases/download/v1.1.12/runc.amd64 - 71aee9d987b7fad0ff2ade50b038ad7e2356324edc02c54045960a3521b3e6a7@https://github.com/containerd/nerdctl/releases/download/v1.7.4/nerdctl-1.7.4-linux-amd64.tar.gz - d16a1ffb3938f5a19d5c8f45d363bd091ef89c0bc4d44ad16b933eede32fdcbb@https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.29.0/crictl-v1.29.0-linux-amd64.tar.gz - f90ed6dcef534e6d1ae17907dc7eb40614b8945ad4af7f0e98d2be7cde8165c6@https://artifacts.k8s.io/binaries/kops/1.21.0-alpha.1/linux/amd64/protokube,https://github.com/kubernetes/kops/releases/download/v1.21.0-alpha.1/protokube-linux-amd64 - 9992e7eb2a2e93f799e5a9e98eb718637433524bc65f630357201a79f49b13d0@https://artifacts.k8s.io/binaries/kops/1.21.0-alpha.1/linux/amd64/channels,https://github.com/kubernetes/kops/releases/download/v1.21.0-alpha.1/channels-linux-amd64 arm64: - - 18aa53ff59740a11504218905b51b29cc78fb8b5dd818a619141afa9dafb8f5a@https://dl.k8s.io/release/v1.25.5/bin/linux/arm64/kubelet,https://cdn.dl.k8s.io/release/v1.25.5/bin/linux/arm64/kubelet - - 7bc650f28a5b4436df2abcfae5905e461728ba416146beac17a2634fa82a6f0a@https://dl.k8s.io/release/v1.25.5/bin/linux/arm64/kubectl,https://cdn.dl.k8s.io/release/v1.25.5/bin/linux/arm64/kubectl - - ef17764ffd6cdcb16d76401bac1db6acc050c9b088f1be5efa0e094ea3b01df0@https://storage.googleapis.com/k8s-artifacts-cni/release/v0.9.1/cni-plugins-linux-arm64-v0.9.1.tgz - - c3e6a054b18b20fce06c7c3ed53f0989bb4b255c849bede446ebca955f07a9ce@https://github.com/containerd/containerd/releases/download/v1.6.20/containerd-1.6.20-linux-arm64.tar.gz - - 54e79e4d48b9e191767e4abc08be1a8476a1c757e9a9f8c45c6ded001226867f@https://github.com/opencontainers/runc/releases/download/v1.1.5/runc.arm64 + - ee2c060deff330d3338e24aec9734c9e5d5aea4fea1905c0795bccff6997a65e@https://dl.k8s.io/release/v1.28.6/bin/linux/arm64/kubelet,https://cdn.dl.k8s.io/release/v1.28.6/bin/linux/arm64/kubelet + - 0de705659a80c3fef01df43cc0926610fe31482f728b0f992818abd9bdcd2cb9@https://dl.k8s.io/release/v1.28.6/bin/linux/arm64/kubectl,https://cdn.dl.k8s.io/release/v1.28.6/bin/linux/arm64/kubectl + - 525e2b62ba92a1b6f3dc9612449a84aa61652e680f7ebf4eff579795fe464b57@https://storage.googleapis.com/k8s-artifacts-cni/release/v1.2.0/cni-plugins-linux-arm64-v1.2.0.tgz + - 118759e398f35337109592b4d237538872dc12a207d38832b9d04515d0acbc4d@https://github.com/containerd/containerd/releases/download/v1.7.13/containerd-1.7.13-linux-arm64.tar.gz + - 879f910a05c95c10c64ad8eb7d5e3aa8e4b30e65587b3d68e009a3565aed5bb8@https://github.com/opencontainers/runc/releases/download/v1.1.12/runc.arm64 - d8df47708ca57b9cd7f498055126ba7dcfc811d9ba43aae1830c93a09e70e22d@https://github.com/containerd/nerdctl/releases/download/v1.7.4/nerdctl-1.7.4-linux-arm64.tar.gz - 0b615cfa00c331fb9c4524f3d4058a61cc487b33a3436d1269e7832cf283f925@https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.29.0/crictl-v1.29.0-linux-arm64.tar.gz - 2f599c3d54f4c4bdbcc95aaf0c7b513a845d8f9503ec5b34c9f86aa1bc34fc0c@https://artifacts.k8s.io/binaries/kops/1.21.0-alpha.1/linux/arm64/protokube,https://github.com/kubernetes/kops/releases/download/v1.21.0-alpha.1/protokube-linux-arm64 @@ -225,22 +225,22 @@ CAs: MA0GCSqGSIb3DQEBCwUAA0EAVQVx5MUtuAIeePuP9o51xtpT2S6Fvfi8J4ICxnlA 9B7UD2ushcVFPtaeoL9Gfu8aY4KJBeqqg5ojl4qmRnThjw== -----END CERTIFICATE----- -ClusterName: scw-minimal.k8s.local +ClusterName: scw-minimal.example.com ControlPlaneConfig: KubeControllerManager: allocateNodeCIDRs: true attachDetachReconcileSyncPeriod: 1m0s cloudProvider: external clusterCIDR: 100.96.0.0/11 - clusterName: scw-minimal.k8s.local + clusterName: scw-minimal.example.com configureCloudRoutes: false - image: registry.k8s.io/kube-controller-manager:v1.25.5 + image: registry.k8s.io/kube-controller-manager:v1.28.6 leaderElection: leaderElect: true logLevel: 2 useServiceAccountCredentials: true KubeScheduler: - image: registry.k8s.io/kube-scheduler:v1.25.5 + image: registry.k8s.io/kube-scheduler:v1.28.6 leaderElection: leaderElect: true logLevel: 2 @@ -290,27 +290,27 @@ KubeletConfig: shutdownGracePeriodCriticalPods: 10s taints: - node-role.kubernetes.io/control-plane=:NoSchedule -KubernetesVersion: 1.25.5 +KubernetesVersion: 1.28.6 Networking: cilium: {} nonMasqueradeCIDR: 100.64.0.0/10 serviceClusterIPRange: 100.64.0.0/13 UpdatePolicy: automatic channels: -- memfs://tests/scw-minimal.k8s.local/addons/bootstrap-channel.yaml +- memfs://tests/scw-minimal.example.com/addons/bootstrap-channel.yaml configStore: - keypairs: memfs://tests/scw-minimal.k8s.local/pki - secrets: memfs://tests/scw-minimal.k8s.local/secrets + keypairs: memfs://tests/scw-minimal.example.com/pki + secrets: memfs://tests/scw-minimal.example.com/secrets containerdConfig: logLevel: info runc: - version: 1.1.5 - version: 1.6.20 + version: 1.1.12 + version: 1.7.13 etcdManifests: -- memfs://tests/scw-minimal.k8s.local/manifests/etcd/main-control-plane-fr-par-1.yaml -- memfs://tests/scw-minimal.k8s.local/manifests/etcd/events-control-plane-fr-par-1.yaml +- memfs://tests/scw-minimal.example.com/manifests/etcd/main-control-plane-fr-par-1.yaml +- memfs://tests/scw-minimal.example.com/manifests/etcd/events-control-plane-fr-par-1.yaml staticManifests: - key: kube-apiserver-healthcheck path: manifests/static/kube-apiserver-healthcheck.yaml -usesLegacyGossip: true -usesNoneDNS: false +usesLegacyGossip: false +usesNoneDNS: true diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_nodeupconfig-nodes-fr-par-1_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_nodeupconfig-nodes-fr-par-1_content index ff3708076ef45..8c5d8414f0367 100644 --- a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_nodeupconfig-nodes-fr-par-1_content +++ b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_nodeupconfig-nodes-fr-par-1_content @@ -1,26 +1,22 @@ Assets: amd64: - - 16b23e1254830805b892cfccf2687eb3edb4ea54ffbadb8cc2eee6d3b1fab8e6@https://dl.k8s.io/release/v1.25.5/bin/linux/amd64/kubelet,https://cdn.dl.k8s.io/release/v1.25.5/bin/linux/amd64/kubelet - - 6a660cd44db3d4bfe1563f6689cbe2ffb28ee4baf3532e04fff2d7b909081c29@https://dl.k8s.io/release/v1.25.5/bin/linux/amd64/kubectl,https://cdn.dl.k8s.io/release/v1.25.5/bin/linux/amd64/kubectl - - 962100bbc4baeaaa5748cdbfce941f756b1531c2eadb290129401498bfac21e7@https://storage.googleapis.com/k8s-artifacts-cni/release/v0.9.1/cni-plugins-linux-amd64-v0.9.1.tgz - - bb9a9ccd6517e2a54da748a9f60dc9aa9d79d19d4724663f2386812f083968e2@https://github.com/containerd/containerd/releases/download/v1.6.20/containerd-1.6.20-linux-amd64.tar.gz - - f00b144e86f8c1db347a2e8f22caade07d55382c5f76dd5c0a5b1ab64eaec8bb@https://github.com/opencontainers/runc/releases/download/v1.1.5/runc.amd64 + - 8506df1f20a5f8bba0592f5a4cf5d0cc541047708e664cb88580735400d0b26f@https://dl.k8s.io/release/v1.28.6/bin/linux/amd64/kubelet,https://cdn.dl.k8s.io/release/v1.28.6/bin/linux/amd64/kubelet + - c8351fe0611119fd36634dd3f53eb94ec1a2d43ef9e78b92b4846df5cc7aa7e3@https://dl.k8s.io/release/v1.28.6/bin/linux/amd64/kubectl,https://cdn.dl.k8s.io/release/v1.28.6/bin/linux/amd64/kubectl + - f3a841324845ca6bf0d4091b4fc7f97e18a623172158b72fc3fdcdb9d42d2d37@https://storage.googleapis.com/k8s-artifacts-cni/release/v1.2.0/cni-plugins-linux-amd64-v1.2.0.tgz + - c2371c009dd8b7738663333d91e5ab50d204f8bcae24201f45d59060d12c3a23@https://github.com/containerd/containerd/releases/download/v1.7.13/containerd-1.7.13-linux-amd64.tar.gz + - aadeef400b8f05645768c1476d1023f7875b78f52c7ff1967a6dbce236b8cbd8@https://github.com/opencontainers/runc/releases/download/v1.1.12/runc.amd64 - 71aee9d987b7fad0ff2ade50b038ad7e2356324edc02c54045960a3521b3e6a7@https://github.com/containerd/nerdctl/releases/download/v1.7.4/nerdctl-1.7.4-linux-amd64.tar.gz - d16a1ffb3938f5a19d5c8f45d363bd091ef89c0bc4d44ad16b933eede32fdcbb@https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.29.0/crictl-v1.29.0-linux-amd64.tar.gz - - f90ed6dcef534e6d1ae17907dc7eb40614b8945ad4af7f0e98d2be7cde8165c6@https://artifacts.k8s.io/binaries/kops/1.21.0-alpha.1/linux/amd64/protokube,https://github.com/kubernetes/kops/releases/download/v1.21.0-alpha.1/protokube-linux-amd64 - - 9992e7eb2a2e93f799e5a9e98eb718637433524bc65f630357201a79f49b13d0@https://artifacts.k8s.io/binaries/kops/1.21.0-alpha.1/linux/amd64/channels,https://github.com/kubernetes/kops/releases/download/v1.21.0-alpha.1/channels-linux-amd64 arm64: - - 18aa53ff59740a11504218905b51b29cc78fb8b5dd818a619141afa9dafb8f5a@https://dl.k8s.io/release/v1.25.5/bin/linux/arm64/kubelet,https://cdn.dl.k8s.io/release/v1.25.5/bin/linux/arm64/kubelet - - 7bc650f28a5b4436df2abcfae5905e461728ba416146beac17a2634fa82a6f0a@https://dl.k8s.io/release/v1.25.5/bin/linux/arm64/kubectl,https://cdn.dl.k8s.io/release/v1.25.5/bin/linux/arm64/kubectl - - ef17764ffd6cdcb16d76401bac1db6acc050c9b088f1be5efa0e094ea3b01df0@https://storage.googleapis.com/k8s-artifacts-cni/release/v0.9.1/cni-plugins-linux-arm64-v0.9.1.tgz - - c3e6a054b18b20fce06c7c3ed53f0989bb4b255c849bede446ebca955f07a9ce@https://github.com/containerd/containerd/releases/download/v1.6.20/containerd-1.6.20-linux-arm64.tar.gz - - 54e79e4d48b9e191767e4abc08be1a8476a1c757e9a9f8c45c6ded001226867f@https://github.com/opencontainers/runc/releases/download/v1.1.5/runc.arm64 + - ee2c060deff330d3338e24aec9734c9e5d5aea4fea1905c0795bccff6997a65e@https://dl.k8s.io/release/v1.28.6/bin/linux/arm64/kubelet,https://cdn.dl.k8s.io/release/v1.28.6/bin/linux/arm64/kubelet + - 0de705659a80c3fef01df43cc0926610fe31482f728b0f992818abd9bdcd2cb9@https://dl.k8s.io/release/v1.28.6/bin/linux/arm64/kubectl,https://cdn.dl.k8s.io/release/v1.28.6/bin/linux/arm64/kubectl + - 525e2b62ba92a1b6f3dc9612449a84aa61652e680f7ebf4eff579795fe464b57@https://storage.googleapis.com/k8s-artifacts-cni/release/v1.2.0/cni-plugins-linux-arm64-v1.2.0.tgz + - 118759e398f35337109592b4d237538872dc12a207d38832b9d04515d0acbc4d@https://github.com/containerd/containerd/releases/download/v1.7.13/containerd-1.7.13-linux-arm64.tar.gz + - 879f910a05c95c10c64ad8eb7d5e3aa8e4b30e65587b3d68e009a3565aed5bb8@https://github.com/opencontainers/runc/releases/download/v1.1.12/runc.arm64 - d8df47708ca57b9cd7f498055126ba7dcfc811d9ba43aae1830c93a09e70e22d@https://github.com/containerd/nerdctl/releases/download/v1.7.4/nerdctl-1.7.4-linux-arm64.tar.gz - 0b615cfa00c331fb9c4524f3d4058a61cc487b33a3436d1269e7832cf283f925@https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.29.0/crictl-v1.29.0-linux-arm64.tar.gz - - 2f599c3d54f4c4bdbcc95aaf0c7b513a845d8f9503ec5b34c9f86aa1bc34fc0c@https://artifacts.k8s.io/binaries/kops/1.21.0-alpha.1/linux/arm64/protokube,https://github.com/kubernetes/kops/releases/download/v1.21.0-alpha.1/protokube-linux-arm64 - - 9d842e3636a95de2315cdea2be7a282355aac0658ef0b86d5dc2449066538f13@https://artifacts.k8s.io/binaries/kops/1.21.0-alpha.1/linux/arm64/channels,https://github.com/kubernetes/kops/releases/download/v1.21.0-alpha.1/channels-linux-arm64 CAs: {} -ClusterName: scw-minimal.k8s.local +ClusterName: scw-minimal.example.com Hooks: - null - null @@ -46,18 +42,16 @@ KubeletConfig: registerSchedulable: true shutdownGracePeriod: 30s shutdownGracePeriodCriticalPods: 10s -KubernetesVersion: 1.25.5 +KubernetesVersion: 1.28.6 Networking: cilium: {} nonMasqueradeCIDR: 100.64.0.0/10 serviceClusterIPRange: 100.64.0.0/13 UpdatePolicy: automatic -channels: -- memfs://tests/scw-minimal.k8s.local/addons/bootstrap-channel.yaml containerdConfig: logLevel: info runc: - version: 1.1.5 - version: 1.6.20 -usesLegacyGossip: true -usesNoneDNS: false + version: 1.1.12 + version: 1.7.13 +usesLegacyGossip: false +usesNoneDNS: true diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-bootstrap_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-bootstrap_content similarity index 74% rename from tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-bootstrap_content rename to tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-bootstrap_content index 0a0ca50855967..9193c35df959d 100644 --- a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-bootstrap_content +++ b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-bootstrap_content @@ -6,7 +6,7 @@ spec: addons: - id: k8s-1.16 manifest: kops-controller.addons.k8s.io/k8s-1.16.yaml - manifestHash: b3a1523fa01fdd522f3274c6332fce313691df8526ca003d6ff62fa8aba548d7 + manifestHash: 349b1ca0f86cc8eab23b6843f04bca79e66ca66a0bccb7227660f8d19d0cb7e3 name: kops-controller.addons.k8s.io needsRollingUpdate: control-plane selector: @@ -14,7 +14,7 @@ spec: version: 9.99.0 - id: k8s-1.12 manifest: coredns.addons.k8s.io/k8s-1.12.yaml - manifestHash: ddc305f9954ac3602fe6660cf55da056a6da6f3744b7a9d5884400c121799ebb + manifestHash: 4173aae7f5e2a6ca9d4f3d68c74d589650f32997bb278d98bc6f23de5cc5eade name: coredns.addons.k8s.io selector: k8s-addon: coredns.addons.k8s.io @@ -32,16 +32,9 @@ spec: selector: k8s-addon: limit-range.addons.k8s.io version: 9.99.0 - - id: k8s-1.12 - manifest: dns-controller.addons.k8s.io/k8s-1.12.yaml - manifestHash: 79b57e3eaf7b2d6bd690e7498e834e7c4486036e7b992af20121b57c0b98131a - name: dns-controller.addons.k8s.io - selector: - k8s-addon: dns-controller.addons.k8s.io - version: 9.99.0 - id: k8s-1.24 manifest: scaleway-cloud-controller.addons.k8s.io/k8s-1.24.yaml - manifestHash: 62cf06c0ba8f17ad6a877108c1f4bb26a167791aac8c3c6f04804c9e2f61ddab + manifestHash: c4badf3b5f687a6631bb011d5ffeae8904be605973f939a140ed81e5d81045ad name: scaleway-cloud-controller.addons.k8s.io selector: k8s-addon: scaleway-cloud-controller.addons.k8s.io @@ -55,7 +48,7 @@ spec: version: 9.99.0 - id: k8s-1.16 manifest: networking.cilium.io/k8s-1.16-v1.15.yaml - manifestHash: 4652a3f140f42408f62a64d19bd71fabf901ca4330b8a70dc0a7e7880097cc9e + manifestHash: 6c983e7c1fb35d8694e0e01dec3aa668f874ee2c7d7708e7b49f3fdf2623c995 name: networking.cilium.io needsRollingUpdate: all selector: diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-coredns.addons.k8s.io-k8s-1.12_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-coredns.addons.k8s.io-k8s-1.12_content similarity index 99% rename from tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-coredns.addons.k8s.io-k8s-1.12_content rename to tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-coredns.addons.k8s.io-k8s-1.12_content index 72af7cc5a1384..18f049f767aa1 100644 --- a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-coredns.addons.k8s.io-k8s-1.12_content +++ b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-coredns.addons.k8s.io-k8s-1.12_content @@ -80,7 +80,7 @@ data: fallthrough in-addr.arpa ip6.arpa ttl 30 } - hosts /rootfs/etc/hosts k8s.local { + hosts /rootfs/etc/hosts scw-minimal.example.com { ttl 30 fallthrough } diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-kops-controller.addons.k8s.io-k8s-1.16_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-kops-controller.addons.k8s.io-k8s-1.16_content similarity index 76% rename from tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-kops-controller.addons.k8s.io-k8s-1.16_content rename to tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-kops-controller.addons.k8s.io-k8s-1.16_content index c5bf8425fc080..a8d5e598d0819 100644 --- a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-kops-controller.addons.k8s.io-k8s-1.16_content +++ b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-kops-controller.addons.k8s.io-k8s-1.16_content @@ -1,7 +1,7 @@ apiVersion: v1 data: config.yaml: | - {"clusterName":"scw-minimal.k8s.local","cloud":"scaleway","configBase":"memfs://tests/scw-minimal.k8s.local","secretStore":"memfs://tests/scw-minimal.k8s.local/secrets","server":{"Listen":":3988","provider":{"scaleway":{}},"serverKeyPath":"/etc/kubernetes/kops-controller/pki/kops-controller.key","serverCertificatePath":"/etc/kubernetes/kops-controller/pki/kops-controller.crt","caBasePath":"/etc/kubernetes/kops-controller/pki","signingCAs":["kubernetes-ca"],"certNames":["kubelet","kubelet-server"]},"discovery":{"enabled":true}} + {"clusterName":"scw-minimal.example.com","cloud":"scaleway","configBase":"memfs://tests/scw-minimal.example.com","secretStore":"memfs://tests/scw-minimal.example.com/secrets","server":{"Listen":":3988","provider":{"scaleway":{}},"serverKeyPath":"/etc/kubernetes/kops-controller/pki/kops-controller.key","serverCertificatePath":"/etc/kubernetes/kops-controller/pki/kops-controller.crt","caBasePath":"/etc/kubernetes/kops-controller/pki","signingCAs":["kubernetes-ca"],"certNames":["kubelet","kubelet-server"]}} kind: ConfigMap metadata: creationTimestamp: null @@ -33,7 +33,7 @@ spec: template: metadata: annotations: - dns.alpha.kubernetes.io/internal: kops-controller.internal.scw-minimal.k8s.local + dns.alpha.kubernetes.io/internal: kops-controller.internal.scw-minimal.example.com creationTimestamp: null labels: k8s-addon: kops-controller.addons.k8s.io @@ -142,14 +142,6 @@ rules: - list - watch - patch -- apiGroups: - - "" - resources: - - endpoints - verbs: - - get - - list - - watch --- @@ -216,16 +208,6 @@ rules: - leases verbs: - create -- apiGroups: - - "" - resourceNames: - - coredns - resources: - - configmaps - verbs: - - get - - watch - - patch --- @@ -247,51 +229,3 @@ subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: system:serviceaccount:kube-system:kops-controller - ---- - -apiVersion: v1 -kind: Service -metadata: - creationTimestamp: null - labels: - addon.kops.k8s.io/name: kops-controller.addons.k8s.io - app.kubernetes.io/managed-by: kops - discovery.kops.k8s.io/internal-name: api - k8s-addon: kops-controller.addons.k8s.io - name: api-internal - namespace: kube-system -spec: - clusterIP: None - ports: - - name: https - port: 443 - protocol: TCP - targetPort: 443 - selector: - k8s-app: kops-controller - type: ClusterIP - ---- - -apiVersion: v1 -kind: Service -metadata: - creationTimestamp: null - labels: - addon.kops.k8s.io/name: kops-controller.addons.k8s.io - app.kubernetes.io/managed-by: kops - discovery.kops.k8s.io/internal-name: kops-controller - k8s-addon: kops-controller.addons.k8s.io - name: kops-controller-internal - namespace: kube-system -spec: - clusterIP: None - ports: - - name: https - port: 3988 - protocol: TCP - targetPort: 3988 - selector: - k8s-app: kops-controller - type: ClusterIP diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-kubelet-api.rbac.addons.k8s.io-k8s-1.9_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-kubelet-api.rbac.addons.k8s.io-k8s-1.9_content similarity index 100% rename from tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-kubelet-api.rbac.addons.k8s.io-k8s-1.9_content rename to tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-kubelet-api.rbac.addons.k8s.io-k8s-1.9_content diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-limit-range.addons.k8s.io_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-limit-range.addons.k8s.io_content similarity index 100% rename from tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-limit-range.addons.k8s.io_content rename to tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-limit-range.addons.k8s.io_content diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-networking.cilium.io-k8s-1.16_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-networking.cilium.io-k8s-1.16_content similarity index 98% rename from tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-networking.cilium.io-k8s-1.16_content rename to tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-networking.cilium.io-k8s-1.16_content index cde6cfc6ee066..05b1917f266b2 100644 --- a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-networking.cilium.io-k8s-1.16_content +++ b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-networking.cilium.io-k8s-1.16_content @@ -548,7 +548,7 @@ spec: name: cilium-config optional: true - name: KUBERNETES_SERVICE_HOST - value: api.internal.scw-minimal.k8s.local + value: api.internal.scw-minimal.example.com - name: KUBERNETES_SERVICE_PORT value: "443" image: quay.io/cilium/cilium:v1.15.4 @@ -645,7 +645,7 @@ spec: apiVersion: v1 fieldPath: metadata.namespace - name: KUBERNETES_SERVICE_HOST - value: api.internal.scw-minimal.k8s.local + value: api.internal.scw-minimal.example.com - name: KUBERNETES_SERVICE_PORT value: "443" image: quay.io/cilium/cilium:v1.15.4 @@ -715,7 +715,7 @@ spec: name: cilium-config optional: true - name: KUBERNETES_SERVICE_HOST - value: api.internal.scw-minimal.k8s.local + value: api.internal.scw-minimal.example.com - name: KUBERNETES_SERVICE_PORT value: "443" image: quay.io/cilium/cilium:v1.15.4 @@ -866,7 +866,7 @@ spec: - args: - --config-dir=/tmp/cilium/config-map - --debug=$(CILIUM_DEBUG) - - --eni-tags=KubernetesCluster=scw-minimal.k8s.local + - --eni-tags=KubernetesCluster=scw-minimal.example.com command: - cilium-operator env: @@ -887,7 +887,7 @@ spec: name: cilium-config optional: true - name: KUBERNETES_SERVICE_HOST - value: api.internal.scw-minimal.k8s.local + value: api.internal.scw-minimal.example.com - name: KUBERNETES_SERVICE_PORT value: "443" image: quay.io/cilium/operator:v1.15.4 diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-scaleway-cloud-controller.addons.k8s.io-k8s-1.24_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-scaleway-cloud-controller.addons.k8s.io-k8s-1.24_content similarity index 99% rename from tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-scaleway-cloud-controller.addons.k8s.io-k8s-1.24_content rename to tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-scaleway-cloud-controller.addons.k8s.io-k8s-1.24_content index 21969be70bd0c..1ce25a853b359 100644 --- a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-scaleway-cloud-controller.addons.k8s.io-k8s-1.24_content +++ b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-scaleway-cloud-controller.addons.k8s.io-k8s-1.24_content @@ -9,6 +9,7 @@ metadata: name: scaleway-secret namespace: kube-system stringData: + PN_ID: scw-minimal.example.com SCW_ACCESS_KEY: null SCW_DEFAULT_PROJECT_ID: null SCW_DEFAULT_REGION: fr-par diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-scaleway-csi-driver.addons.k8s.io-k8s-1.24_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-scaleway-csi-driver.addons.k8s.io-k8s-1.24_content similarity index 100% rename from tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-scaleway-csi-driver.addons.k8s.io-k8s-1.24_content rename to tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.example.com-addons-scaleway-csi-driver.addons.k8s.io-k8s-1.24_content diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-dns-controller.addons.k8s.io-k8s-1.12_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-dns-controller.addons.k8s.io-k8s-1.12_content deleted file mode 100644 index e625b93b139bb..0000000000000 --- a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_scw-minimal.k8s.local-addons-dns-controller.addons.k8s.io-k8s-1.12_content +++ /dev/null @@ -1,144 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - creationTimestamp: null - labels: - addon.kops.k8s.io/name: dns-controller.addons.k8s.io - app.kubernetes.io/managed-by: kops - k8s-addon: dns-controller.addons.k8s.io - k8s-app: dns-controller - version: v1.30.0-alpha.1 - name: dns-controller - namespace: kube-system -spec: - replicas: 1 - selector: - matchLabels: - k8s-app: dns-controller - strategy: - type: Recreate - template: - metadata: - creationTimestamp: null - labels: - k8s-addon: dns-controller.addons.k8s.io - k8s-app: dns-controller - kops.k8s.io/managed-by: kops - version: v1.30.0-alpha.1 - spec: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: node-role.kubernetes.io/control-plane - operator: Exists - - matchExpressions: - - key: node-role.kubernetes.io/master - operator: Exists - containers: - - args: - - --watch-ingress=false - - --dns=gossip - - --gossip-seed=127.0.0.1:3999 - - --gossip-protocol-secondary=memberlist - - --gossip-listen-secondary=0.0.0.0:3993 - - --gossip-seed-secondary=127.0.0.1:4000 - - --internal-ipv4 - - --zone=*/* - - -v=2 - command: null - env: - - name: KUBERNETES_SERVICE_HOST - value: 127.0.0.1 - envFrom: - - secretRef: - name: scaleway-secret - image: registry.k8s.io/kops/dns-controller:1.30.0-alpha.1 - name: dns-controller - resources: - requests: - cpu: 50m - memory: 50Mi - securityContext: - runAsNonRoot: true - dnsPolicy: Default - hostNetwork: true - nodeSelector: null - priorityClassName: system-cluster-critical - serviceAccount: dns-controller - tolerations: - - key: node.cloudprovider.kubernetes.io/uninitialized - operator: Exists - - key: node.kubernetes.io/not-ready - operator: Exists - - key: node-role.kubernetes.io/control-plane - operator: Exists - - key: node-role.kubernetes.io/master - operator: Exists - ---- - -apiVersion: v1 -kind: ServiceAccount -metadata: - creationTimestamp: null - labels: - addon.kops.k8s.io/name: dns-controller.addons.k8s.io - app.kubernetes.io/managed-by: kops - k8s-addon: dns-controller.addons.k8s.io - name: dns-controller - namespace: kube-system - ---- - -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - creationTimestamp: null - labels: - addon.kops.k8s.io/name: dns-controller.addons.k8s.io - app.kubernetes.io/managed-by: kops - k8s-addon: dns-controller.addons.k8s.io - name: kops:dns-controller -rules: -- apiGroups: - - "" - resources: - - endpoints - - services - - pods - - ingress - - nodes - verbs: - - get - - list - - watch -- apiGroups: - - networking.k8s.io - resources: - - ingresses - verbs: - - get - - list - - watch - ---- - -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - creationTimestamp: null - labels: - addon.kops.k8s.io/name: dns-controller.addons.k8s.io - app.kubernetes.io/managed-by: kops - k8s-addon: dns-controller.addons.k8s.io - name: kops:dns-controller -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: kops:dns-controller -subjects: -- apiGroup: rbac.authorization.k8s.io - kind: User - name: system:serviceaccount:kube-system:dns-controller diff --git a/tests/integration/update_cluster/minimal_scaleway/data/scaleway_instance_server_control-plane-fr-par-1-0_user_data b/tests/integration/update_cluster/minimal_scaleway/data/scaleway_instance_server_control-plane-fr-par-1-0_user_data index d7b5cc1fef90a..42a560e9067be 100644 --- a/tests/integration/update_cluster/minimal_scaleway/data/scaleway_instance_server_control-plane-fr-par-1-0_user_data +++ b/tests/integration/update_cluster/minimal_scaleway/data/scaleway_instance_server_control-plane-fr-par-1-0_user_data @@ -128,11 +128,11 @@ ensure-install-dir cat > conf/kube_env.yaml << '__EOF_KUBE_ENV' CloudProvider: scaleway -ClusterName: scw-minimal.k8s.local -ConfigBase: memfs://tests/scw-minimal.k8s.local +ClusterName: scw-minimal.example.com +ConfigBase: memfs://tests/scw-minimal.example.com InstanceGroupName: control-plane-fr-par-1 InstanceGroupRole: ControlPlane -NodeupConfigHash: qkMcgEhTy3oHXOw6o8xCcTcSY7T7PLlQIesc5/8Ntvg= +NodeupConfigHash: /srPUs9wfFh9GZm7GpynKobnMWgo2YO6lyNg19Av9rk= __EOF_KUBE_ENV diff --git a/tests/integration/update_cluster/minimal_scaleway/data/scaleway_instance_server_nodes-fr-par-1-0_user_data b/tests/integration/update_cluster/minimal_scaleway/data/scaleway_instance_server_nodes-fr-par-1-0_user_data index d58243ac1f99d..c43c28c3870da 100644 --- a/tests/integration/update_cluster/minimal_scaleway/data/scaleway_instance_server_nodes-fr-par-1-0_user_data +++ b/tests/integration/update_cluster/minimal_scaleway/data/scaleway_instance_server_nodes-fr-par-1-0_user_data @@ -8,9 +8,6 @@ NODEUP_HASH_AMD64=585fbda0f0a43184656b4bfc0cc5f0c0b85612faf43b8816acca1f99d422c9 NODEUP_URL_ARM64=https://artifacts.k8s.io/binaries/kops/1.21.0-alpha.1/linux/arm64/nodeup,https://github.com/kubernetes/kops/releases/download/v1.21.0-alpha.1/nodeup-linux-arm64 NODEUP_HASH_ARM64=7603675379699105a9b9915ff97718ea99b1bbb01a4c184e2f827c8a96e8e865 -export SCW_ACCESS_KEY= -export SCW_DEFAULT_PROJECT_ID= -export SCW_SECRET_KEY= @@ -128,7 +125,7 @@ ensure-install-dir cat > conf/kube_env.yaml << '__EOF_KUBE_ENV' CloudProvider: scaleway -ClusterName: scw-minimal.k8s.local +ClusterName: scw-minimal.example.com ConfigServer: CACertificates: | -----BEGIN CERTIFICATE----- @@ -152,10 +149,10 @@ ConfigServer: 9B7UD2ushcVFPtaeoL9Gfu8aY4KJBeqqg5ojl4qmRnThjw== -----END CERTIFICATE----- servers: - - https://kops-controller.internal.scw-minimal.k8s.local:3988/ + - https://kops-controller.internal.scw-minimal.example.com:3988/ InstanceGroupName: nodes-fr-par-1 InstanceGroupRole: Node -NodeupConfigHash: yA/wFF1FUShF6l1o35leWYVzXlzUIdXNllHEnm1FfjI= +NodeupConfigHash: d7gC7Jb6IZwRD8DlQf9iR/p5DRak6EB1SEzlzPZeyf8= __EOF_KUBE_ENV diff --git a/tests/integration/update_cluster/minimal_scaleway/in-v1alpha2.yaml b/tests/integration/update_cluster/minimal_scaleway/in-v1alpha2.yaml index b88f6a364fd99..bd69866588337 100644 --- a/tests/integration/update_cluster/minimal_scaleway/in-v1alpha2.yaml +++ b/tests/integration/update_cluster/minimal_scaleway/in-v1alpha2.yaml @@ -2,7 +2,7 @@ apiVersion: kops.k8s.io/v1alpha2 kind: Cluster metadata: creationTimestamp: "2023-01-01T00:00:00Z" - name: scw-minimal.k8s.local + name: scw-minimal.example.com spec: api: loadBalancer: @@ -11,18 +11,22 @@ spec: rbac: {} channel: stable cloudProvider: scaleway - configBase: memfs://tests/scw-minimal.k8s.local + configBase: memfs://tests/scw-minimal.example.com etcdClusters: - cpuRequest: 200m etcdMembers: - instanceGroup: control-plane-fr-par-1 name: etcd-1 + manager: + backupRetentionDays: 90 memoryRequest: 100Mi name: main - cpuRequest: 100m etcdMembers: - instanceGroup: control-plane-fr-par-1 name: etcd-1 + manager: + backupRetentionDays: 90 memoryRequest: 100Mi name: events iam: @@ -35,7 +39,7 @@ spec: kubernetesApiAccess: - 0.0.0.0/0 - ::/0 - kubernetesVersion: 1.25.5 + kubernetesVersion: 1.28.6 networking: cilium: enableNodePort: true @@ -49,7 +53,7 @@ spec: zone: fr-par-1 topology: dns: - type: Private + type: None --- @@ -58,7 +62,7 @@ kind: InstanceGroup metadata: creationTimestamp: "2023-01-01T00:00:00Z" labels: - kops.k8s.io/cluster: scw-minimal.k8s.local + kops.k8s.io/cluster: scw-minimal.example.com name: control-plane-fr-par-1 spec: image: ubuntu_focal @@ -76,7 +80,7 @@ kind: InstanceGroup metadata: creationTimestamp: "2023-01-01T00:00:00Z" labels: - kops.k8s.io/cluster: scw-minimal.k8s.local + kops.k8s.io/cluster: scw-minimal.example.com name: nodes-fr-par-1 spec: image: ubuntu_focal diff --git a/tests/integration/update_cluster/minimal_scaleway/kubernetes.tf b/tests/integration/update_cluster/minimal_scaleway/kubernetes.tf index e71149eb5a657..d420afd9b1252 100644 --- a/tests/integration/update_cluster/minimal_scaleway/kubernetes.tf +++ b/tests/integration/update_cluster/minimal_scaleway/kubernetes.tf @@ -1,11 +1,11 @@ locals { - cluster_name = "scw-minimal.k8s.local" + cluster_name = "scw-minimal.example.com" region = "fr-par" zone = "fr-par-1" } output "cluster_name" { - value = "scw-minimal.k8s.local" + value = "scw-minimal.example.com" } output "region" { @@ -29,7 +29,7 @@ provider "aws" { resource "aws_s3_object" "cluster-completed-spec" { bucket = "testingBucket" content = file("${path.module}/data/aws_s3_object_cluster-completed.spec_content") - key = "tests/scw-minimal.k8s.local/cluster-completed.spec" + key = "tests/scw-minimal.example.com/cluster-completed.spec" provider = aws.files server_side_encryption = "AES256" } @@ -37,7 +37,7 @@ resource "aws_s3_object" "cluster-completed-spec" { resource "aws_s3_object" "etcd-cluster-spec-events" { bucket = "testingBucket" content = file("${path.module}/data/aws_s3_object_etcd-cluster-spec-events_content") - key = "tests/scw-minimal.k8s.local/backups/etcd/events/control/etcd-cluster-spec" + key = "tests/scw-minimal.example.com/backups/etcd/events/control/etcd-cluster-spec" provider = aws.files server_side_encryption = "AES256" } @@ -45,7 +45,7 @@ resource "aws_s3_object" "etcd-cluster-spec-events" { resource "aws_s3_object" "etcd-cluster-spec-main" { bucket = "testingBucket" content = file("${path.module}/data/aws_s3_object_etcd-cluster-spec-main_content") - key = "tests/scw-minimal.k8s.local/backups/etcd/main/control/etcd-cluster-spec" + key = "tests/scw-minimal.example.com/backups/etcd/main/control/etcd-cluster-spec" provider = aws.files server_side_encryption = "AES256" } @@ -53,7 +53,7 @@ resource "aws_s3_object" "etcd-cluster-spec-main" { resource "aws_s3_object" "kops-version-txt" { bucket = "testingBucket" content = file("${path.module}/data/aws_s3_object_kops-version.txt_content") - key = "tests/scw-minimal.k8s.local/kops-version.txt" + key = "tests/scw-minimal.example.com/kops-version.txt" provider = aws.files server_side_encryption = "AES256" } @@ -61,7 +61,7 @@ resource "aws_s3_object" "kops-version-txt" { resource "aws_s3_object" "manifests-etcdmanager-events-control-plane-fr-par-1" { bucket = "testingBucket" content = file("${path.module}/data/aws_s3_object_manifests-etcdmanager-events-control-plane-fr-par-1_content") - key = "tests/scw-minimal.k8s.local/manifests/etcd/events-control-plane-fr-par-1.yaml" + key = "tests/scw-minimal.example.com/manifests/etcd/events-control-plane-fr-par-1.yaml" provider = aws.files server_side_encryption = "AES256" } @@ -69,7 +69,7 @@ resource "aws_s3_object" "manifests-etcdmanager-events-control-plane-fr-par-1" { resource "aws_s3_object" "manifests-etcdmanager-main-control-plane-fr-par-1" { bucket = "testingBucket" content = file("${path.module}/data/aws_s3_object_manifests-etcdmanager-main-control-plane-fr-par-1_content") - key = "tests/scw-minimal.k8s.local/manifests/etcd/main-control-plane-fr-par-1.yaml" + key = "tests/scw-minimal.example.com/manifests/etcd/main-control-plane-fr-par-1.yaml" provider = aws.files server_side_encryption = "AES256" } @@ -77,7 +77,7 @@ resource "aws_s3_object" "manifests-etcdmanager-main-control-plane-fr-par-1" { resource "aws_s3_object" "manifests-static-kube-apiserver-healthcheck" { bucket = "testingBucket" content = file("${path.module}/data/aws_s3_object_manifests-static-kube-apiserver-healthcheck_content") - key = "tests/scw-minimal.k8s.local/manifests/static/kube-apiserver-healthcheck.yaml" + key = "tests/scw-minimal.example.com/manifests/static/kube-apiserver-healthcheck.yaml" provider = aws.files server_side_encryption = "AES256" } @@ -85,7 +85,7 @@ resource "aws_s3_object" "manifests-static-kube-apiserver-healthcheck" { resource "aws_s3_object" "nodeupconfig-control-plane-fr-par-1" { bucket = "testingBucket" content = file("${path.module}/data/aws_s3_object_nodeupconfig-control-plane-fr-par-1_content") - key = "tests/scw-minimal.k8s.local/igconfig/control-plane/control-plane-fr-par-1/nodeupconfig.yaml" + key = "tests/scw-minimal.example.com/igconfig/control-plane/control-plane-fr-par-1/nodeupconfig.yaml" provider = aws.files server_side_encryption = "AES256" } @@ -93,94 +93,98 @@ resource "aws_s3_object" "nodeupconfig-control-plane-fr-par-1" { resource "aws_s3_object" "nodeupconfig-nodes-fr-par-1" { bucket = "testingBucket" content = file("${path.module}/data/aws_s3_object_nodeupconfig-nodes-fr-par-1_content") - key = "tests/scw-minimal.k8s.local/igconfig/node/nodes-fr-par-1/nodeupconfig.yaml" + key = "tests/scw-minimal.example.com/igconfig/node/nodes-fr-par-1/nodeupconfig.yaml" provider = aws.files server_side_encryption = "AES256" } -resource "aws_s3_object" "scw-minimal-k8s-local-addons-bootstrap" { +resource "aws_s3_object" "scw-minimal-example-com-addons-bootstrap" { bucket = "testingBucket" - content = file("${path.module}/data/aws_s3_object_scw-minimal.k8s.local-addons-bootstrap_content") - key = "tests/scw-minimal.k8s.local/addons/bootstrap-channel.yaml" + content = file("${path.module}/data/aws_s3_object_scw-minimal.example.com-addons-bootstrap_content") + key = "tests/scw-minimal.example.com/addons/bootstrap-channel.yaml" provider = aws.files server_side_encryption = "AES256" } -resource "aws_s3_object" "scw-minimal-k8s-local-addons-coredns-addons-k8s-io-k8s-1-12" { +resource "aws_s3_object" "scw-minimal-example-com-addons-coredns-addons-k8s-io-k8s-1-12" { bucket = "testingBucket" - content = file("${path.module}/data/aws_s3_object_scw-minimal.k8s.local-addons-coredns.addons.k8s.io-k8s-1.12_content") - key = "tests/scw-minimal.k8s.local/addons/coredns.addons.k8s.io/k8s-1.12.yaml" + content = file("${path.module}/data/aws_s3_object_scw-minimal.example.com-addons-coredns.addons.k8s.io-k8s-1.12_content") + key = "tests/scw-minimal.example.com/addons/coredns.addons.k8s.io/k8s-1.12.yaml" provider = aws.files server_side_encryption = "AES256" } -resource "aws_s3_object" "scw-minimal-k8s-local-addons-dns-controller-addons-k8s-io-k8s-1-12" { +resource "aws_s3_object" "scw-minimal-example-com-addons-kops-controller-addons-k8s-io-k8s-1-16" { bucket = "testingBucket" - content = file("${path.module}/data/aws_s3_object_scw-minimal.k8s.local-addons-dns-controller.addons.k8s.io-k8s-1.12_content") - key = "tests/scw-minimal.k8s.local/addons/dns-controller.addons.k8s.io/k8s-1.12.yaml" + content = file("${path.module}/data/aws_s3_object_scw-minimal.example.com-addons-kops-controller.addons.k8s.io-k8s-1.16_content") + key = "tests/scw-minimal.example.com/addons/kops-controller.addons.k8s.io/k8s-1.16.yaml" provider = aws.files server_side_encryption = "AES256" } -resource "aws_s3_object" "scw-minimal-k8s-local-addons-kops-controller-addons-k8s-io-k8s-1-16" { +resource "aws_s3_object" "scw-minimal-example-com-addons-kubelet-api-rbac-addons-k8s-io-k8s-1-9" { bucket = "testingBucket" - content = file("${path.module}/data/aws_s3_object_scw-minimal.k8s.local-addons-kops-controller.addons.k8s.io-k8s-1.16_content") - key = "tests/scw-minimal.k8s.local/addons/kops-controller.addons.k8s.io/k8s-1.16.yaml" + content = file("${path.module}/data/aws_s3_object_scw-minimal.example.com-addons-kubelet-api.rbac.addons.k8s.io-k8s-1.9_content") + key = "tests/scw-minimal.example.com/addons/kubelet-api.rbac.addons.k8s.io/k8s-1.9.yaml" provider = aws.files server_side_encryption = "AES256" } -resource "aws_s3_object" "scw-minimal-k8s-local-addons-kubelet-api-rbac-addons-k8s-io-k8s-1-9" { +resource "aws_s3_object" "scw-minimal-example-com-addons-limit-range-addons-k8s-io" { bucket = "testingBucket" - content = file("${path.module}/data/aws_s3_object_scw-minimal.k8s.local-addons-kubelet-api.rbac.addons.k8s.io-k8s-1.9_content") - key = "tests/scw-minimal.k8s.local/addons/kubelet-api.rbac.addons.k8s.io/k8s-1.9.yaml" + content = file("${path.module}/data/aws_s3_object_scw-minimal.example.com-addons-limit-range.addons.k8s.io_content") + key = "tests/scw-minimal.example.com/addons/limit-range.addons.k8s.io/v1.5.0.yaml" provider = aws.files server_side_encryption = "AES256" } -resource "aws_s3_object" "scw-minimal-k8s-local-addons-limit-range-addons-k8s-io" { +resource "aws_s3_object" "scw-minimal-example-com-addons-networking-cilium-io-k8s-1-16" { bucket = "testingBucket" - content = file("${path.module}/data/aws_s3_object_scw-minimal.k8s.local-addons-limit-range.addons.k8s.io_content") - key = "tests/scw-minimal.k8s.local/addons/limit-range.addons.k8s.io/v1.5.0.yaml" + content = file("${path.module}/data/aws_s3_object_scw-minimal.example.com-addons-networking.cilium.io-k8s-1.16_content") + key = "tests/scw-minimal.example.com/addons/networking.cilium.io/k8s-1.16-v1.15.yaml" provider = aws.files server_side_encryption = "AES256" } -resource "aws_s3_object" "scw-minimal-k8s-local-addons-networking-cilium-io-k8s-1-16" { +resource "aws_s3_object" "scw-minimal-example-com-addons-scaleway-cloud-controller-addons-k8s-io-k8s-1-24" { bucket = "testingBucket" - content = file("${path.module}/data/aws_s3_object_scw-minimal.k8s.local-addons-networking.cilium.io-k8s-1.16_content") - key = "tests/scw-minimal.k8s.local/addons/networking.cilium.io/k8s-1.16-v1.15.yaml" + content = file("${path.module}/data/aws_s3_object_scw-minimal.example.com-addons-scaleway-cloud-controller.addons.k8s.io-k8s-1.24_content") + key = "tests/scw-minimal.example.com/addons/scaleway-cloud-controller.addons.k8s.io/k8s-1.24.yaml" provider = aws.files server_side_encryption = "AES256" } -resource "aws_s3_object" "scw-minimal-k8s-local-addons-scaleway-cloud-controller-addons-k8s-io-k8s-1-24" { +resource "aws_s3_object" "scw-minimal-example-com-addons-scaleway-csi-driver-addons-k8s-io-k8s-1-24" { bucket = "testingBucket" - content = file("${path.module}/data/aws_s3_object_scw-minimal.k8s.local-addons-scaleway-cloud-controller.addons.k8s.io-k8s-1.24_content") - key = "tests/scw-minimal.k8s.local/addons/scaleway-cloud-controller.addons.k8s.io/k8s-1.24.yaml" + content = file("${path.module}/data/aws_s3_object_scw-minimal.example.com-addons-scaleway-csi-driver.addons.k8s.io-k8s-1.24_content") + key = "tests/scw-minimal.example.com/addons/scaleway-csi-driver.addons.k8s.io/k8s-1.24.yaml" provider = aws.files server_side_encryption = "AES256" } -resource "aws_s3_object" "scw-minimal-k8s-local-addons-scaleway-csi-driver-addons-k8s-io-k8s-1-24" { - bucket = "testingBucket" - content = file("${path.module}/data/aws_s3_object_scw-minimal.k8s.local-addons-scaleway-csi-driver.addons.k8s.io-k8s-1.24_content") - key = "tests/scw-minimal.k8s.local/addons/scaleway-csi-driver.addons.k8s.io/k8s-1.24.yaml" - provider = aws.files - server_side_encryption = "AES256" -} - -resource "scaleway_iam_ssh_key" "kubernetes-scw-minimal-k8s-local-be_9e_c3_eb_cb_0c_c0_50_ea_bd_b4_5a_15_e3_40_2a" { - name = "kubernetes.scw-minimal.k8s.local-be:9e:c3:eb:cb:0c:c0:50:ea:bd:b4:5a:15:e3:40:2a" +resource "scaleway_iam_ssh_key" "kubernetes-scw-minimal-example-com-be_9e_c3_eb_cb_0c_c0_50_ea_bd_b4_5a_15_e3_40_2a" { + name = "kubernetes.scw-minimal.example.com-be:9e:c3:eb:cb:0c:c0:50:ea:bd:b4:5a:15:e3:40:2a" public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDKqbVEozfAqng0gx8HTUu69EppcE5SWet6MpwrGShqMVUC4wkoiuVtJDPhMmWmdt7B7Ttc5pvnAZAZaQ6TKMguyBoAyS7qOTLU9/hM803XtSiwQUftOXiJfmsqAXEc8yDyb7UnrF8X7aA3gQJsnQBGJGdp+C88dPHNZenw4PnQc8BNYTCXG9d8F5vJ3xQ5qbiG4HVNoQ2CZh2ht+GedZJ3hl9lMJ24kE/cbMCLKxabMP4ROetECG6PU251jnm84NA8rm0Av/JMmn/c9CFAe0D0D1dGDlHWPsk4mbhGKJ0yU0YliatmPfmgSasismbYzIFf7VPq91ARzRUbavd1fYMBmkMsce0YR/5FdtrpzRhqDzuvwQgQRsoTcttdvp0puFcrtNefMfk8NCbBedIlkzOFxfGiBbe6jde4wqsqEnSrNHwZ2b+Er8z7vjcDPBqYk3gubmMBCrYxg6o1lOS6tTN0kJDUlyKO2AN1ZDr3mpkbhkvZV/N7gLglcClM0X5X7iM= leila@leila-ThinkPad-T14s-Gen-2i" } resource "scaleway_instance_ip" "control-plane-fr-par-1-0" { - tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.k8s.local"] + tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.example.com"] } resource "scaleway_instance_ip" "nodes-fr-par-1-0" { - tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.k8s.local"] + tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.example.com"] +} + +resource "scaleway_instance_private_nic" "control-plane-fr-par-1-0" { + private_network_id = scaleway_vpc_private_network.scw-minimal-example-com.id + server_id = scaleway_instance_server.control-plane-fr-par-1-0.id + tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.example.com", "noprefix=kops.k8s.io/instance-group=control-plane-fr-par-1"] +} + +resource "scaleway_instance_private_nic" "nodes-fr-par-1-0" { + private_network_id = scaleway_vpc_private_network.scw-minimal-example-com.id + server_id = scaleway_instance_server.nodes-fr-par-1-0.id + tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.example.com", "noprefix=kops.k8s.io/instance-group=nodes-fr-par-1"] } resource "scaleway_instance_server" "control-plane-fr-par-1-0" { @@ -192,7 +196,7 @@ resource "scaleway_instance_server" "control-plane-fr-par-1-0" { } name = "control-plane-fr-par-1-0" replace_on_type_change = false - tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.k8s.local", "noprefix=kops.k8s.io/instance-group=control-plane-fr-par-1", "noprefix=kops.k8s.io/role=ControlPlane"] + tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.example.com", "noprefix=kops.k8s.io/instance-group=control-plane-fr-par-1", "noprefix=kops.k8s.io/role=ControlPlane"] type = "DEV1-M" user_data = { "cloud-init" = file("${path.module}/data/scaleway_instance_server_control-plane-fr-par-1-0_user_data") @@ -205,39 +209,39 @@ resource "scaleway_instance_server" "nodes-fr-par-1-0" { ip_id = scaleway_instance_ip.nodes-fr-par-1-0.id name = "nodes-fr-par-1-0" replace_on_type_change = false - tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.k8s.local", "noprefix=kops.k8s.io/instance-group=nodes-fr-par-1"] + tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.example.com", "noprefix=kops.k8s.io/instance-group=nodes-fr-par-1", "noprefix=kops.k8s.io/role=Node"] type = "DEV1-M" user_data = { "cloud-init" = file("${path.module}/data/scaleway_instance_server_nodes-fr-par-1-0_user_data") } } -resource "scaleway_instance_volume" "etcd-1-etcd-events-scw-minimal-k8s-local" { - name = "etcd-1.etcd-events.scw-minimal.k8s.local" +resource "scaleway_instance_volume" "etcd-1-etcd-events-scw-minimal-example-com" { + name = "etcd-1.etcd-events.scw-minimal.example.com" size_in_gb = 20 - tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.k8s.local", "noprefix=kops.k8s.io/etcd=events", "noprefix=kops.k8s.io/role=ControlPlane", "noprefix=kops.k8s.io/instance-group=control-plane-fr-par-1"] + tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.example.com", "noprefix=kops.k8s.io/etcd=events", "noprefix=kops.k8s.io/role=ControlPlane", "noprefix=kops.k8s.io/instance-group=control-plane-fr-par-1"] type = "b_ssd" } -resource "scaleway_instance_volume" "etcd-1-etcd-main-scw-minimal-k8s-local" { - name = "etcd-1.etcd-main.scw-minimal.k8s.local" +resource "scaleway_instance_volume" "etcd-1-etcd-main-scw-minimal-example-com" { + name = "etcd-1.etcd-main.scw-minimal.example.com" size_in_gb = 20 - tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.k8s.local", "noprefix=kops.k8s.io/etcd=main", "noprefix=kops.k8s.io/role=ControlPlane", "noprefix=kops.k8s.io/instance-group=control-plane-fr-par-1"] + tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.example.com", "noprefix=kops.k8s.io/etcd=main", "noprefix=kops.k8s.io/role=ControlPlane", "noprefix=kops.k8s.io/instance-group=control-plane-fr-par-1"] type = "b_ssd" } -resource "scaleway_lb" "api-scw-minimal-k8s-local" { - description = "Load-balancer for kops cluster scw-minimal.k8s.local" - ip_id = scaleway_lb_ip.api-scw-minimal-k8s-local.id - name = "api.scw-minimal.k8s.local" - tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.k8s.local", "noprefix=kops.k8s.io/role=ControlPlane"] +resource "scaleway_lb" "api-scw-minimal-example-com" { + description = "Load-balancer for kops cluster scw-minimal.example.com" + ip_id = scaleway_lb_ip.api-scw-minimal-example-com.id + name = "api.scw-minimal.example.com" + tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.example.com", "noprefix=kops.k8s.io/role=ControlPlane"] type = "LB-S" } resource "scaleway_lb_backend" "lb-backend-https" { forward_port = 443 forward_protocol = "tcp" - lb_id = scaleway_lb.api-scw-minimal-k8s-local.id + lb_id = scaleway_lb.api-scw-minimal-example-com.id name = "lb-backend-https" proxy_protocol = "none" server_ips = [scaleway_instance_server.control-plane-fr-par-1-0.private_ip] @@ -246,7 +250,7 @@ resource "scaleway_lb_backend" "lb-backend-https" { resource "scaleway_lb_backend" "lb-backend-kops-controller" { forward_port = 3988 forward_protocol = "tcp" - lb_id = scaleway_lb.api-scw-minimal-k8s-local.id + lb_id = scaleway_lb.api-scw-minimal-example-com.id name = "lb-backend-kops-controller" proxy_protocol = "none" server_ips = [scaleway_instance_server.control-plane-fr-par-1-0.private_ip] @@ -255,18 +259,48 @@ resource "scaleway_lb_backend" "lb-backend-kops-controller" { resource "scaleway_lb_frontend" "lb-frontend-https" { backend_id = scaleway_lb_backend.lb-backend-https.id inbound_port = 443 - lb_id = scaleway_lb.api-scw-minimal-k8s-local.id + lb_id = scaleway_lb.api-scw-minimal-example-com.id name = "lb-frontend-https" } resource "scaleway_lb_frontend" "lb-frontend-kops-controller" { backend_id = scaleway_lb_backend.lb-backend-kops-controller.id inbound_port = 3988 - lb_id = scaleway_lb.api-scw-minimal-k8s-local.id + lb_id = scaleway_lb.api-scw-minimal-example-com.id name = "lb-frontend-kops-controller" } -resource "scaleway_lb_ip" "api-scw-minimal-k8s-local" { +resource "scaleway_lb_ip" "api-scw-minimal-example-com" { +} + +resource "scaleway_vpc" "scw-minimal-example-com" { + name = "scw-minimal.example.com" + tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.example.com"] +} + +resource "scaleway_vpc_gateway_network" "scw-minimal-example-com" { + enable_dhcp = true + enable_masquerade = true + gateway_id = scaleway_vpc_public_gateway.scw-minimal-example-com.id + ipam_config { + push_default_route = true + } + private_network_id = scaleway_vpc_private_network.scw-minimal-example-com.id +} + +resource "scaleway_vpc_private_network" "scw-minimal-example-com" { + ipv4_subnet { + subnet = "192.168.1.0/24" + } + name = "scw-minimal.example.com" + tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.example.com"] + vpc_id = scaleway_vpc.scw-minimal-example-com.id +} + +resource "scaleway_vpc_public_gateway" "scw-minimal-example-com" { + name = "scw-minimal.example.com" + tags = ["noprefix=kops.k8s.io/cluster=scw-minimal.example.com"] + type = "VPC-GW-S" } terraform { diff --git a/upup/models/cloudup/resources/addons/scaleway-cloud-controller.addons.k8s.io/k8s-1.24.yaml.template b/upup/models/cloudup/resources/addons/scaleway-cloud-controller.addons.k8s.io/k8s-1.24.yaml.template index 4d57c6f8e356b..3217b689a1137 100644 --- a/upup/models/cloudup/resources/addons/scaleway-cloud-controller.addons.k8s.io/k8s-1.24.yaml.template +++ b/upup/models/cloudup/resources/addons/scaleway-cloud-controller.addons.k8s.io/k8s-1.24.yaml.template @@ -15,6 +15,8 @@ stringData: SCW_DEFAULT_REGION: {{ SCW_DEFAULT_REGION }} # Zone is where your servers and volumes will be created, ex: fr-par-1, nl-ams-2 SCW_DEFAULT_ZONE: {{ SCW_DEFAULT_ZONE }} + # The ID of the private network of the cluster + PN_ID: {{ SCW_PN_ID }} --- apiVersion: apps/v1 kind: Deployment diff --git a/upup/pkg/fi/cloudup/apply_cluster.go b/upup/pkg/fi/cloudup/apply_cluster.go index 29d875ed26251..d63e3d044c03d 100644 --- a/upup/pkg/fi/cloudup/apply_cluster.go +++ b/upup/pkg/fi/cloudup/apply_cluster.go @@ -682,6 +682,7 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) error { &scalewaymodel.APILoadBalancerModelBuilder{ScwModelContext: scwModelContext, Lifecycle: networkLifecycle}, &scalewaymodel.DNSModelBuilder{ScwModelContext: scwModelContext, Lifecycle: networkLifecycle}, &scalewaymodel.InstanceModelBuilder{ScwModelContext: scwModelContext, BootstrapScriptBuilder: bootstrapScriptBuilder, Lifecycle: clusterLifecycle}, + &scalewaymodel.NetworkModelBuilder{ScwModelContext: scwModelContext, Lifecycle: networkLifecycle}, &scalewaymodel.SSHKeyModelBuilder{ScwModelContext: scwModelContext, Lifecycle: securityLifecycle}, ) diff --git a/upup/pkg/fi/cloudup/scaleway/cloud.go b/upup/pkg/fi/cloudup/scaleway/cloud.go index 092734df43809..c4f498615da71 100644 --- a/upup/pkg/fi/cloudup/scaleway/cloud.go +++ b/upup/pkg/fi/cloudup/scaleway/cloud.go @@ -27,6 +27,8 @@ import ( ipam "github.com/scaleway/scaleway-sdk-go/api/ipam/v1alpha1" "github.com/scaleway/scaleway-sdk-go/api/lb/v1" "github.com/scaleway/scaleway-sdk-go/api/marketplace/v2" + "github.com/scaleway/scaleway-sdk-go/api/vpc/v2" + "github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1" "github.com/scaleway/scaleway-sdk-go/scw" v1 "k8s.io/api/core/v1" "k8s.io/klog/v2" @@ -60,11 +62,13 @@ type ScwCloud interface { Zone() string DomainService() *domain.API + GatewayService() *vpcgw.API IamService() *iam.API InstanceService() *instance.API IPAMService() *ipam.API LBService() *lb.ZonedAPI MarketplaceService() *marketplace.API + VPCService() *vpc.API DeleteGroup(group *cloudinstances.CloudInstanceGroup) error DeleteInstance(i *cloudinstances.CloudInstance) error @@ -76,17 +80,26 @@ type ScwCloud interface { GetCloudGroups(cluster *kops.Cluster, instancegroups []*kops.InstanceGroup, warnUnmatched bool, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error) GetClusterDNSRecords(clusterName string) ([]*domain.Record, error) + GetClusterGatewayNetworks(clusterName string) ([]*vpcgw.GatewayNetwork, error) + GetClusterGateways(clusterName string) ([]*vpcgw.Gateway, error) GetClusterLoadBalancers(clusterName string) ([]*lb.LB, error) + GetClusterPrivateNetworks(clusterName string) ([]*vpc.PrivateNetwork, error) GetClusterServers(clusterName string, instanceGroupName *string) ([]*instance.Server, error) GetClusterSSHKeys(clusterName string) ([]*iam.SSHKey, error) GetClusterVolumes(clusterName string) ([]*instance.Volume, error) - GetServerIP(serverID string, zone scw.Zone) (string, error) + GetClusterVPCs(clusterName string) ([]*vpc.VPC, error) + GetServerPublicIP(serverID string, zone scw.Zone) (string, error) + GetServerPrivateIP(serverID string, zone scw.Zone) (string, error) DeleteDNSRecord(record *domain.Record, clusterName string) error + DeleteGateway(gateway *vpcgw.Gateway) error + DeleteGatewayNetwork(gatewayNetwork *vpcgw.GatewayNetwork) error DeleteLoadBalancer(loadBalancer *lb.LB) error + DeletePrivateNetwork(privateNetwork *vpc.PrivateNetwork) error DeleteServer(server *instance.Server) error DeleteSSHKey(sshkey *iam.SSHKey) error DeleteVolume(volume *instance.Volume) error + DeleteVPC(vpc *vpc.VPC) error } // static compile time check to validate ScwCloud's fi.Cloud Interface. @@ -101,11 +114,13 @@ type scwCloudImplementation struct { tags map[string]string domainAPI *domain.API + gatewayAPI *vpcgw.API iamAPI *iam.API instanceAPI *instance.API ipamAPI *ipam.API lbAPI *lb.ZonedAPI marketplaceAPI *marketplace.API + vpcAPI *vpc.API } // NewScwCloud returns a Cloud with a Scaleway Client using the env vars SCW_PROFILE or @@ -156,11 +171,13 @@ func NewScwCloud(tags map[string]string) (ScwCloud, error) { dns: dns.NewProvider(domain.NewAPI(scwClient)), tags: tags, domainAPI: domain.NewAPI(scwClient), + gatewayAPI: vpcgw.NewAPI(scwClient), iamAPI: iam.NewAPI(scwClient), instanceAPI: instance.NewAPI(scwClient), ipamAPI: ipam.NewAPI(scwClient), lbAPI: lb.NewZonedAPI(scwClient), marketplaceAPI: marketplace.NewAPI(scwClient), + vpcAPI: vpc.NewAPI(scwClient), }, nil } @@ -198,6 +215,10 @@ func (s *scwCloudImplementation) DomainService() *domain.API { return s.domainAPI } +func (s *scwCloudImplementation) GatewayService() *vpcgw.API { + return s.gatewayAPI +} + func (s *scwCloudImplementation) IamService() *iam.API { return s.iamAPI } @@ -218,6 +239,10 @@ func (s *scwCloudImplementation) MarketplaceService() *marketplace.API { return s.marketplaceAPI } +func (s *scwCloudImplementation) VPCService() *vpc.API { + return s.vpcAPI +} + func (s *scwCloudImplementation) DeleteGroup(group *cloudinstances.CloudInstanceGroup) error { toDelete := append(group.NeedUpdate, group.Ready...) for _, cloudInstance := range toDelete { @@ -258,7 +283,7 @@ func (s *scwCloudImplementation) DeregisterInstance(i *cloudinstances.CloudInsta if err != nil { return fmt.Errorf("deregistering cloud instance %s of group %q: %w", i.ID, i.CloudInstanceGroup.HumanName, err) } - serverIP, err := s.GetServerIP(server.Server.ID, server.Server.Zone) + serverIP, err := s.GetServerPrivateIP(server.Server.ID, server.Server.Zone) if err != nil { return fmt.Errorf("deregistering cloud instance %s of group %q: %w", i.ID, i.CloudInstanceGroup.HumanName, err) } @@ -407,11 +432,15 @@ func buildCloudGroup(s *scwCloudImplementation, ig *kops.InstanceGroup, sg []*in cloudInstance.State = cloudinstances.State(server.State) cloudInstance.MachineType = server.CommercialType cloudInstance.Roles = append(cloudInstance.Roles, InstanceRoleFromTags(server.Tags)) - ip, err := s.GetServerIP(server.ID, server.Zone) + + cloudInstance.ExternalIP, err = s.GetServerPublicIP(server.ID, server.Zone) + if err != nil { + return nil, fmt.Errorf("getting server public IP: %w", err) + } + cloudInstance.PrivateIP, err = s.GetServerPrivateIP(server.ID, server.Zone) if err != nil { - return nil, fmt.Errorf("getting server IP: %w", err) + return nil, fmt.Errorf("getting server private IP: %w", err) } - cloudInstance.PrivateIP = ip } return cloudInstanceGroup, nil @@ -438,6 +467,28 @@ func (s *scwCloudImplementation) GetClusterDNSRecords(clusterName string) ([]*do return clusterDNSRecords, nil } +func (s *scwCloudImplementation) GetClusterGatewayNetworks(privateNetworkID string) ([]*vpcgw.GatewayNetwork, error) { + gwNetworks, err := s.gatewayAPI.ListGatewayNetworks(&vpcgw.ListGatewayNetworksRequest{ + Zone: s.zone, + PrivateNetworkID: scw.StringPtr(privateNetworkID), + }, scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("failed to list gateway networks: %w", err) + } + return gwNetworks.GatewayNetworks, nil +} + +func (s *scwCloudImplementation) GetClusterGateways(clusterName string) ([]*vpcgw.Gateway, error) { + gws, err := s.gatewayAPI.ListGateways(&vpcgw.ListGatewaysRequest{ + Zone: s.zone, + Tags: []string{TagClusterName + "=" + clusterName}, + }, scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("failed to list gateways: %w", err) + } + return gws.Gateways, nil +} + func (s *scwCloudImplementation) GetClusterLoadBalancers(clusterName string) ([]*lb.LB, error) { loadBalancerName := "api." + clusterName lbs, err := s.lbAPI.ListLBs(&lb.ZonedAPIListLBsRequest{ @@ -450,6 +501,17 @@ func (s *scwCloudImplementation) GetClusterLoadBalancers(clusterName string) ([] return lbs.LBs, nil } +func (s *scwCloudImplementation) GetClusterPrivateNetworks(clusterName string) ([]*vpc.PrivateNetwork, error) { + pns, err := s.vpcAPI.ListPrivateNetworks(&vpc.ListPrivateNetworksRequest{ + Region: s.region, + Tags: []string{TagClusterName + "=" + clusterName}, + }, scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("failed to list cluster private networks: %w", err) + } + return pns.PrivateNetworks, nil +} + func (s *scwCloudImplementation) GetClusterServers(clusterName string, instanceGroupName *string) ([]*instance.Server, error) { tags := []string{TagClusterName + "=" + clusterName} if instanceGroupName != nil { @@ -473,14 +535,14 @@ func (s *scwCloudImplementation) GetClusterServers(clusterName string, instanceG func (s *scwCloudImplementation) GetClusterSSHKeys(clusterName string) ([]*iam.SSHKey, error) { clusterSSHKeys := []*iam.SSHKey(nil) allSSHKeys, err := s.iamAPI.ListSSHKeys(&iam.ListSSHKeysRequest{}, scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("failed to list cluster SSH keys: %w", err) + } for _, sshkey := range allSSHKeys.SSHKeys { if strings.HasPrefix(sshkey.Name, fmt.Sprintf("kubernetes.%s-", clusterName)) { clusterSSHKeys = append(clusterSSHKeys, sshkey) } } - if err != nil { - return nil, fmt.Errorf("failed to list cluster ssh keys: %w", err) - } return clusterSSHKeys, nil } @@ -495,30 +557,23 @@ func (s *scwCloudImplementation) GetClusterVolumes(clusterName string) ([]*insta return volumes.Volumes, nil } -func (s *scwCloudImplementation) GetServerIP(serverID string, zone scw.Zone) (string, error) { - region, err := zone.Region() - if err != nil { - return "", fmt.Errorf("converting zone %s to region: %w", zone, err) - } - - ips, err := s.ipamAPI.ListIPs(&ipam.ListIPsRequest{ - Region: region, - IsIPv6: fi.PtrTo(false), - ResourceID: &serverID, - Zonal: fi.PtrTo(zone.String()), +func (s *scwCloudImplementation) GetClusterVPCs(clusterName string) ([]*vpc.VPC, error) { + vpcs, err := s.vpcAPI.ListVPCs(&vpc.ListVPCsRequest{ + Region: s.region, + Tags: []string{TagClusterName + "=" + clusterName}, }, scw.WithAllPages()) if err != nil { - return "", fmt.Errorf("listing IPs for server %s: %w", serverID, err) + return nil, fmt.Errorf("failed to list cluster VPCs: %w", err) } + return vpcs.Vpcs, nil +} - if len(ips.IPs) < 1 { - return "", fmt.Errorf("could not find IP for server %s", serverID) - } - if len(ips.IPs) > 1 { - klog.V(10).Infof("Found more than 1 IP for server %s, using %s", serverID, ips.IPs[0].Address.IP.String()) - } +func (s *scwCloudImplementation) GetServerPublicIP(serverID string, zone scw.Zone) (string, error) { + return GetPublicIP(s.client, serverID, zone) +} - return ips.IPs[0].Address.IP.String(), nil +func (s *scwCloudImplementation) GetServerPrivateIP(serverID string, zone scw.Zone) (string, error) { + return GetPrivateIP(s.client, serverID, zone) } func (s *scwCloudImplementation) DeleteDNSRecord(record *domain.Record, clusterName string) error { @@ -544,6 +599,78 @@ func (s *scwCloudImplementation) DeleteDNSRecord(record *domain.Record, clusterN return nil } +func (s *scwCloudImplementation) DeleteGateway(gateway *vpcgw.Gateway) error { + // We detach the IP of the gateway + _, err := s.gatewayAPI.WaitForGateway(&vpcgw.WaitForGatewayRequest{ + GatewayID: gateway.ID, + Zone: s.zone, + }) + if err != nil { + if is404Error(err) { + klog.V(8).Infof("Gateway %q (%s) was already deleted", gateway.Name, gateway.ID) + return nil + } + return fmt.Errorf("waiting for gateway: %w", err) + } + + _, err = s.gatewayAPI.UpdateIP(&vpcgw.UpdateIPRequest{ + Zone: s.zone, + IPID: gateway.IP.ID, + GatewayID: scw.StringPtr(""), + }) + if err != nil { + return fmt.Errorf("failed to detach gateway IP: %w", err) + } + + // We delete the IP of the gateway + _, err = s.gatewayAPI.WaitForGateway(&vpcgw.WaitForGatewayRequest{ + GatewayID: gateway.ID, + Zone: s.zone, + }) + if err != nil { + return fmt.Errorf("waiting for gateway: %w", err) + } + + err = s.gatewayAPI.DeleteIP(&vpcgw.DeleteIPRequest{ + Zone: s.zone, + IPID: gateway.IP.ID, + }) + if err != nil { + return fmt.Errorf("failed to delete gateway IP: %w", err) + } + + // We delete the gateway once it's in a stable state + _, err = s.gatewayAPI.WaitForGateway(&vpcgw.WaitForGatewayRequest{ + GatewayID: gateway.ID, + Zone: s.zone, + }) + if err != nil { + return fmt.Errorf("waiting for gateway: %w", err) + } + err = s.gatewayAPI.DeleteGateway(&vpcgw.DeleteGatewayRequest{ + Zone: s.zone, + GatewayID: gateway.ID, + CleanupDHCP: true, + }) + if err != nil { + return fmt.Errorf("failed to delete gateway %s: %w", gateway.ID, err) + } + + return nil +} + +func (s *scwCloudImplementation) DeleteGatewayNetwork(gatewayNetwork *vpcgw.GatewayNetwork) error { + err := s.gatewayAPI.DeleteGatewayNetwork(&vpcgw.DeleteGatewayNetworkRequest{ + Zone: s.zone, + GatewayNetworkID: gatewayNetwork.ID, + CleanupDHCP: true, + }) + if err != nil { + return fmt.Errorf("failed to delete gateway network %s from private network: %w", gatewayNetwork.ID, err) + } + return nil +} + func (s *scwCloudImplementation) DeleteLoadBalancer(loadBalancer *lb.LB) error { ipsToRelease := loadBalancer.IP @@ -587,6 +714,21 @@ func (s *scwCloudImplementation) DeleteLoadBalancer(loadBalancer *lb.LB) error { return nil } +func (s *scwCloudImplementation) DeletePrivateNetwork(privateNetwork *vpc.PrivateNetwork) error { + err := s.vpcAPI.DeletePrivateNetwork(&vpc.DeletePrivateNetworkRequest{ + PrivateNetworkID: privateNetwork.ID, + Region: s.region, + }) + if err != nil { + if is404Error(err) { + klog.V(8).Infof("Private network %q (%s) was already deleted", privateNetwork.Name, privateNetwork.ID) + return nil + } + return fmt.Errorf("failed to delete private network %s: %w", privateNetwork.ID, err) + } + return nil +} + func (s *scwCloudImplementation) DeleteServer(server *instance.Server) error { srv, err := s.instanceAPI.GetServer(&instance.GetServerRequest{ Zone: s.zone, @@ -600,6 +742,18 @@ func (s *scwCloudImplementation) DeleteServer(server *instance.Server) error { return err } + // We detach the private network + if len(srv.Server.PrivateNics) > 0 { + err = s.instanceAPI.DeletePrivateNIC(&instance.DeletePrivateNICRequest{ + Zone: s.zone, + ServerID: server.ID, + PrivateNicID: srv.Server.PrivateNics[0].ID, + }) + if err != nil { + return fmt.Errorf("delete server %s: detaching private network: %w", server.ID, err) + } + } + // We detach the etcd volumes for _, volume := range srv.Server.Volumes { volumeResponse, err := s.instanceAPI.GetVolume(&instance.GetVolumeRequest{ @@ -680,3 +834,18 @@ func (s *scwCloudImplementation) DeleteVolume(volume *instance.Volume) error { return nil } + +func (s *scwCloudImplementation) DeleteVPC(v *vpc.VPC) error { + err := s.vpcAPI.DeleteVPC(&vpc.DeleteVPCRequest{ + Region: s.region, + VpcID: v.ID, + }) + if err != nil { + if is404Error(err) { + klog.V(8).Infof("VPC %q (%s) was already deleted", v.Name, v.ID) + return nil + } + return fmt.Errorf("failed to delete VPC %s: %w", v.ID, err) + } + return nil +} diff --git a/upup/pkg/fi/cloudup/scaleway/utils.go b/upup/pkg/fi/cloudup/scaleway/utils.go index 5390164f5ff6a..b1cef33acd056 100644 --- a/upup/pkg/fi/cloudup/scaleway/utils.go +++ b/upup/pkg/fi/cloudup/scaleway/utils.go @@ -23,8 +23,12 @@ import ( "os" "strings" + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + ipam "github.com/scaleway/scaleway-sdk-go/api/ipam/v1alpha1" "github.com/scaleway/scaleway-sdk-go/scw" k8serrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/klog/v2" + kopsv "k8s.io/kops" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi" ) @@ -170,3 +174,143 @@ func CreateValidScalewayProfile() (*scw.Profile, error) { return &profile, nil } + +func GetPublicIP(scwClient *scw.Client, serverID string, zone scw.Zone) (string, error) { + if scwClient == nil { + profile, err := CreateValidScalewayProfile() + if err != nil { + return "", err + } + scwClient, err = scw.NewClient( + scw.WithProfile(profile), + scw.WithUserAgent(KopsUserAgentPrefix+kopsv.Version), + ) + if err != nil { + return "", fmt.Errorf("creating client for Scaleway IPAM checking: %w", err) + } + } + + ipamAPI := ipam.NewAPI(scwClient) + + region, err := zone.Region() + if err != nil { + return "", fmt.Errorf("converting zone %s to region: %w", zone, err) + } + + ips, err := ipamAPI.ListIPs(&ipam.ListIPsRequest{ + Region: region, + IsIPv6: fi.PtrTo(false), + ResourceID: &serverID, + Zonal: fi.PtrTo(zone.String()), // not useful according to tests made on this route with the CLI + }, scw.WithAllPages()) + if err != nil { + return "", fmt.Errorf("listing IPs for server %s: %w", serverID, err) + } + + if len(ips.IPs) < 1 { + return "", fmt.Errorf("could not find IP for server %s", serverID) + } + if len(ips.IPs) > 1 { + klog.V(10).Infof("Found more than 1 IP for server %s, using %s", serverID, ips.IPs[0].Address.IP.String()) + } + + return ips.IPs[0].Address.IP.String(), nil +} + +func GetPrivateIP(scwClient *scw.Client, serverID string, zone scw.Zone) (string, error) { + if scwClient == nil { + profile, err := CreateValidScalewayProfile() + if err != nil { + return "", err + } + scwClient, err = scw.NewClient( + scw.WithProfile(profile), + scw.WithUserAgent(KopsUserAgentPrefix+kopsv.Version), + ) + if err != nil { + return "", fmt.Errorf("creating client for Scaleway IPAM checking: %w", err) + } + } + + instanceAPI := instance.NewAPI(scwClient) + ipamAPI := ipam.NewAPI(scwClient) + + region, err := zone.Region() + if err != nil { + return "", fmt.Errorf("converting zone %s to region: %w", zone, err) + } + + privateNICs, err := instanceAPI.ListPrivateNICs(&instance.ListPrivateNICsRequest{ + Zone: zone, + ServerID: serverID, + }, scw.WithAllPages()) + if err != nil { + return "", err + } + + var privateIPs []string + for _, privateNIC := range privateNICs.PrivateNics { + //resourceType := "instance_server" + ips, err := ipamAPI.ListIPs(&ipam.ListIPsRequest{ + Region: region, + PrivateNetworkID: fi.PtrTo(privateNIC.PrivateNetworkID), + ResourceID: fi.PtrTo(privateNIC.ID), + IsIPv6: fi.PtrTo(false), + //ResourceName: fi.PtrTo(serverName), + //Zonal: nil, + //ZonalNat: nil, + //Regional: fi.PtrTo(false), + //SubnetID: nil, + //Attached: nil, + //ResourceType: ipam.ResourceType(resourceType), + //MacAddress: nil, + //Tags: nil, + //ResourceIDs: nil, + }, scw.WithAllPages()) + if err != nil { + return "", fmt.Errorf("listing IPs for server %s: %w", serverID, err) + } + for _, ip := range ips.IPs { + privateIPs = append(privateIPs, ip.Address.IP.String()) + } + } + + if len(privateIPs) < 1 { + return "", fmt.Errorf("could not find IP for server %s", serverID) + } + + if len(privateIPs) > 1 { + klog.Infof("Found more than 1 IP for server %s, using %s", serverID, privateIPs[0]) + } + return privateIPs[0], nil +} + +func GetControlPlanesIPs(scwCloud ScwCloud, clusterName string, getInternalIPs bool) ([]string, error) { + var controlPlanePrivateIPs []string + + servers, err := scwCloud.GetClusterServers(clusterName, nil) + if err != nil { + return nil, fmt.Errorf("getting cluster servers for load-balancer's back-end: %w", err) + } + + for _, server := range servers { + if role := InstanceRoleFromTags(server.Tags); role == TagRoleWorker { + continue + } + if getInternalIPs { + ip, err := scwCloud.GetServerPrivateIP(server.ID, server.Zone) + if err != nil { + return nil, fmt.Errorf("getting IP of server %s for load-balancer's back-end: %w", server.Name, err) + } + controlPlanePrivateIPs = append(controlPlanePrivateIPs, ip) + } else { + ip, err := scwCloud.GetServerPublicIP(server.ID, server.Zone) + if err != nil { + return nil, fmt.Errorf("getting IP of server %s for load-balancer's back-end: %w", server.Name, err) + } + controlPlanePrivateIPs = append(controlPlanePrivateIPs, ip) + } + } + + return controlPlanePrivateIPs, nil +} diff --git a/upup/pkg/fi/cloudup/scaleway/verifier.go b/upup/pkg/fi/cloudup/scaleway/verifier.go index 0045830bb4fdb..430afafa1c922 100644 --- a/upup/pkg/fi/cloudup/scaleway/verifier.go +++ b/upup/pkg/fi/cloudup/scaleway/verifier.go @@ -25,12 +25,10 @@ import ( "strings" "github.com/scaleway/scaleway-sdk-go/api/instance/v1" - ipam "github.com/scaleway/scaleway-sdk-go/api/ipam/v1alpha1" "github.com/scaleway/scaleway-sdk-go/scw" kopsv "k8s.io/kops" "k8s.io/kops/pkg/bootstrap" "k8s.io/kops/pkg/wellknownports" - "k8s.io/kops/upup/pkg/fi" ) type ScalewayVerifierOptions struct{} @@ -41,7 +39,7 @@ type scalewayVerifier struct { var _ bootstrap.Verifier = &scalewayVerifier{} -func NewScalewayVerifier(ctx context.Context, opt *ScalewayVerifierOptions) (bootstrap.Verifier, error) { +func NewScalewayVerifier(_ context.Context, _ *ScalewayVerifierOptions) (bootstrap.Verifier, error) { profile, err := CreateValidScalewayProfile() if err != nil { return nil, fmt.Errorf("creating client for Scaleway Verifier: %w", err) @@ -58,7 +56,7 @@ func NewScalewayVerifier(ctx context.Context, opt *ScalewayVerifierOptions) (boo }, nil } -func (v scalewayVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request, token string, body []byte) (*bootstrap.VerifyResult, error) { +func (v scalewayVerifier) VerifyToken(ctx context.Context, _ *http.Request, token string, _ []byte) (*bootstrap.VerifyResult, error) { if !strings.HasPrefix(token, ScalewayAuthenticationTokenPrefix) { return nil, bootstrap.ErrNotThisVerifier } @@ -73,10 +71,6 @@ func (v scalewayVerifier) VerifyToken(ctx context.Context, rawRequest *http.Requ if err != nil { return nil, fmt.Errorf("unable to parse Scaleway zone %q: %w", metadata.Location.ZoneID, err) } - region, err := zone.Region() - if err != nil { - return nil, fmt.Errorf("unable to determine region from zone %s", zone) - } profile, err := CreateValidScalewayProfile() if err != nil { @@ -99,31 +93,16 @@ func (v scalewayVerifier) VerifyToken(ctx context.Context, rawRequest *http.Requ } server := serverResponse.Server - ips, err := ipam.NewAPI(scwClient).ListIPs(&ipam.ListIPsRequest{ - Region: region, - ResourceID: fi.PtrTo(server.ID), - IsIPv6: fi.PtrTo(false), - Zonal: fi.PtrTo(zone.String()), - }, scw.WithContext(ctx), scw.WithAllPages()) + privateIP, err := GetPrivateIP(scwClient, serverID, zone) if err != nil { - return nil, fmt.Errorf("failed to get IP for server %q: %w", server.Name, err) - } - if ips.TotalCount == 0 { - return nil, fmt.Errorf("no IP found for server %q: %w", server.Name, err) - } - - addresses := []string(nil) - challengeEndPoints := []string(nil) - for _, ip := range ips.IPs { - addresses = append(addresses, ip.Address.IP.String()) - challengeEndPoints = append(challengeEndPoints, net.JoinHostPort(ip.Address.IP.String(), strconv.Itoa(wellknownports.NodeupChallenge))) + return nil, fmt.Errorf("failed to get private IP for server %s: %w", serverID, err) } result := &bootstrap.VerifyResult{ NodeName: server.Name, InstanceGroupName: InstanceGroupNameFromTags(server.Tags), - CertificateNames: addresses, - ChallengeEndpoint: challengeEndPoints[0], + CertificateNames: []string{privateIP}, + ChallengeEndpoint: net.JoinHostPort(privateIP, strconv.Itoa(wellknownports.NodeupChallenge)), } return result, nil diff --git a/upup/pkg/fi/cloudup/scalewaytasks/dns_record.go b/upup/pkg/fi/cloudup/scalewaytasks/dns_record.go index 3a4543f9580a0..418888764c02f 100644 --- a/upup/pkg/fi/cloudup/scalewaytasks/dns_record.go +++ b/upup/pkg/fi/cloudup/scalewaytasks/dns_record.go @@ -27,15 +27,19 @@ import ( "k8s.io/kops/upup/pkg/fi/cloudup/terraformWriter" ) +const PlaceholderIP = "203.0.113.123" + // +kops:fitask type DNSRecord struct { - ID *string - Name *string - Data *string - DNSZone *string - Type *string - TTL *uint32 - Lifecycle fi.Lifecycle + ID *string + Name *string + Data *string + DNSZone *string + Type *string + TTL *uint32 + IsInternal *bool + ClusterName *string + Lifecycle fi.Lifecycle } var _ fi.CloudupTask = &DNSRecord{} @@ -45,6 +49,21 @@ func (d *DNSRecord) CompareWithID() *string { return d.ID } +var _ fi.CloudupHasDependencies = &Instance{} + +func (d *DNSRecord) GetDependencies(tasks map[string]fi.CloudupTask) []fi.CloudupTask { + var deps []fi.CloudupTask + for _, task := range tasks { + if _, ok := task.(*Instance); ok { + deps = append(deps, task) + } + if _, ok := task.(*PrivateNetwork); ok { + deps = append(deps, task) + } + } + return deps +} + func (d *DNSRecord) Find(context *fi.CloudupContext) (*DNSRecord, error) { cloud := context.T.Cloud.(scaleway.ScwCloud) records, err := cloud.DomainService().ListDNSZoneRecords(&domain.ListDNSZoneRecordsRequest{ @@ -66,13 +85,15 @@ func (d *DNSRecord) Find(context *fi.CloudupContext) (*DNSRecord, error) { recordFound := records.Records[0] return &DNSRecord{ - ID: fi.PtrTo(recordFound.ID), - Name: fi.PtrTo(recordFound.Name), - Data: fi.PtrTo(recordFound.Data), - TTL: fi.PtrTo(recordFound.TTL), - DNSZone: d.DNSZone, - Type: fi.PtrTo(recordFound.Type.String()), - Lifecycle: d.Lifecycle, + ID: fi.PtrTo(recordFound.ID), + Name: fi.PtrTo(recordFound.Name), + Data: fi.PtrTo(recordFound.Data), + TTL: fi.PtrTo(recordFound.TTL), + DNSZone: d.DNSZone, + Type: fi.PtrTo(recordFound.Type.String()), + IsInternal: d.IsInternal, + ClusterName: d.ClusterName, + Lifecycle: d.Lifecycle, }, nil } @@ -114,6 +135,14 @@ func (_ *DNSRecord) CheckChanges(actual, expected, changes *DNSRecord) error { func (d *DNSRecord) RenderScw(t *scaleway.ScwAPITarget, actual, expected, changes *DNSRecord) error { cloud := t.Cloud.(scaleway.ScwCloud) + if *expected.Data == PlaceholderIP { + controlPlanesIPs, err := scaleway.GetControlPlanesIPs(cloud, *expected.ClusterName, *expected.IsInternal) + if err != nil || len(controlPlanesIPs) == 0 { + return fmt.Errorf("error getting control plane IPs: %v", err) + } + expected.Data = &controlPlanesIPs[0] + } + if actual != nil { recordUpdated, err := cloud.DomainService().UpdateDNSZoneRecords(&domain.UpdateDNSZoneRecordsRequest{ DNSZone: fi.ValueOf(actual.DNSZone), diff --git a/upup/pkg/fi/cloudup/scalewaytasks/gateway.go b/upup/pkg/fi/cloudup/scalewaytasks/gateway.go new file mode 100644 index 0000000000000..9137532af2a9c --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/gateway.go @@ -0,0 +1,200 @@ +/* +Copyright 2024 The Kubernetes 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 scalewaytasks + +import ( + "fmt" + "strings" + + "github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1" + "github.com/scaleway/scaleway-sdk-go/scw" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" + "k8s.io/kops/upup/pkg/fi/cloudup/terraform" + "k8s.io/kops/upup/pkg/fi/cloudup/terraformWriter" +) + +const GatewayDefaultType = "VPC-GW-S" + +// +kops:fitask +type Gateway struct { + ID *string + Name *string + Zone *string + Tags []string + + Lifecycle fi.Lifecycle + //PrivateNetwork *PrivateNetwork +} + +//func (g *Gateway) IsForAPIServer() bool { +// return true +//} +// +//func (g *Gateway) FindAddresses(context *fi.CloudupContext) ([]string, error) { +// if g.ID == nil { +// return nil, nil +// } +// +// cloud := context.T.Cloud.(scaleway.ScwCloud) +// //gwFound, err := cloud.GetClusterGateways(scaleway.ClusterNameFromTags(g.Tags)) +// //if err != nil { +// // return nil, err +// //} +// +// var gatewayAddresses []string +// region, err := scw.Zone(fi.ValueOf(g.Zone)).Region() +// if err != nil { +// return nil, fmt.Errorf("finding public gateway's region: %w", err) +// } +// +// //for _, gw := range gwFound { +// ips, err := cloud.IPAMService().ListIPs(&ipam.ListIPsRequest{ +// Region: region, +// //Zonal: g.Zone, +// //ResourceID: &gw.ID, +// PrivateNetworkID: g.PrivateNetwork.ID, +// ResourceName: g.Name, +// ResourceType: ipam.ResourceTypeVpcGateway, +// }, scw.WithContext(context.Context()), scw.WithAllPages()) +// if err != nil { +// return nil, fmt.Errorf("listing public gateway's IPs: %w", err) +// } +// for _, ip := range ips.IPs { +// gatewayAddresses = append(gatewayAddresses, ip.Address.IP.String()) +// } +// //} +// return gatewayAddresses, nil +//} + +var _ fi.CloudupTask = &Gateway{} +var _ fi.CompareWithID = &Gateway{} + +//var _ fi.HasAddress = &Gateway{} + +func (g *Gateway) CompareWithID() *string { + return g.ID +} + +func (g *Gateway) Find(context *fi.CloudupContext) (*Gateway, error) { + cloud := context.T.Cloud.(scaleway.ScwCloud) + gateways, err := cloud.GatewayService().ListGateways(&vpcgw.ListGatewaysRequest{ + Zone: scw.Zone(cloud.Zone()), + Name: g.Name, + Tags: []string{fmt.Sprintf("%s=%s", scaleway.TagClusterName, scaleway.ClusterNameFromTags(g.Tags))}, + }, scw.WithContext(context.Context()), scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("listing gateways: %w", err) + } + + if gateways.TotalCount == 0 { + return nil, nil + } + if gateways.TotalCount > 1 { + return nil, fmt.Errorf("expected exactly 1 gateway, got %d", gateways.TotalCount) + } + gatewayFound := gateways.Gateways[0] + + return &Gateway{ + ID: fi.PtrTo(gatewayFound.ID), + Name: fi.PtrTo(gatewayFound.Name), + Zone: fi.PtrTo(gatewayFound.Zone.String()), + Tags: gatewayFound.Tags, + Lifecycle: g.Lifecycle, + }, nil +} + +func (g *Gateway) Run(context *fi.CloudupContext) error { + return fi.CloudupDefaultDeltaRunMethod(g, context) +} + +func (_ *Gateway) CheckChanges(actual, expected, changes *Gateway) error { + if actual != nil { + if changes.Name != nil { + return fi.CannotChangeField("Name") + } + if changes.ID != nil { + return fi.CannotChangeField("ID") + } + if changes.Zone != nil { + return fi.CannotChangeField("Zone") + } + } else { + if expected.Name == nil { + return fi.RequiredField("Name") + } + if expected.Zone == nil { + return fi.RequiredField("Zone") + } + } + return nil +} + +func (_ *Gateway) RenderScw(t *scaleway.ScwAPITarget, actual, expected, changes *Gateway) error { + if actual != nil { + //TODO(Mia-Cross): update tags + return nil + } + + cloud := t.Cloud.(scaleway.ScwCloud) + zone := scw.Zone(fi.ValueOf(expected.Zone)) + + gatewayCreated, err := cloud.GatewayService().CreateGateway(&vpcgw.CreateGatewayRequest{ + Zone: zone, + Name: fi.ValueOf(expected.Name), + Tags: expected.Tags, + Type: GatewayDefaultType, + EnableBastion: true, + BastionPort: scw.Uint32Ptr(1042), + }) + if err != nil { + return fmt.Errorf("creating gateway: %w", err) + } + + _, err = cloud.GatewayService().WaitForGateway(&vpcgw.WaitForGatewayRequest{ + GatewayID: gatewayCreated.ID, + Zone: zone, + }) + if err != nil { + return fmt.Errorf("waiting for gateway: %w", err) + } + + expected.ID = &gatewayCreated.ID + + return nil +} + +type terraformGateway struct { + Type string `cty:"type"` + Name *string `cty:"name"` + Tags []string `cty:"tags"` +} + +func (_ *Gateway) RenderTerraform(t *terraform.TerraformTarget, actual, expected, changes *Gateway) error { + tfName := strings.ReplaceAll(fi.ValueOf(expected.Name), ".", "-") + + tfGW := terraformGateway{ + Type: GatewayDefaultType, + Name: expected.Name, + Tags: expected.Tags, + } + return t.RenderResource("scaleway_vpc_public_gateway", tfName, tfGW) +} + +func (g *Gateway) TerraformLink() *terraformWriter.Literal { + return terraformWriter.LiteralProperty("scaleway_vpc_public_gateway", fi.ValueOf(g.Name), "id") +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/gateway_fitask.go b/upup/pkg/fi/cloudup/scalewaytasks/gateway_fitask.go new file mode 100644 index 0000000000000..0d3a7da233806 --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/gateway_fitask.go @@ -0,0 +1,52 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes 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. +*/ + +// Code generated by fitask. DO NOT EDIT. + +package scalewaytasks + +import ( + "k8s.io/kops/upup/pkg/fi" +) + +// Gateway + +var _ fi.HasLifecycle = &Gateway{} + +// GetLifecycle returns the Lifecycle of the object, implementing fi.HasLifecycle +func (o *Gateway) GetLifecycle() fi.Lifecycle { + return o.Lifecycle +} + +// SetLifecycle sets the Lifecycle of the object, implementing fi.SetLifecycle +func (o *Gateway) SetLifecycle(lifecycle fi.Lifecycle) { + o.Lifecycle = lifecycle +} + +var _ fi.HasName = &Gateway{} + +// GetName returns the Name of the object, implementing fi.HasName +func (o *Gateway) GetName() *string { + return o.Name +} + +// String is the stringer function for the task, producing readable output using fi.TaskAsString +func (o *Gateway) String() string { + return fi.CloudupTaskAsString(o) +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/gateway_network.go b/upup/pkg/fi/cloudup/scalewaytasks/gateway_network.go new file mode 100644 index 0000000000000..d2ca140227c83 --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/gateway_network.go @@ -0,0 +1,185 @@ +/* +Copyright 2024 The Kubernetes 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 scalewaytasks + +import ( + "fmt" + "strings" + + "github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1" + "github.com/scaleway/scaleway-sdk-go/scw" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" + "k8s.io/kops/upup/pkg/fi/cloudup/terraform" + "k8s.io/kops/upup/pkg/fi/cloudup/terraformWriter" +) + +// +kops:fitask +type GatewayNetwork struct { + ID *string + Name *string + Zone *string + + //Address *string + //IsForAPIServer bool + + Lifecycle fi.Lifecycle + Gateway *Gateway + PrivateNetwork *PrivateNetwork +} + +//func (g *GatewayNetwork) IsForAPIServer() bool { +// return g. +//} + +//func (g *GatewayNetwork) FindAddresses(context *fi.CloudupContext) ([]string, error) { +// //TODO implement me +// panic("implement me") +//} + +var _ fi.CloudupTask = &GatewayNetwork{} +var _ fi.CompareWithID = &GatewayNetwork{} +var _ fi.CloudupHasDependencies = &GatewayNetwork{} + +//var _ fi.HasAddress = &GatewayNetwork{} + +func (g *GatewayNetwork) CompareWithID() *string { + return g.ID +} + +func (g *GatewayNetwork) GetDependencies(tasks map[string]fi.CloudupTask) []fi.CloudupTask { + var deps []fi.CloudupTask + for _, task := range tasks { + if _, ok := task.(*PrivateNetwork); ok { + deps = append(deps, task) + } + if _, ok := task.(*Gateway); ok { + deps = append(deps, task) + } + } + return deps +} + +func (g *GatewayNetwork) Find(context *fi.CloudupContext) (*GatewayNetwork, error) { + cloud := context.T.Cloud.(scaleway.ScwCloud) + gwns, err := cloud.GatewayService().ListGatewayNetworks(&vpcgw.ListGatewayNetworksRequest{ + Zone: scw.Zone(cloud.Zone()), + GatewayID: g.Gateway.ID, + PrivateNetworkID: g.PrivateNetwork.ID, + }, scw.WithContext(context.Context()), scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("listing gateway networks: %w", err) + } + + if gwns.TotalCount == 0 { + return nil, nil + } + if gwns.TotalCount > 1 { + return nil, fmt.Errorf("expected exactly 1 gateway network, got %d", gwns.TotalCount) + } + gwnFound := gwns.GatewayNetworks[0] + + return &GatewayNetwork{ + ID: fi.PtrTo(gwnFound.ID), + Zone: fi.PtrTo(gwnFound.Zone.String()), + //Address: fi.PtrTo(gwnFound.Address.IP.String()), + Lifecycle: g.Lifecycle, + Gateway: g.Gateway, + PrivateNetwork: g.PrivateNetwork, + }, nil +} + +func (g *GatewayNetwork) Run(context *fi.CloudupContext) error { + return fi.CloudupDefaultDeltaRunMethod(g, context) +} + +func (_ *GatewayNetwork) CheckChanges(actual, expected, changes *GatewayNetwork) error { + if actual != nil { + if changes.ID != nil { + return fi.CannotChangeField("ID") + } + if changes.Zone != nil { + return fi.CannotChangeField("Zone") + } + } else { + if expected.Zone == nil { + return fi.RequiredField("Zone") + } + } + return nil +} + +func (_ *GatewayNetwork) RenderScw(t *scaleway.ScwAPITarget, actual, expected, changes *GatewayNetwork) error { + if actual != nil { + //TODO(Mia-Cross): update tags + return nil + } + + cloud := t.Cloud.(scaleway.ScwCloud) + zone := scw.Zone(fi.ValueOf(expected.Zone)) + + gwnCreated, err := cloud.GatewayService().CreateGatewayNetwork(&vpcgw.CreateGatewayNetworkRequest{ + Zone: zone, + GatewayID: fi.ValueOf(expected.Gateway.ID), + PrivateNetworkID: fi.ValueOf(expected.PrivateNetwork.ID), + IpamConfig: &vpcgw.CreateGatewayNetworkRequestIpamConfig{ + PushDefaultRoute: false, + }, + EnableMasquerade: true, + }) + if err != nil { + return fmt.Errorf("creating gateway network: %w", err) + } + + _, err = cloud.GatewayService().WaitForGatewayNetwork(&vpcgw.WaitForGatewayNetworkRequest{ + GatewayNetworkID: gwnCreated.ID, + Zone: zone, + }) + if err != nil { + return fmt.Errorf("waiting for gateway: %v", err) + } + + expected.ID = &gwnCreated.ID + + return nil +} + +type gwnIpamConfig struct { + PushDefaultRoute bool `cty:"push_default_route"` +} + +type terraformGatewayNetwork struct { + GatewayID *terraformWriter.Literal `cty:"gateway_id"` + PrivateNetworkID *terraformWriter.Literal `cty:"private_network_id"` + EnableMasquerade bool `cty:"enable_masquerade"` + EnableDHCP bool `cty:"enable_dhcp"` + IpamConfig *gwnIpamConfig `cty:"ipam_config"` +} + +func (_ *GatewayNetwork) RenderTerraform(t *terraform.TerraformTarget, actual, expected, changes *GatewayNetwork) error { + tfName := strings.ReplaceAll(fi.ValueOf(expected.Name), ".", "-") + + tfGWN := terraformGatewayNetwork{ + GatewayID: expected.Gateway.TerraformLink(), + PrivateNetworkID: expected.PrivateNetwork.TerraformLink(), + IpamConfig: &gwnIpamConfig{ + PushDefaultRoute: false, + }, + } + + return t.RenderResource("scaleway_vpc_gateway_network", tfName, tfGWN) +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/gatewaynetwork_fitask.go b/upup/pkg/fi/cloudup/scalewaytasks/gatewaynetwork_fitask.go new file mode 100644 index 0000000000000..b0b11c516362b --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/gatewaynetwork_fitask.go @@ -0,0 +1,52 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes 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. +*/ + +// Code generated by fitask. DO NOT EDIT. + +package scalewaytasks + +import ( + "k8s.io/kops/upup/pkg/fi" +) + +// GatewayNetwork + +var _ fi.HasLifecycle = &GatewayNetwork{} + +// GetLifecycle returns the Lifecycle of the object, implementing fi.HasLifecycle +func (o *GatewayNetwork) GetLifecycle() fi.Lifecycle { + return o.Lifecycle +} + +// SetLifecycle sets the Lifecycle of the object, implementing fi.SetLifecycle +func (o *GatewayNetwork) SetLifecycle(lifecycle fi.Lifecycle) { + o.Lifecycle = lifecycle +} + +var _ fi.HasName = &GatewayNetwork{} + +// GetName returns the Name of the object, implementing fi.HasName +func (o *GatewayNetwork) GetName() *string { + return o.Name +} + +// String is the stringer function for the task, producing readable output using fi.TaskAsString +func (o *GatewayNetwork) String() string { + return fi.CloudupTaskAsString(o) +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/instance.go b/upup/pkg/fi/cloudup/scalewaytasks/instance.go index 97478b92927ff..ec9393eb52a95 100644 --- a/upup/pkg/fi/cloudup/scalewaytasks/instance.go +++ b/upup/pkg/fi/cloudup/scalewaytasks/instance.go @@ -47,8 +47,9 @@ type Instance struct { VolumeSize *int NeedsUpdate []string - UserData *fi.Resource - LoadBalancer *LoadBalancer + UserData *fi.Resource + LoadBalancer *LoadBalancer + PrivateNetwork *PrivateNetwork } var _ fi.CloudupTask = &Instance{} @@ -63,9 +64,15 @@ var _ fi.CloudupHasDependencies = &Instance{} func (s *Instance) GetDependencies(tasks map[string]fi.CloudupTask) []fi.CloudupTask { var deps []fi.CloudupTask for _, task := range tasks { + if _, ok := task.(*LoadBalancer); ok { + deps = append(deps, task) + } if _, ok := task.(*Volume); ok { deps = append(deps, task) } + if _, ok := task.(*PrivateNetwork); ok { + deps = append(deps, task) + } } return deps } diff --git a/upup/pkg/fi/cloudup/scalewaytasks/lb_backend.go b/upup/pkg/fi/cloudup/scalewaytasks/lb_backend.go index 20e8b59e84558..56d80bcf34253 100644 --- a/upup/pkg/fi/cloudup/scalewaytasks/lb_backend.go +++ b/upup/pkg/fi/cloudup/scalewaytasks/lb_backend.go @@ -58,6 +58,9 @@ func (l *LBBackend) GetDependencies(tasks map[string]fi.CloudupTask) []fi.Cloudu if _, ok := task.(*Instance); ok { deps = append(deps, task) } + if _, ok := task.(*PrivateNIC); ok { + deps = append(deps, task) + } } return deps } @@ -133,7 +136,7 @@ func (l *LBBackend) RenderScw(t *scaleway.ScwAPITarget, actual, expected, change lbService := t.Cloud.LBService() zone := scw.Zone(fi.ValueOf(expected.Zone)) - controlPlanesIPs, err := getControlPlanesIPs(t.Cloud, expected.LoadBalancer, zone) + controlPlanesIPs, err := scaleway.GetControlPlanesIPs(t.Cloud, t.Cloud.ClusterName(expected.LoadBalancer.Tags), true) if err != nil { return err } @@ -239,25 +242,3 @@ func (l *LBBackend) RenderTerraform(t *terraform.TerraformTarget, actual, expect func (l *LBBackend) TerraformLink() *terraformWriter.Literal { return terraformWriter.LiteralProperty("scaleway_lb_backend", fi.ValueOf(l.Name), "id") } - -func getControlPlanesIPs(scwCloud scaleway.ScwCloud, lb *LoadBalancer, zone scw.Zone) ([]string, error) { - var controlPlanePrivateIPs []string - - servers, err := scwCloud.GetClusterServers(scwCloud.ClusterName(lb.Tags), nil) - if err != nil { - return nil, fmt.Errorf("getting cluster servers for load-balancer's back-end: %w", err) - } - - for _, server := range servers { - if role := scaleway.InstanceRoleFromTags(server.Tags); role == scaleway.TagRoleWorker { - continue - } - ip, err := scwCloud.GetServerIP(server.ID, server.Zone) - if err != nil { - return nil, fmt.Errorf("getting IP of server %s for load-balancer's back-end: %w", server.Name, err) - } - controlPlanePrivateIPs = append(controlPlanePrivateIPs, ip) - } - - return controlPlanePrivateIPs, nil -} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/loadbalancer.go b/upup/pkg/fi/cloudup/scalewaytasks/loadbalancer.go index 830d4156fab92..bfcb416bead8e 100644 --- a/upup/pkg/fi/cloudup/scalewaytasks/loadbalancer.go +++ b/upup/pkg/fi/cloudup/scalewaytasks/loadbalancer.go @@ -50,15 +50,29 @@ type LoadBalancer struct { // WellKnownServices indicates which services are supported by this resource. // This field is internal and is not rendered to the cloud. WellKnownServices []wellknownservices.WellKnownService + + PrivateNetwork *PrivateNetwork } +var _ fi.CloudupTask = &LoadBalancer{} var _ fi.CompareWithID = &LoadBalancer{} +var _ fi.CloudupHasDependencies = &LoadBalancer{} var _ fi.HasAddress = &LoadBalancer{} func (l *LoadBalancer) CompareWithID() *string { return l.LBID } +func (l *LoadBalancer) GetDependencies(tasks map[string]fi.CloudupTask) []fi.CloudupTask { + var deps []fi.CloudupTask + for _, task := range tasks { + if _, ok := task.(*PrivateNetwork); ok { + deps = append(deps, task) + } + } + return deps +} + // GetWellKnownServices implements fi.HasAddress::GetWellKnownServices. // It indicates which services we support with this load balancer. func (l *LoadBalancer) GetWellKnownServices() []wellknownservices.WellKnownService { @@ -178,12 +192,14 @@ func (l *LoadBalancer) RenderScw(t *scaleway.ScwAPITarget, actual, expected, cha } else { klog.Infof("Creating new load-balancer with name %q", fi.ValueOf(expected.Name)) + zone := scw.Zone(fi.ValueOf(expected.Zone)) lbCreated, err := lbService.CreateLB(&lb.ZonedAPICreateLBRequest{ - Zone: scw.Zone(fi.ValueOf(expected.Zone)), + Zone: zone, Name: fi.ValueOf(expected.Name), Type: LbDefaultType, Tags: expected.Tags, + //AssignFlexibleIP: fi.PtrTo(true), }) if err != nil { return fmt.Errorf("creating load-balancer: %w", err) @@ -191,7 +207,24 @@ func (l *LoadBalancer) RenderScw(t *scaleway.ScwAPITarget, actual, expected, cha _, err = lbService.WaitForLb(&lb.ZonedAPIWaitForLBRequest{ LBID: lbCreated.ID, - Zone: scw.Zone(fi.ValueOf(expected.Zone)), + Zone: zone, + }) + if err != nil { + return fmt.Errorf("waiting for load-balancer %s: %w", lbCreated.ID, err) + } + + _, err = lbService.AttachPrivateNetwork(&lb.ZonedAPIAttachPrivateNetworkRequest{ + Zone: zone, + LBID: lbCreated.ID, + PrivateNetworkID: fi.ValueOf(expected.PrivateNetwork.ID), + }) + if err != nil { + return fmt.Errorf("attaching load-balancer to private network: %w", err) + } + + _, err = lbService.WaitForLb(&lb.ZonedAPIWaitForLBRequest{ + LBID: lbCreated.ID, + Zone: zone, }) if err != nil { return fmt.Errorf("waiting for load-balancer %s: %w", lbCreated.ID, err) @@ -203,7 +236,6 @@ func (l *LoadBalancer) RenderScw(t *scaleway.ScwAPITarget, actual, expected, cha } expected.LBID = &lbCreated.ID expected.LBAddresses = lbIPs - } return nil diff --git a/upup/pkg/fi/cloudup/scalewaytasks/private_network.go b/upup/pkg/fi/cloudup/scalewaytasks/private_network.go new file mode 100644 index 0000000000000..bcca69d6a51a3 --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/private_network.go @@ -0,0 +1,195 @@ +/* +Copyright 2024 The Kubernetes 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 scalewaytasks + +import ( + "fmt" + "net" + "strings" + + "github.com/scaleway/scaleway-sdk-go/api/vpc/v2" + "github.com/scaleway/scaleway-sdk-go/scw" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" + "k8s.io/kops/upup/pkg/fi/cloudup/terraform" + "k8s.io/kops/upup/pkg/fi/cloudup/terraformWriter" +) + +// +kops:fitask +type PrivateNetwork struct { + ID *string + Name *string + Region *string + Tags []string + + IPRange *string + + Lifecycle fi.Lifecycle + VPC *VPC +} + +var _ fi.CloudupTask = &PrivateNetwork{} +var _ fi.CompareWithID = &PrivateNetwork{} +var _ fi.CloudupHasDependencies = &PrivateNetwork{} + +func (p *PrivateNetwork) CompareWithID() *string { + return p.ID +} + +func (p *PrivateNetwork) GetDependencies(tasks map[string]fi.CloudupTask) []fi.CloudupTask { + var deps []fi.CloudupTask + for _, task := range tasks { + if _, ok := task.(*VPC); ok { + deps = append(deps, task) + } + } + return deps +} + +func (p *PrivateNetwork) Find(context *fi.CloudupContext) (*PrivateNetwork, error) { + cloud := context.T.Cloud.(scaleway.ScwCloud) + pns, err := cloud.VPCService().ListPrivateNetworks(&vpc.ListPrivateNetworksRequest{ + Region: scw.Region(cloud.Region()), + Name: p.Name, + Tags: []string{fmt.Sprintf("%s=%s", scaleway.TagClusterName, scaleway.ClusterNameFromTags(p.Tags))}, + }, scw.WithContext(context.Context()), scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("listing private networks: %w", err) + } + + if pns.TotalCount == 0 { + return nil, nil + } + if pns.TotalCount > 1 { + return nil, fmt.Errorf("expected exactly 1 private network, got %d", pns.TotalCount) + } + pnFound := pns.PrivateNetworks[0] + + var ipRange *string + if len(pnFound.Subnets) > 0 { + ipRange = fi.PtrTo(pnFound.Subnets[0].Subnet.String()) + } + return &PrivateNetwork{ + ID: fi.PtrTo(pnFound.ID), + Name: fi.PtrTo(pnFound.Name), + Region: fi.PtrTo(cloud.Region()), + Tags: pnFound.Tags, + IPRange: ipRange, + Lifecycle: p.Lifecycle, + VPC: &VPC{ + Name: fi.PtrTo(pnFound.Name), + }, + }, nil +} + +func (p *PrivateNetwork) Run(c *fi.CloudupContext) error { + return fi.CloudupDefaultDeltaRunMethod(p, c) +} + +func (_ *PrivateNetwork) CheckChanges(actual, expected, changes *PrivateNetwork) error { + if actual != nil { + if changes.Name != nil { + return fi.CannotChangeField("Name") + } + if changes.ID != nil { + return fi.CannotChangeField("ID") + } + if changes.Region != nil { + return fi.CannotChangeField("Region") + } + //TODO(Mia-Cross): IP Range ??? + } else { + if expected.Name == nil { + return fi.RequiredField("Name") + } + if expected.Region == nil { + return fi.RequiredField("Region") + } + if expected.IPRange == nil { + return fi.RequiredField("IPRange") + } + } + return nil +} + +func (_ *PrivateNetwork) RenderScw(t *scaleway.ScwAPITarget, actual, expected, changes *PrivateNetwork) error { + if actual != nil { + //TODO(Mia-Cross): update tags + //TODO(Mia-Cross): update IPRange ?? + expected.ID = actual.ID + + return nil + } + + cloud := t.Cloud.(scaleway.ScwCloud) + region := scw.Region(fi.ValueOf(expected.Region)) + _, ipRange, err := net.ParseCIDR(fi.ValueOf(expected.IPRange)) + if err != nil { + return fmt.Errorf("parsing CIDR: %w", err) + } + + pnCreated, err := cloud.VPCService().CreatePrivateNetwork(&vpc.CreatePrivateNetworkRequest{ + Region: region, + Name: fi.ValueOf(expected.Name), + Tags: expected.Tags, + Subnets: []scw.IPNet{ + {IPNet: fi.ValueOf(ipRange)}, + }, + VpcID: expected.VPC.ID, + }) + if err != nil { + return fmt.Errorf("creating private network: %w", err) + } + + expected.ID = &pnCreated.ID + + // We create a public gateway + // We create a DHCP server + // We link the gateway (with DHCP) to the private network once it's in a stable state + + return nil +} + +type pnSubnet struct { + Subnet *string `cty:"subnet"` +} + +type terraformPrivateNetwork struct { + Name *string `cty:"name"` + Tags []string `cty:"tags"` + IPV4Subnet *pnSubnet `cty:"ipv4_subnet"` + VPCID *terraformWriter.Literal `cty:"vpc_id"` +} + +func (_ *PrivateNetwork) RenderTerraform(t *terraform.TerraformTarget, actual, expected, changes *PrivateNetwork) error { + tfName := strings.ReplaceAll(fi.ValueOf(expected.Name), ".", "-") + + tfPN := &terraformPrivateNetwork{ + Name: expected.Name, + Tags: expected.Tags, + IPV4Subnet: &pnSubnet{ + Subnet: expected.IPRange, + }, + VPCID: expected.VPC.TerraformLink(), + } + + return t.RenderResource("scaleway_vpc_private_network", tfName, tfPN) +} + +func (p *PrivateNetwork) TerraformLink() *terraformWriter.Literal { + return terraformWriter.LiteralProperty("scaleway_vpc_private_network", fi.ValueOf(p.Name), "id") +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/private_nic.go b/upup/pkg/fi/cloudup/scalewaytasks/private_nic.go new file mode 100644 index 0000000000000..5fb070b954ed1 --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/private_nic.go @@ -0,0 +1,271 @@ +/* +Copyright 2024 The Kubernetes 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 scalewaytasks + +import ( + "fmt" + "strings" + + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + "github.com/scaleway/scaleway-sdk-go/scw" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" + "k8s.io/kops/upup/pkg/fi/cloudup/terraform" + "k8s.io/kops/upup/pkg/fi/cloudup/terraformWriter" +) + +// +kops:fitask +type PrivateNIC struct { + Name *string + Zone *string + Tags []string + Count int + + Lifecycle fi.Lifecycle + Instance *Instance + PrivateNetwork *PrivateNetwork +} + +var _ fi.CloudupTask = &PrivateNIC{} +var _ fi.CompareWithID = &PrivateNIC{} +var _ fi.CloudupHasDependencies = &PrivateNIC{} + +//var _ fi.HasAddress = &PrivateNIC{} + +func (p *PrivateNIC) CompareWithID() *string { + return p.Name +} + +func (p *PrivateNIC) GetDependencies(tasks map[string]fi.CloudupTask) []fi.CloudupTask { + var deps []fi.CloudupTask + for _, task := range tasks { + if _, ok := task.(*Instance); ok { + deps = append(deps, task) + } + if _, ok := task.(*PrivateNetwork); ok { + deps = append(deps, task) + } + } + return deps +} + +/* + func (p *PrivateNIC) IsForAPIServer() bool { + return p.ForAPIServer + } + + func (p *PrivateNIC) FindAddresses(context *fi.CloudupContext) ([]string, error) { + cloud := context.T.Cloud.(scaleway.ScwCloud) + region, err := scw.Zone(fi.ValueOf(p.Zone)).Region() + if err != nil { + return nil, fmt.Errorf("finding private NIC's region: %w", err) + } + + servers, err := cloud.GetClusterServers(scaleway.ClusterNameFromTags(p.Tags), p.Name) + if err != nil { + return nil, err + } + + var pnicIPs []string + + for _, server := range servers { + + pNICs, err := cloud.InstanceService().ListPrivateNICs(&instance.ListPrivateNICsRequest{ + Zone: scw.Zone(cloud.Zone()), + Tags: p.Tags, + ServerID: server.ID, + }, scw.WithContext(context.Context()), scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("listing private NICs for instance %q: %w", fi.ValueOf(p.Name), err) + } + + for _, pNIC := range pNICs.PrivateNics { + + ips, err := cloud.IPAMService().ListIPs(&ipam.ListIPsRequest{ + Region: region, + PrivateNetworkID: p.PrivateNetwork.ID, + ResourceID: &pNIC.ID, + }, scw.WithContext(context.Context()), scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("listing private NIC's IPs: %w", err) + } + + for _, ip := range ips.IPs { + pnicIPs = append(pnicIPs, ip.Address.IP.String()) + } + } + } + return pnicIPs, nil + } +*/ +func (p *PrivateNIC) Find(context *fi.CloudupContext) (*PrivateNIC, error) { + cloud := context.T.Cloud.(scaleway.ScwCloud) + servers, err := cloud.GetClusterServers(scaleway.ClusterNameFromTags(p.Tags), p.Name) + if err != nil { + return nil, err + } + + var privateNICsFound []*instance.PrivateNIC + for _, server := range servers { + pNICs, err := cloud.InstanceService().ListPrivateNICs(&instance.ListPrivateNICsRequest{ + Zone: scw.Zone(cloud.Zone()), + Tags: p.Tags, + ServerID: server.ID, + }, scw.WithContext(context.Context()), scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("listing private NICs for instance group %s: %w", fi.ValueOf(p.Name), err) + } + for _, pNIC := range pNICs.PrivateNics { + privateNICsFound = append(privateNICsFound, pNIC) + } + } + + if len(privateNICsFound) == 0 { + return nil, nil + } + pNICFound := privateNICsFound[0] + + return &PrivateNIC{ + //ID: fi.PtrTo(pNICFound.ID), + Name: p.Name, + Zone: p.Zone, + Tags: pNICFound.Tags, + Count: len(privateNICsFound), + Lifecycle: p.Lifecycle, + Instance: p.Instance, + PrivateNetwork: p.PrivateNetwork, + }, nil +} + +func (p *PrivateNIC) Run(context *fi.CloudupContext) error { + return fi.CloudupDefaultDeltaRunMethod(p, context) +} + +func (p *PrivateNIC) CheckChanges(actual, expected, changes *PrivateNIC) error { + if actual != nil { + if changes.Name != nil { + return fi.CannotChangeField("Name") + } + if changes.Zone != nil { + return fi.CannotChangeField("Zone") + } + } else { + if expected.Name == nil { + return fi.RequiredField("Name") + } + if expected.Zone == nil { + return fi.RequiredField("Zone") + } + } + return nil +} + +func (_ *PrivateNIC) RenderScw(t *scaleway.ScwAPITarget, actual, expected, changes *PrivateNIC) error { + cloud := t.Cloud.(scaleway.ScwCloud) + zone := scw.Zone(fi.ValueOf(expected.Zone)) + clusterName := scaleway.ClusterNameFromTags(expected.Instance.Tags) + igName := fi.ValueOf(expected.Name) + + var serversNeedUpdate []string + var serversNeedPNIC []string + servers, err := cloud.GetClusterServers(clusterName, &igName) + if err != nil { + return fmt.Errorf("rendering private NIC for instance group %q: getting servers: %w", igName, err) + } + for _, server := range servers { + if len(server.PrivateNics) > 0 { + serversNeedUpdate = append(serversNeedUpdate, server.ID) + } else { + serversNeedPNIC = append(serversNeedPNIC, server.ID) + } + } + + if actual != nil { + + for _, serverID := range serversNeedUpdate { + pNICs, err := cloud.InstanceService().ListPrivateNICs(&instance.ListPrivateNICsRequest{ + Zone: zone, + ServerID: serverID, + }, scw.WithAllPages()) + if err != nil { + return fmt.Errorf("failed to list private NICs for server %s: %w", serverID, err) + } + + for _, pNIC := range pNICs.PrivateNics { + _, err = cloud.InstanceService().UpdatePrivateNIC(&instance.UpdatePrivateNICRequest{ + Zone: zone, + ServerID: serverID, + PrivateNicID: pNIC.ID, + Tags: fi.PtrTo(expected.Tags), + }) + if err != nil { + return fmt.Errorf("updating Private NIC %s for server %q: %w", pNIC.ID, serverID, err) + } + } + } + } + + for _, serverID := range serversNeedPNIC { + pNICCreated, err := cloud.InstanceService().CreatePrivateNIC(&instance.CreatePrivateNICRequest{ + Zone: zone, + ServerID: serverID, + PrivateNetworkID: fi.ValueOf(expected.PrivateNetwork.ID), + Tags: expected.Tags, + }) + if err != nil { + return fmt.Errorf("creating private NIC between instance %s and private network %s: %w", serverID, fi.ValueOf(expected.PrivateNetwork.ID), err) + } + + // We wait for the private nic to be ready + _, err = cloud.InstanceService().WaitForPrivateNIC(&instance.WaitForPrivateNICRequest{ + ServerID: serverID, + PrivateNicID: pNICCreated.PrivateNic.ID, + Zone: zone, + }) + if err != nil { + return fmt.Errorf("waiting for private NIC %s: %w", pNICCreated.PrivateNic.ID, err) + } + + } + + return nil +} + +type terraformPrivateNIC struct { + ServerID *terraformWriter.Literal `cty:"server_id"` + PrivateNetworkID *terraformWriter.Literal `cty:"private_network_id"` + Tags []string `cty:"tags"` +} + +func (_ *PrivateNIC) RenderTerraform(t *terraform.TerraformTarget, actual, expected, changes *PrivateNIC) error { + for i := 0; i < expected.Count; i++ { + uniqueName := fmt.Sprintf("%s-%d", fi.ValueOf(expected.Name), i) + tfName := strings.ReplaceAll(uniqueName, ".", "-") + + tfPNic := terraformPrivateNIC{ + ServerID: terraformWriter.LiteralProperty("scaleway_instance_server", tfName, "id"), + PrivateNetworkID: expected.PrivateNetwork.TerraformLink(), + Tags: expected.Tags, + } + + err := t.RenderResource("scaleway_instance_private_nic", tfName, tfPNic) + if err != nil { + return err + } + } + return nil +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/privatenetwork_fitask.go b/upup/pkg/fi/cloudup/scalewaytasks/privatenetwork_fitask.go new file mode 100644 index 0000000000000..fa6c1980b878b --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/privatenetwork_fitask.go @@ -0,0 +1,52 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes 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. +*/ + +// Code generated by fitask. DO NOT EDIT. + +package scalewaytasks + +import ( + "k8s.io/kops/upup/pkg/fi" +) + +// PrivateNetwork + +var _ fi.HasLifecycle = &PrivateNetwork{} + +// GetLifecycle returns the Lifecycle of the object, implementing fi.HasLifecycle +func (o *PrivateNetwork) GetLifecycle() fi.Lifecycle { + return o.Lifecycle +} + +// SetLifecycle sets the Lifecycle of the object, implementing fi.SetLifecycle +func (o *PrivateNetwork) SetLifecycle(lifecycle fi.Lifecycle) { + o.Lifecycle = lifecycle +} + +var _ fi.HasName = &PrivateNetwork{} + +// GetName returns the Name of the object, implementing fi.HasName +func (o *PrivateNetwork) GetName() *string { + return o.Name +} + +// String is the stringer function for the task, producing readable output using fi.TaskAsString +func (o *PrivateNetwork) String() string { + return fi.CloudupTaskAsString(o) +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/privatenic_fitask.go b/upup/pkg/fi/cloudup/scalewaytasks/privatenic_fitask.go new file mode 100644 index 0000000000000..25059c56d7f83 --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/privatenic_fitask.go @@ -0,0 +1,52 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes 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. +*/ + +// Code generated by fitask. DO NOT EDIT. + +package scalewaytasks + +import ( + "k8s.io/kops/upup/pkg/fi" +) + +// PrivateNIC + +var _ fi.HasLifecycle = &PrivateNIC{} + +// GetLifecycle returns the Lifecycle of the object, implementing fi.HasLifecycle +func (o *PrivateNIC) GetLifecycle() fi.Lifecycle { + return o.Lifecycle +} + +// SetLifecycle sets the Lifecycle of the object, implementing fi.SetLifecycle +func (o *PrivateNIC) SetLifecycle(lifecycle fi.Lifecycle) { + o.Lifecycle = lifecycle +} + +var _ fi.HasName = &PrivateNIC{} + +// GetName returns the Name of the object, implementing fi.HasName +func (o *PrivateNIC) GetName() *string { + return o.Name +} + +// String is the stringer function for the task, producing readable output using fi.TaskAsString +func (o *PrivateNIC) String() string { + return fi.CloudupTaskAsString(o) +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/vpc.go b/upup/pkg/fi/cloudup/scalewaytasks/vpc.go new file mode 100644 index 0000000000000..72599f2380152 --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/vpc.go @@ -0,0 +1,141 @@ +/* +Copyright 2024 The Kubernetes 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 scalewaytasks + +import ( + "fmt" + "strings" + + "github.com/scaleway/scaleway-sdk-go/api/vpc/v2" + "github.com/scaleway/scaleway-sdk-go/scw" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" + "k8s.io/kops/upup/pkg/fi/cloudup/terraform" + "k8s.io/kops/upup/pkg/fi/cloudup/terraformWriter" +) + +// +kops:fitask +type VPC struct { + ID *string + Name *string + Region *string + Tags []string + + Lifecycle fi.Lifecycle +} + +var _ fi.CloudupTask = &VPC{} +var _ fi.CompareWithID = &VPC{} + +func (v *VPC) CompareWithID() *string { + return v.ID +} + +func (v *VPC) Find(context *fi.CloudupContext) (*VPC, error) { + cloud := context.T.Cloud.(scaleway.ScwCloud) + vpcs, err := cloud.VPCService().ListVPCs(&vpc.ListVPCsRequest{ + Region: scw.Region(cloud.Region()), + Name: v.Name, + Tags: []string{fmt.Sprintf("%s=%s", scaleway.TagClusterName, scaleway.ClusterNameFromTags(v.Tags))}, + }, scw.WithContext(context.Context()), scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("listing VPCs: %w", err) + } + + if vpcs.TotalCount == 0 { + return nil, nil + } + if vpcs.TotalCount > 1 { + return nil, fmt.Errorf("expected exactly 1 VPC, got %d", vpcs.TotalCount) + } + vpcFound := vpcs.Vpcs[0] + + return &VPC{ + ID: fi.PtrTo(vpcFound.ID), + Name: fi.PtrTo(vpcFound.Name), + Region: fi.PtrTo(vpcFound.Region.String()), + Tags: vpcFound.Tags, + Lifecycle: v.Lifecycle, + }, nil +} + +func (v *VPC) Run(context *fi.CloudupContext) error { + return fi.CloudupDefaultDeltaRunMethod(v, context) +} + +func (_ *VPC) CheckChanges(actual, expected, changes *VPC) error { + if actual != nil { + if changes.Name != nil { + return fi.CannotChangeField("Name") + } + if changes.ID != nil { + return fi.CannotChangeField("ID") + } + if changes.Region != nil { + return fi.CannotChangeField("Region") + } + } else { + if expected.Name == nil { + return fi.RequiredField("Name") + } + if expected.Region == nil { + return fi.RequiredField("Region") + } + } + return nil +} + +func (_ *VPC) RenderScw(t *scaleway.ScwAPITarget, actual, expected, _ *VPC) error { + if actual != nil { + //TODO(Mia-Cross): update tags + return nil + } + + cloud := t.Cloud.(scaleway.ScwCloud) + region := scw.Region(fi.ValueOf(expected.Region)) + + vpcCreated, err := cloud.VPCService().CreateVPC(&vpc.CreateVPCRequest{ + Region: region, + Name: fi.ValueOf(expected.Name), + Tags: expected.Tags, + }) + if err != nil { + return fmt.Errorf("creating VPC: %w", err) + } + + expected.ID = &vpcCreated.ID + + return nil +} + +type terraformVPC struct { + Name *string `cty:"name"` + Tags []string `cty:"tags"` +} + +func (_ *VPC) RenderTerraform(t *terraform.TerraformTarget, actual, expected, changes *VPC) error { + tfName := strings.ReplaceAll(fi.ValueOf(expected.Name), ".", "-") + tfVPC := terraformVPC{ + Name: expected.Name, + Tags: expected.Tags, + } + return t.RenderResource("scaleway_vpc", tfName, tfVPC) +} + +func (v *VPC) TerraformLink() *terraformWriter.Literal { + return terraformWriter.LiteralProperty("scaleway_vpc", fi.ValueOf(v.Name), "id") +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/vpc_fitask.go b/upup/pkg/fi/cloudup/scalewaytasks/vpc_fitask.go new file mode 100644 index 0000000000000..0dcd914564582 --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/vpc_fitask.go @@ -0,0 +1,52 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes 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. +*/ + +// Code generated by fitask. DO NOT EDIT. + +package scalewaytasks + +import ( + "k8s.io/kops/upup/pkg/fi" +) + +// VPC + +var _ fi.HasLifecycle = &VPC{} + +// GetLifecycle returns the Lifecycle of the object, implementing fi.HasLifecycle +func (o *VPC) GetLifecycle() fi.Lifecycle { + return o.Lifecycle +} + +// SetLifecycle sets the Lifecycle of the object, implementing fi.SetLifecycle +func (o *VPC) SetLifecycle(lifecycle fi.Lifecycle) { + o.Lifecycle = lifecycle +} + +var _ fi.HasName = &VPC{} + +// GetName returns the Name of the object, implementing fi.HasName +func (o *VPC) GetName() *string { + return o.Name +} + +// String is the stringer function for the task, producing readable output using fi.TaskAsString +func (o *VPC) String() string { + return fi.CloudupTaskAsString(o) +} diff --git a/upup/pkg/fi/cloudup/template_functions.go b/upup/pkg/fi/cloudup/template_functions.go index d779c896c678d..f3ce9c76f528f 100644 --- a/upup/pkg/fi/cloudup/template_functions.go +++ b/upup/pkg/fi/cloudup/template_functions.go @@ -223,6 +223,12 @@ func (tf *TemplateFunctions) AddTo(dest template.FuncMap, secretStore fi.SecretS scwCloud := tf.cloud.(scaleway.ScwCloud) return scwCloud.Zone() } + dest["SCW_PN_ID"] = func() string { + if cluster.Spec.Networking.NetworkID != "" { + return cluster.Spec.Networking.NetworkID + } + return cluster.Name + } if featureflag.Spotinst.Enabled() { if creds, err := spotinst.LoadCredentials(); err == nil { diff --git a/vendor/github.com/scaleway/scaleway-sdk-go/api/vpc/v2/vpc_sdk.go b/vendor/github.com/scaleway/scaleway-sdk-go/api/vpc/v2/vpc_sdk.go new file mode 100644 index 0000000000000..10475107b216c --- /dev/null +++ b/vendor/github.com/scaleway/scaleway-sdk-go/api/vpc/v2/vpc_sdk.go @@ -0,0 +1,1328 @@ +// This file was automatically generated. DO NOT EDIT. +// If you have any remark or suggestion do not hesitate to open an issue. + +// Package vpc provides methods and message types of the vpc v2 API. +package vpc + +import ( + "bytes" + "encoding/json" + "fmt" + "net" + "net/http" + "net/url" + "strings" + "time" + + "github.com/scaleway/scaleway-sdk-go/internal/errors" + "github.com/scaleway/scaleway-sdk-go/internal/marshaler" + "github.com/scaleway/scaleway-sdk-go/internal/parameter" + "github.com/scaleway/scaleway-sdk-go/namegenerator" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +// always import dependencies +var ( + _ fmt.Stringer + _ json.Unmarshaler + _ url.URL + _ net.IP + _ http.Header + _ bytes.Reader + _ time.Time + _ = strings.Join + + _ scw.ScalewayRequest + _ marshaler.Duration + _ scw.File + _ = parameter.AddToQuery + _ = namegenerator.GetRandomName +) + +type ListPrivateNetworksRequestOrderBy string + +const ( + ListPrivateNetworksRequestOrderByCreatedAtAsc = ListPrivateNetworksRequestOrderBy("created_at_asc") + ListPrivateNetworksRequestOrderByCreatedAtDesc = ListPrivateNetworksRequestOrderBy("created_at_desc") + ListPrivateNetworksRequestOrderByNameAsc = ListPrivateNetworksRequestOrderBy("name_asc") + ListPrivateNetworksRequestOrderByNameDesc = ListPrivateNetworksRequestOrderBy("name_desc") +) + +func (enum ListPrivateNetworksRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "created_at_asc" + } + return string(enum) +} + +func (enum ListPrivateNetworksRequestOrderBy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ListPrivateNetworksRequestOrderBy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ListPrivateNetworksRequestOrderBy(ListPrivateNetworksRequestOrderBy(tmp).String()) + return nil +} + +type ListRoutesWithNexthopRequestOrderBy string + +const ( + ListRoutesWithNexthopRequestOrderByCreatedAtAsc = ListRoutesWithNexthopRequestOrderBy("created_at_asc") + ListRoutesWithNexthopRequestOrderByCreatedAtDesc = ListRoutesWithNexthopRequestOrderBy("created_at_desc") + ListRoutesWithNexthopRequestOrderByDestinationAsc = ListRoutesWithNexthopRequestOrderBy("destination_asc") + ListRoutesWithNexthopRequestOrderByDestinationDesc = ListRoutesWithNexthopRequestOrderBy("destination_desc") + ListRoutesWithNexthopRequestOrderByPrefixLenAsc = ListRoutesWithNexthopRequestOrderBy("prefix_len_asc") + ListRoutesWithNexthopRequestOrderByPrefixLenDesc = ListRoutesWithNexthopRequestOrderBy("prefix_len_desc") +) + +func (enum ListRoutesWithNexthopRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "created_at_asc" + } + return string(enum) +} + +func (enum ListRoutesWithNexthopRequestOrderBy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ListRoutesWithNexthopRequestOrderBy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ListRoutesWithNexthopRequestOrderBy(ListRoutesWithNexthopRequestOrderBy(tmp).String()) + return nil +} + +type ListVPCsRequestOrderBy string + +const ( + ListVPCsRequestOrderByCreatedAtAsc = ListVPCsRequestOrderBy("created_at_asc") + ListVPCsRequestOrderByCreatedAtDesc = ListVPCsRequestOrderBy("created_at_desc") + ListVPCsRequestOrderByNameAsc = ListVPCsRequestOrderBy("name_asc") + ListVPCsRequestOrderByNameDesc = ListVPCsRequestOrderBy("name_desc") +) + +func (enum ListVPCsRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "created_at_asc" + } + return string(enum) +} + +func (enum ListVPCsRequestOrderBy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ListVPCsRequestOrderBy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ListVPCsRequestOrderBy(ListVPCsRequestOrderBy(tmp).String()) + return nil +} + +type RouteWithNexthopResourceType string + +const ( + RouteWithNexthopResourceTypeUnknownType = RouteWithNexthopResourceType("unknown_type") + RouteWithNexthopResourceTypeVpcGatewayNetwork = RouteWithNexthopResourceType("vpc_gateway_network") + RouteWithNexthopResourceTypeInstancePrivateNic = RouteWithNexthopResourceType("instance_private_nic") + RouteWithNexthopResourceTypeBaremetalPrivateNic = RouteWithNexthopResourceType("baremetal_private_nic") +) + +func (enum RouteWithNexthopResourceType) String() string { + if enum == "" { + // return default value if empty + return "unknown_type" + } + return string(enum) +} + +func (enum RouteWithNexthopResourceType) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *RouteWithNexthopResourceType) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = RouteWithNexthopResourceType(RouteWithNexthopResourceType(tmp).String()) + return nil +} + +// Subnet: subnet. +type Subnet struct { + // ID: ID of the subnet. + ID string `json:"id"` + + // CreatedAt: subnet creation date. + CreatedAt *time.Time `json:"created_at"` + + // UpdatedAt: subnet last modification date. + UpdatedAt *time.Time `json:"updated_at"` + + // Subnet: subnet CIDR. + Subnet scw.IPNet `json:"subnet"` +} + +// PrivateNetwork: private network. +type PrivateNetwork struct { + // ID: private Network ID. + ID string `json:"id"` + + // Name: private Network name. + Name string `json:"name"` + + // OrganizationID: scaleway Organization the Private Network belongs to. + OrganizationID string `json:"organization_id"` + + // ProjectID: scaleway Project the Private Network belongs to. + ProjectID string `json:"project_id"` + + // Region: region in which the Private Network is available. + Region scw.Region `json:"region"` + + // Tags: tags of the Private Network. + Tags []string `json:"tags"` + + // CreatedAt: date the Private Network was created. + CreatedAt *time.Time `json:"created_at"` + + // UpdatedAt: date the Private Network was last modified. + UpdatedAt *time.Time `json:"updated_at"` + + // Subnets: private Network subnets. + Subnets []*Subnet `json:"subnets"` + + // VpcID: vPC the Private Network belongs to. + VpcID string `json:"vpc_id"` + + // DHCPEnabled: defines whether managed DHCP is enabled for this Private Network. + DHCPEnabled bool `json:"dhcp_enabled"` +} + +// Route: route. +type Route struct { + ID string `json:"id"` + + CreatedAt *time.Time `json:"created_at"` + + VpcID string `json:"vpc_id"` + + Destination scw.IPNet `json:"destination"` + + NexthopResourceID *string `json:"nexthop_resource_id"` + + NexthopPrivateNetworkID *string `json:"nexthop_private_network_id"` + + Tags []string `json:"tags"` + + Description string `json:"description"` + + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"region"` +} + +// RouteWithNexthop: route with nexthop. +type RouteWithNexthop struct { + // Route: route. + Route *Route `json:"route"` + + // NexthopIP: IP of the route's next hop. + NexthopIP *net.IP `json:"nexthop_ip"` + + // NexthopName: name of the route's next hop. + NexthopName *string `json:"nexthop_name"` + + // NexthopResourceType: resource type of the route's next hop. + // Default value: unknown_type + NexthopResourceType RouteWithNexthopResourceType `json:"nexthop_resource_type"` +} + +// VPC: vpc. +type VPC struct { + // ID: vPC ID. + ID string `json:"id"` + + // Name: vPC name. + Name string `json:"name"` + + // OrganizationID: scaleway Organization the VPC belongs to. + OrganizationID string `json:"organization_id"` + + // ProjectID: scaleway Project the VPC belongs to. + ProjectID string `json:"project_id"` + + // Region: region of the VPC. + Region scw.Region `json:"region"` + + // Tags: tags for the VPC. + Tags []string `json:"tags"` + + // IsDefault: defines whether the VPC is the default one for its Project. + IsDefault bool `json:"is_default"` + + // CreatedAt: date the VPC was created. + CreatedAt *time.Time `json:"created_at"` + + // UpdatedAt: date the VPC was last modified. + UpdatedAt *time.Time `json:"updated_at"` + + // PrivateNetworkCount: number of Private Networks within this VPC. + PrivateNetworkCount uint32 `json:"private_network_count"` + + // RoutingEnabled: defines whether the VPC routes traffic between its Private Networks. + RoutingEnabled bool `json:"routing_enabled"` +} + +// AddSubnetsRequest: add subnets request. +type AddSubnetsRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // PrivateNetworkID: private Network ID. + PrivateNetworkID string `json:"-"` + + // Subnets: private Network subnets CIDR. + Subnets []scw.IPNet `json:"subnets"` +} + +// AddSubnetsResponse: add subnets response. +type AddSubnetsResponse struct { + Subnets []scw.IPNet `json:"subnets"` +} + +// CreatePrivateNetworkRequest: create private network request. +type CreatePrivateNetworkRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // Name: name for the Private Network. + Name string `json:"name"` + + // ProjectID: scaleway Project in which to create the Private Network. + ProjectID string `json:"project_id"` + + // Tags: tags for the Private Network. + Tags []string `json:"tags"` + + // Subnets: private Network subnets CIDR. + Subnets []scw.IPNet `json:"subnets"` + + // VpcID: vPC in which to create the Private Network. + VpcID *string `json:"vpc_id,omitempty"` +} + +// CreateVPCRequest: create vpc request. +type CreateVPCRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // Name: name for the VPC. + Name string `json:"name"` + + // ProjectID: scaleway Project in which to create the VPC. + ProjectID string `json:"project_id"` + + // Tags: tags for the VPC. + Tags []string `json:"tags"` + + // EnableRouting: enable routing between Private Networks in the VPC. + EnableRouting bool `json:"enable_routing"` +} + +// DeletePrivateNetworkRequest: delete private network request. +type DeletePrivateNetworkRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // PrivateNetworkID: private Network ID. + PrivateNetworkID string `json:"-"` +} + +// DeleteSubnetsRequest: delete subnets request. +type DeleteSubnetsRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // PrivateNetworkID: private Network ID. + PrivateNetworkID string `json:"-"` + + // Subnets: private Network subnets CIDR. + Subnets []scw.IPNet `json:"subnets"` +} + +// DeleteSubnetsResponse: delete subnets response. +type DeleteSubnetsResponse struct { + Subnets []scw.IPNet `json:"subnets"` +} + +// DeleteVPCRequest: delete vpc request. +type DeleteVPCRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // VpcID: vPC ID. + VpcID string `json:"-"` +} + +// EnableDHCPRequest: enable dhcp request. +type EnableDHCPRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // PrivateNetworkID: private Network ID. + PrivateNetworkID string `json:"-"` +} + +// EnableRoutingRequest: enable routing request. +type EnableRoutingRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + VpcID string `json:"-"` +} + +// GetPrivateNetworkRequest: get private network request. +type GetPrivateNetworkRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // PrivateNetworkID: private Network ID. + PrivateNetworkID string `json:"-"` +} + +// GetVPCRequest: get vpc request. +type GetVPCRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // VpcID: vPC ID. + VpcID string `json:"-"` +} + +// ListPrivateNetworksRequest: list private networks request. +type ListPrivateNetworksRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // OrderBy: sort order of the returned Private Networks. + // Default value: created_at_asc + OrderBy ListPrivateNetworksRequestOrderBy `json:"-"` + + // Page: page number to return, from the paginated results. + Page *int32 `json:"-"` + + // PageSize: maximum number of Private Networks to return per page. + PageSize *uint32 `json:"-"` + + // Name: name to filter for. Only Private Networks with names containing this string will be returned. + Name *string `json:"-"` + + // Tags: tags to filter for. Only Private Networks with one or more matching tags will be returned. + Tags []string `json:"-"` + + // OrganizationID: organization ID to filter for. Only Private Networks belonging to this Organization will be returned. + OrganizationID *string `json:"-"` + + // ProjectID: project ID to filter for. Only Private Networks belonging to this Project will be returned. + ProjectID *string `json:"-"` + + // PrivateNetworkIDs: private Network IDs to filter for. Only Private Networks with one of these IDs will be returned. + PrivateNetworkIDs []string `json:"-"` + + // VpcID: vPC ID to filter for. Only Private Networks belonging to this VPC will be returned. + VpcID *string `json:"-"` + + // DHCPEnabled: DHCP status to filter for. When true, only Private Networks with managed DHCP enabled will be returned. + DHCPEnabled *bool `json:"-"` +} + +// ListPrivateNetworksResponse: list private networks response. +type ListPrivateNetworksResponse struct { + PrivateNetworks []*PrivateNetwork `json:"private_networks"` + + TotalCount uint32 `json:"total_count"` +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListPrivateNetworksResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListPrivateNetworksResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListPrivateNetworksResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.PrivateNetworks = append(r.PrivateNetworks, results.PrivateNetworks...) + r.TotalCount += uint32(len(results.PrivateNetworks)) + return uint32(len(results.PrivateNetworks)), nil +} + +// ListRoutesWithNexthopResponse: list routes with nexthop response. +type ListRoutesWithNexthopResponse struct { + // Routes: list of routes. + Routes []*RouteWithNexthop `json:"routes"` + + // TotalCount: total number of routes. + TotalCount uint64 `json:"total_count"` +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListRoutesWithNexthopResponse) UnsafeGetTotalCount() uint64 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListRoutesWithNexthopResponse) UnsafeAppend(res interface{}) (uint64, error) { + results, ok := res.(*ListRoutesWithNexthopResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.Routes = append(r.Routes, results.Routes...) + r.TotalCount += uint64(len(results.Routes)) + return uint64(len(results.Routes)), nil +} + +// ListVPCsRequest: list vp cs request. +type ListVPCsRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // OrderBy: sort order of the returned VPCs. + // Default value: created_at_asc + OrderBy ListVPCsRequestOrderBy `json:"-"` + + // Page: page number to return, from the paginated results. + Page *int32 `json:"-"` + + // PageSize: maximum number of VPCs to return per page. + PageSize *uint32 `json:"-"` + + // Name: name to filter for. Only VPCs with names containing this string will be returned. + Name *string `json:"-"` + + // Tags: tags to filter for. Only VPCs with one more more matching tags will be returned. + Tags []string `json:"-"` + + // OrganizationID: organization ID to filter for. Only VPCs belonging to this Organization will be returned. + OrganizationID *string `json:"-"` + + // ProjectID: project ID to filter for. Only VPCs belonging to this Project will be returned. + ProjectID *string `json:"-"` + + // IsDefault: defines whether to filter only for VPCs which are the default one for their Project. + IsDefault *bool `json:"-"` + + // RoutingEnabled: defines whether to filter only for VPCs which route traffic between their Private Networks. + RoutingEnabled *bool `json:"-"` +} + +// ListVPCsResponse: list vp cs response. +type ListVPCsResponse struct { + Vpcs []*VPC `json:"vpcs"` + + TotalCount uint32 `json:"total_count"` +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListVPCsResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListVPCsResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListVPCsResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.Vpcs = append(r.Vpcs, results.Vpcs...) + r.TotalCount += uint32(len(results.Vpcs)) + return uint32(len(results.Vpcs)), nil +} + +// MigrateZonalPrivateNetworksRequest: migrate zonal private networks request. +type MigrateZonalPrivateNetworksRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // OrganizationID: organization ID to target. The specified zoned Private Networks within this Organization will be migrated to regional. + // Precisely one of OrganizationID, ProjectID must be set. + OrganizationID *string `json:"organization_id,omitempty"` + + // ProjectID: project to target. The specified zoned Private Networks within this Project will be migrated to regional. + // Precisely one of OrganizationID, ProjectID must be set. + ProjectID *string `json:"project_id,omitempty"` + + // PrivateNetworkIDs: iDs of the Private Networks to migrate. + PrivateNetworkIDs []string `json:"private_network_ids"` +} + +// RoutesWithNexthopAPIListRoutesWithNexthopRequest: routes with nexthop api list routes with nexthop request. +type RoutesWithNexthopAPIListRoutesWithNexthopRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // OrderBy: sort order of the returned routes. + // Default value: created_at_asc + OrderBy ListRoutesWithNexthopRequestOrderBy `json:"-"` + + // Page: page number to return, from the paginated results. + Page *int32 `json:"-"` + + // PageSize: maximum number of routes to return per page. + PageSize *uint32 `json:"-"` + + // VpcID: vPC to filter for. Only routes within this VPC will be returned. + VpcID *string `json:"-"` + + // NexthopResourceID: next hop resource ID to filter for. Only routes with a matching next hop resource ID will be returned. + NexthopResourceID *string `json:"-"` + + // NexthopPrivateNetworkID: next hop private network ID to filter for. Only routes with a matching next hop private network ID will be returned. + NexthopPrivateNetworkID *string `json:"-"` + + // NexthopResourceType: next hop resource type to filter for. Only Routes with a matching next hop resource type will be returned. + // Default value: unknown_type + NexthopResourceType RouteWithNexthopResourceType `json:"-"` + + // Contains: only routes whose destination is contained in this subnet will be returned. + Contains *scw.IPNet `json:"-"` + + // Tags: tags to filter for, only routes with one or more matching tags will be returned. + Tags []string `json:"-"` + + // IsIPv6: only routes with an IPv6 destination will be returned. + IsIPv6 *bool `json:"-"` +} + +// SetSubnetsRequest: set subnets request. +type SetSubnetsRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // PrivateNetworkID: private Network ID. + PrivateNetworkID string `json:"-"` + + // Subnets: private Network subnets CIDR. + Subnets []scw.IPNet `json:"subnets"` +} + +// SetSubnetsResponse: set subnets response. +type SetSubnetsResponse struct { + Subnets []scw.IPNet `json:"subnets"` +} + +// UpdatePrivateNetworkRequest: update private network request. +type UpdatePrivateNetworkRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // PrivateNetworkID: private Network ID. + PrivateNetworkID string `json:"-"` + + // Name: name for the Private Network. + Name *string `json:"name,omitempty"` + + // Tags: tags for the Private Network. + Tags *[]string `json:"tags,omitempty"` +} + +// UpdateVPCRequest: update vpc request. +type UpdateVPCRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // VpcID: vPC ID. + VpcID string `json:"-"` + + // Name: name for the VPC. + Name *string `json:"name,omitempty"` + + // Tags: tags for the VPC. + Tags *[]string `json:"tags,omitempty"` +} + +// VPC API. +type API struct { + client *scw.Client +} + +// NewAPI returns a API object from a Scaleway client. +func NewAPI(client *scw.Client) *API { + return &API{ + client: client, + } +} +func (s *API) Regions() []scw.Region { + return []scw.Region{scw.RegionFrPar, scw.RegionNlAms, scw.RegionPlWaw} +} + +// ListVPCs: List existing VPCs in the specified region. +func (s *API) ListVPCs(req *ListVPCsRequest, opts ...scw.RequestOption) (*ListVPCsResponse, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + defaultPageSize, exist := s.client.GetDefaultPageSize() + if (req.PageSize == nil || *req.PageSize == 0) && exist { + req.PageSize = &defaultPageSize + } + + query := url.Values{} + parameter.AddToQuery(query, "order_by", req.OrderBy) + parameter.AddToQuery(query, "page", req.Page) + parameter.AddToQuery(query, "page_size", req.PageSize) + parameter.AddToQuery(query, "name", req.Name) + parameter.AddToQuery(query, "tags", req.Tags) + parameter.AddToQuery(query, "organization_id", req.OrganizationID) + parameter.AddToQuery(query, "project_id", req.ProjectID) + parameter.AddToQuery(query, "is_default", req.IsDefault) + parameter.AddToQuery(query, "routing_enabled", req.RoutingEnabled) + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/vpcs", + Query: query, + } + + var resp ListVPCsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// CreateVPC: Create a new VPC in the specified region. +func (s *API) CreateVPC(req *CreateVPCRequest, opts ...scw.RequestOption) (*VPC, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + if req.Name == "" { + req.Name = namegenerator.GetRandomName("vpc") + } + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/vpcs", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp VPC + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// GetVPC: Retrieve details of an existing VPC, specified by its VPC ID. +func (s *API) GetVPC(req *GetVPCRequest, opts ...scw.RequestOption) (*VPC, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + if fmt.Sprint(req.VpcID) == "" { + return nil, errors.New("field VpcID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/vpcs/" + fmt.Sprint(req.VpcID) + "", + } + + var resp VPC + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// UpdateVPC: Update parameters including name and tags of the specified VPC. +func (s *API) UpdateVPC(req *UpdateVPCRequest, opts ...scw.RequestOption) (*VPC, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + if fmt.Sprint(req.VpcID) == "" { + return nil, errors.New("field VpcID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/vpcs/" + fmt.Sprint(req.VpcID) + "", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp VPC + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// DeleteVPC: Delete a VPC specified by its VPC ID. +func (s *API) DeleteVPC(req *DeleteVPCRequest, opts ...scw.RequestOption) error { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + if fmt.Sprint(req.Region) == "" { + return errors.New("field Region cannot be empty in request") + } + + if fmt.Sprint(req.VpcID) == "" { + return errors.New("field VpcID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/vpcs/" + fmt.Sprint(req.VpcID) + "", + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +// ListPrivateNetworks: List existing Private Networks in the specified region. By default, the Private Networks returned in the list are ordered by creation date in ascending order, though this can be modified via the order_by field. +func (s *API) ListPrivateNetworks(req *ListPrivateNetworksRequest, opts ...scw.RequestOption) (*ListPrivateNetworksResponse, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + defaultPageSize, exist := s.client.GetDefaultPageSize() + if (req.PageSize == nil || *req.PageSize == 0) && exist { + req.PageSize = &defaultPageSize + } + + query := url.Values{} + parameter.AddToQuery(query, "order_by", req.OrderBy) + parameter.AddToQuery(query, "page", req.Page) + parameter.AddToQuery(query, "page_size", req.PageSize) + parameter.AddToQuery(query, "name", req.Name) + parameter.AddToQuery(query, "tags", req.Tags) + parameter.AddToQuery(query, "organization_id", req.OrganizationID) + parameter.AddToQuery(query, "project_id", req.ProjectID) + parameter.AddToQuery(query, "private_network_ids", req.PrivateNetworkIDs) + parameter.AddToQuery(query, "vpc_id", req.VpcID) + parameter.AddToQuery(query, "dhcp_enabled", req.DHCPEnabled) + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/private-networks", + Query: query, + } + + var resp ListPrivateNetworksResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// CreatePrivateNetwork: Create a new Private Network. Once created, you can attach Scaleway resources which are in the same region. +func (s *API) CreatePrivateNetwork(req *CreatePrivateNetworkRequest, opts ...scw.RequestOption) (*PrivateNetwork, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + if req.Name == "" { + req.Name = namegenerator.GetRandomName("pn") + } + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/private-networks", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp PrivateNetwork + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// GetPrivateNetwork: Retrieve information about an existing Private Network, specified by its Private Network ID. Its full details are returned in the response object. +func (s *API) GetPrivateNetwork(req *GetPrivateNetworkRequest, opts ...scw.RequestOption) (*PrivateNetwork, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + if fmt.Sprint(req.PrivateNetworkID) == "" { + return nil, errors.New("field PrivateNetworkID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/private-networks/" + fmt.Sprint(req.PrivateNetworkID) + "", + } + + var resp PrivateNetwork + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// UpdatePrivateNetwork: Update parameters (such as name or tags) of an existing Private Network, specified by its Private Network ID. +func (s *API) UpdatePrivateNetwork(req *UpdatePrivateNetworkRequest, opts ...scw.RequestOption) (*PrivateNetwork, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + if fmt.Sprint(req.PrivateNetworkID) == "" { + return nil, errors.New("field PrivateNetworkID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/private-networks/" + fmt.Sprint(req.PrivateNetworkID) + "", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp PrivateNetwork + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// DeletePrivateNetwork: Delete an existing Private Network. Note that you must first detach all resources from the network, in order to delete it. +func (s *API) DeletePrivateNetwork(req *DeletePrivateNetworkRequest, opts ...scw.RequestOption) error { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + if fmt.Sprint(req.Region) == "" { + return errors.New("field Region cannot be empty in request") + } + + if fmt.Sprint(req.PrivateNetworkID) == "" { + return errors.New("field PrivateNetworkID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/private-networks/" + fmt.Sprint(req.PrivateNetworkID) + "", + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +// MigrateZonalPrivateNetworks: Transform multiple existing zoned Private Networks (scoped to a single Availability Zone) into regional Private Networks, scoped to an entire region. You can transform one or many Private Networks (specified by their Private Network IDs) within a single Scaleway Organization or Project, with the same call. +func (s *API) MigrateZonalPrivateNetworks(req *MigrateZonalPrivateNetworksRequest, opts ...scw.RequestOption) error { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + defaultOrganizationID, exist := s.client.GetDefaultOrganizationID() + if exist && req.OrganizationID == nil && req.ProjectID == nil { + req.OrganizationID = &defaultOrganizationID + } + + defaultProjectID, exist := s.client.GetDefaultProjectID() + if exist && req.OrganizationID == nil && req.ProjectID == nil { + req.ProjectID = &defaultProjectID + } + + if fmt.Sprint(req.Region) == "" { + return errors.New("field Region cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/private-networks/migrate-zonal", + } + + err = scwReq.SetBody(req) + if err != nil { + return err + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +// EnableDHCP: Enable DHCP managed on an existing Private Network. Note that you will not be able to deactivate it afterwards. +func (s *API) EnableDHCP(req *EnableDHCPRequest, opts ...scw.RequestOption) (*PrivateNetwork, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + if fmt.Sprint(req.PrivateNetworkID) == "" { + return nil, errors.New("field PrivateNetworkID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/private-networks/" + fmt.Sprint(req.PrivateNetworkID) + "/enable-dhcp", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp PrivateNetwork + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// EnableRouting: Enable routing on an existing VPC. Note that you will not be able to deactivate it afterwards. +func (s *API) EnableRouting(req *EnableRoutingRequest, opts ...scw.RequestOption) (*VPC, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + if fmt.Sprint(req.VpcID) == "" { + return nil, errors.New("field VpcID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/vpcs/" + fmt.Sprint(req.VpcID) + "/enable-routing", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp VPC + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// SetSubnets: Set subnets for an existing Private Network. Note that the method is PUT and not PATCH. Any existing subnets will be removed in favor of the new specified set of subnets. +func (s *API) SetSubnets(req *SetSubnetsRequest, opts ...scw.RequestOption) (*SetSubnetsResponse, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + if fmt.Sprint(req.PrivateNetworkID) == "" { + return nil, errors.New("field PrivateNetworkID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PUT", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/private-networks/" + fmt.Sprint(req.PrivateNetworkID) + "/subnets", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp SetSubnetsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// AddSubnets: Add new subnets to an existing Private Network. +func (s *API) AddSubnets(req *AddSubnetsRequest, opts ...scw.RequestOption) (*AddSubnetsResponse, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + if fmt.Sprint(req.PrivateNetworkID) == "" { + return nil, errors.New("field PrivateNetworkID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/private-networks/" + fmt.Sprint(req.PrivateNetworkID) + "/subnets", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp AddSubnetsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// DeleteSubnets: Delete the specified subnets from a Private Network. +func (s *API) DeleteSubnets(req *DeleteSubnetsRequest, opts ...scw.RequestOption) (*DeleteSubnetsResponse, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + if fmt.Sprint(req.PrivateNetworkID) == "" { + return nil, errors.New("field PrivateNetworkID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/private-networks/" + fmt.Sprint(req.PrivateNetworkID) + "/subnets", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp DeleteSubnetsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RoutesWithNexthopAPI struct { + client *scw.Client +} + +// NewRoutesWithNexthopAPI returns a RoutesWithNexthopAPI object from a Scaleway client. +func NewRoutesWithNexthopAPI(client *scw.Client) *RoutesWithNexthopAPI { + return &RoutesWithNexthopAPI{ + client: client, + } +} + +// ListRoutesWithNexthop: Return routes with associated next hop data. +func (s *RoutesWithNexthopAPI) ListRoutesWithNexthop(req *RoutesWithNexthopAPIListRoutesWithNexthopRequest, opts ...scw.RequestOption) (*ListRoutesWithNexthopResponse, error) { + var err error + + if req.Region == "" { + defaultRegion, _ := s.client.GetDefaultRegion() + req.Region = defaultRegion + } + + defaultPageSize, exist := s.client.GetDefaultPageSize() + if (req.PageSize == nil || *req.PageSize == 0) && exist { + req.PageSize = &defaultPageSize + } + + query := url.Values{} + parameter.AddToQuery(query, "order_by", req.OrderBy) + parameter.AddToQuery(query, "page", req.Page) + parameter.AddToQuery(query, "page_size", req.PageSize) + parameter.AddToQuery(query, "vpc_id", req.VpcID) + parameter.AddToQuery(query, "nexthop_resource_id", req.NexthopResourceID) + parameter.AddToQuery(query, "nexthop_private_network_id", req.NexthopPrivateNetworkID) + parameter.AddToQuery(query, "nexthop_resource_type", req.NexthopResourceType) + parameter.AddToQuery(query, "contains", req.Contains) + parameter.AddToQuery(query, "tags", req.Tags) + parameter.AddToQuery(query, "is_ipv6", req.IsIPv6) + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc/v2/regions/" + fmt.Sprint(req.Region) + "/routes-with-nexthop", + Query: query, + } + + var resp ListRoutesWithNexthopResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} diff --git a/vendor/github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1/vpcgw_sdk.go b/vendor/github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1/vpcgw_sdk.go new file mode 100644 index 0000000000000..1e606feb72f1f --- /dev/null +++ b/vendor/github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1/vpcgw_sdk.go @@ -0,0 +1,2830 @@ +// This file was automatically generated. DO NOT EDIT. +// If you have any remark or suggestion do not hesitate to open an issue. + +// Package vpcgw provides methods and message types of the vpcgw v1 API. +package vpcgw + +import ( + "bytes" + "encoding/json" + "fmt" + "net" + "net/http" + "net/url" + "strings" + "time" + + "github.com/scaleway/scaleway-sdk-go/internal/errors" + "github.com/scaleway/scaleway-sdk-go/internal/marshaler" + "github.com/scaleway/scaleway-sdk-go/internal/parameter" + "github.com/scaleway/scaleway-sdk-go/namegenerator" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +// always import dependencies +var ( + _ fmt.Stringer + _ json.Unmarshaler + _ url.URL + _ net.IP + _ http.Header + _ bytes.Reader + _ time.Time + _ = strings.Join + + _ scw.ScalewayRequest + _ marshaler.Duration + _ scw.File + _ = parameter.AddToQuery + _ = namegenerator.GetRandomName +) + +type DHCPEntryType string + +const ( + DHCPEntryTypeUnknown = DHCPEntryType("unknown") + DHCPEntryTypeReservation = DHCPEntryType("reservation") + DHCPEntryTypeLease = DHCPEntryType("lease") +) + +func (enum DHCPEntryType) String() string { + if enum == "" { + // return default value if empty + return "unknown" + } + return string(enum) +} + +func (enum DHCPEntryType) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *DHCPEntryType) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = DHCPEntryType(DHCPEntryType(tmp).String()) + return nil +} + +type GatewayNetworkStatus string + +const ( + GatewayNetworkStatusUnknown = GatewayNetworkStatus("unknown") + GatewayNetworkStatusCreated = GatewayNetworkStatus("created") + GatewayNetworkStatusAttaching = GatewayNetworkStatus("attaching") + GatewayNetworkStatusConfiguring = GatewayNetworkStatus("configuring") + GatewayNetworkStatusReady = GatewayNetworkStatus("ready") + GatewayNetworkStatusDetaching = GatewayNetworkStatus("detaching") + GatewayNetworkStatusDeleted = GatewayNetworkStatus("deleted") +) + +func (enum GatewayNetworkStatus) String() string { + if enum == "" { + // return default value if empty + return "unknown" + } + return string(enum) +} + +func (enum GatewayNetworkStatus) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *GatewayNetworkStatus) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = GatewayNetworkStatus(GatewayNetworkStatus(tmp).String()) + return nil +} + +type GatewayStatus string + +const ( + GatewayStatusUnknown = GatewayStatus("unknown") + GatewayStatusStopped = GatewayStatus("stopped") + GatewayStatusAllocating = GatewayStatus("allocating") + GatewayStatusConfiguring = GatewayStatus("configuring") + GatewayStatusRunning = GatewayStatus("running") + GatewayStatusStopping = GatewayStatus("stopping") + GatewayStatusFailed = GatewayStatus("failed") + GatewayStatusDeleting = GatewayStatus("deleting") + GatewayStatusDeleted = GatewayStatus("deleted") + GatewayStatusLocked = GatewayStatus("locked") +) + +func (enum GatewayStatus) String() string { + if enum == "" { + // return default value if empty + return "unknown" + } + return string(enum) +} + +func (enum GatewayStatus) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *GatewayStatus) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = GatewayStatus(GatewayStatus(tmp).String()) + return nil +} + +type ListDHCPEntriesRequestOrderBy string + +const ( + ListDHCPEntriesRequestOrderByCreatedAtAsc = ListDHCPEntriesRequestOrderBy("created_at_asc") + ListDHCPEntriesRequestOrderByCreatedAtDesc = ListDHCPEntriesRequestOrderBy("created_at_desc") + ListDHCPEntriesRequestOrderByIPAddressAsc = ListDHCPEntriesRequestOrderBy("ip_address_asc") + ListDHCPEntriesRequestOrderByIPAddressDesc = ListDHCPEntriesRequestOrderBy("ip_address_desc") + ListDHCPEntriesRequestOrderByHostnameAsc = ListDHCPEntriesRequestOrderBy("hostname_asc") + ListDHCPEntriesRequestOrderByHostnameDesc = ListDHCPEntriesRequestOrderBy("hostname_desc") +) + +func (enum ListDHCPEntriesRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "created_at_asc" + } + return string(enum) +} + +func (enum ListDHCPEntriesRequestOrderBy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ListDHCPEntriesRequestOrderBy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ListDHCPEntriesRequestOrderBy(ListDHCPEntriesRequestOrderBy(tmp).String()) + return nil +} + +type ListDHCPsRequestOrderBy string + +const ( + ListDHCPsRequestOrderByCreatedAtAsc = ListDHCPsRequestOrderBy("created_at_asc") + ListDHCPsRequestOrderByCreatedAtDesc = ListDHCPsRequestOrderBy("created_at_desc") + ListDHCPsRequestOrderBySubnetAsc = ListDHCPsRequestOrderBy("subnet_asc") + ListDHCPsRequestOrderBySubnetDesc = ListDHCPsRequestOrderBy("subnet_desc") +) + +func (enum ListDHCPsRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "created_at_asc" + } + return string(enum) +} + +func (enum ListDHCPsRequestOrderBy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ListDHCPsRequestOrderBy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ListDHCPsRequestOrderBy(ListDHCPsRequestOrderBy(tmp).String()) + return nil +} + +type ListGatewayNetworksRequestOrderBy string + +const ( + ListGatewayNetworksRequestOrderByCreatedAtAsc = ListGatewayNetworksRequestOrderBy("created_at_asc") + ListGatewayNetworksRequestOrderByCreatedAtDesc = ListGatewayNetworksRequestOrderBy("created_at_desc") + ListGatewayNetworksRequestOrderByStatusAsc = ListGatewayNetworksRequestOrderBy("status_asc") + ListGatewayNetworksRequestOrderByStatusDesc = ListGatewayNetworksRequestOrderBy("status_desc") +) + +func (enum ListGatewayNetworksRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "created_at_asc" + } + return string(enum) +} + +func (enum ListGatewayNetworksRequestOrderBy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ListGatewayNetworksRequestOrderBy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ListGatewayNetworksRequestOrderBy(ListGatewayNetworksRequestOrderBy(tmp).String()) + return nil +} + +type ListGatewaysRequestOrderBy string + +const ( + ListGatewaysRequestOrderByCreatedAtAsc = ListGatewaysRequestOrderBy("created_at_asc") + ListGatewaysRequestOrderByCreatedAtDesc = ListGatewaysRequestOrderBy("created_at_desc") + ListGatewaysRequestOrderByNameAsc = ListGatewaysRequestOrderBy("name_asc") + ListGatewaysRequestOrderByNameDesc = ListGatewaysRequestOrderBy("name_desc") + ListGatewaysRequestOrderByTypeAsc = ListGatewaysRequestOrderBy("type_asc") + ListGatewaysRequestOrderByTypeDesc = ListGatewaysRequestOrderBy("type_desc") + ListGatewaysRequestOrderByStatusAsc = ListGatewaysRequestOrderBy("status_asc") + ListGatewaysRequestOrderByStatusDesc = ListGatewaysRequestOrderBy("status_desc") +) + +func (enum ListGatewaysRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "created_at_asc" + } + return string(enum) +} + +func (enum ListGatewaysRequestOrderBy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ListGatewaysRequestOrderBy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ListGatewaysRequestOrderBy(ListGatewaysRequestOrderBy(tmp).String()) + return nil +} + +type ListIPsRequestOrderBy string + +const ( + ListIPsRequestOrderByCreatedAtAsc = ListIPsRequestOrderBy("created_at_asc") + ListIPsRequestOrderByCreatedAtDesc = ListIPsRequestOrderBy("created_at_desc") + ListIPsRequestOrderByIPAsc = ListIPsRequestOrderBy("ip_asc") + ListIPsRequestOrderByIPDesc = ListIPsRequestOrderBy("ip_desc") + ListIPsRequestOrderByReverseAsc = ListIPsRequestOrderBy("reverse_asc") + ListIPsRequestOrderByReverseDesc = ListIPsRequestOrderBy("reverse_desc") +) + +func (enum ListIPsRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "created_at_asc" + } + return string(enum) +} + +func (enum ListIPsRequestOrderBy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ListIPsRequestOrderBy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ListIPsRequestOrderBy(ListIPsRequestOrderBy(tmp).String()) + return nil +} + +type ListPATRulesRequestOrderBy string + +const ( + ListPATRulesRequestOrderByCreatedAtAsc = ListPATRulesRequestOrderBy("created_at_asc") + ListPATRulesRequestOrderByCreatedAtDesc = ListPATRulesRequestOrderBy("created_at_desc") + ListPATRulesRequestOrderByPublicPortAsc = ListPATRulesRequestOrderBy("public_port_asc") + ListPATRulesRequestOrderByPublicPortDesc = ListPATRulesRequestOrderBy("public_port_desc") +) + +func (enum ListPATRulesRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "created_at_asc" + } + return string(enum) +} + +func (enum ListPATRulesRequestOrderBy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ListPATRulesRequestOrderBy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ListPATRulesRequestOrderBy(ListPATRulesRequestOrderBy(tmp).String()) + return nil +} + +type PATRuleProtocol string + +const ( + PATRuleProtocolUnknown = PATRuleProtocol("unknown") + PATRuleProtocolBoth = PATRuleProtocol("both") + PATRuleProtocolTCP = PATRuleProtocol("tcp") + PATRuleProtocolUDP = PATRuleProtocol("udp") +) + +func (enum PATRuleProtocol) String() string { + if enum == "" { + // return default value if empty + return "unknown" + } + return string(enum) +} + +func (enum PATRuleProtocol) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *PATRuleProtocol) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = PATRuleProtocol(PATRuleProtocol(tmp).String()) + return nil +} + +// DHCP: dhcp. +type DHCP struct { + // ID: ID of the DHCP config. + ID string `json:"id"` + + // OrganizationID: owning Organization. + OrganizationID string `json:"organization_id"` + + // ProjectID: owning Project. + ProjectID string `json:"project_id"` + + // CreatedAt: date the DHCP configuration was created. + CreatedAt *time.Time `json:"created_at"` + + // UpdatedAt: configuration last modification date. + UpdatedAt *time.Time `json:"updated_at"` + + // Subnet: subnet for the DHCP server. + Subnet scw.IPNet `json:"subnet"` + + // Address: IP address of the DHCP server. This will be the Public Gateway's address in the Private Network. It must be part of config's subnet. + Address net.IP `json:"address"` + + // PoolLow: low IP (inclusive) of the dynamic address pool. Must be in the config's subnet. + PoolLow net.IP `json:"pool_low"` + + // PoolHigh: high IP (inclusive) of the dynamic address pool. Must be in the config's subnet. + PoolHigh net.IP `json:"pool_high"` + + // EnableDynamic: defines whether to enable dynamic pooling of IPs. When false, only pre-existing DHCP reservations will be handed out. + EnableDynamic bool `json:"enable_dynamic"` + + // ValidLifetime: how long DHCP entries will be valid for. + ValidLifetime *scw.Duration `json:"valid_lifetime"` + + // RenewTimer: after how long a renew will be attempted. Must be 30s lower than `rebind_timer`. + RenewTimer *scw.Duration `json:"renew_timer"` + + // RebindTimer: after how long a DHCP client will query for a new lease if previous renews fail. Must be 30s lower than `valid_lifetime`. + RebindTimer *scw.Duration `json:"rebind_timer"` + + // PushDefaultRoute: defines whether the gateway should push a default route to DHCP clients, or only hand out IPs. + PushDefaultRoute bool `json:"push_default_route"` + + // PushDNSServer: defines whether the gateway should push custom DNS servers to clients. This allows for instance hostname -> IP resolution. + PushDNSServer bool `json:"push_dns_server"` + + // DNSServersOverride: array of DNS server IP addresses used to override the DNS server list pushed to DHCP clients, instead of the gateway itself. + DNSServersOverride []string `json:"dns_servers_override"` + + // DNSSearch: array of search paths in addition to the pushed DNS configuration. + DNSSearch []string `json:"dns_search"` + + // DNSLocalName: tLD given to hostnames in the Private Networks. If an Instance with hostname `foo` gets a lease, and this is set to `bar`, `foo.bar` will resolve. + DNSLocalName string `json:"dns_local_name"` + + // Zone: zone of this DHCP configuration. + Zone scw.Zone `json:"zone"` +} + +// IpamConfig: ipam config. +type IpamConfig struct { + // PushDefaultRoute: defines whether the default route is enabled on that Gateway Network. + PushDefaultRoute bool `json:"push_default_route"` + + // IpamIPID: iPAM-booked IP ID as the Gateway's IP in this Private Network. + IpamIPID string `json:"ipam_ip_id"` +} + +// GatewayNetwork: gateway network. +type GatewayNetwork struct { + // ID: ID of the Public Gateway-Private Network connection. + ID string `json:"id"` + + // CreatedAt: connection creation date. + CreatedAt *time.Time `json:"created_at"` + + // UpdatedAt: connection last modification date. + UpdatedAt *time.Time `json:"updated_at"` + + // GatewayID: ID of the connected Public Gateway. + GatewayID string `json:"gateway_id"` + + // PrivateNetworkID: ID of the connected Private Network. + PrivateNetworkID string `json:"private_network_id"` + + // MacAddress: mAC address of the gateway in the Private Network (if the gateway is up and running). + MacAddress *string `json:"mac_address"` + + // EnableMasquerade: defines whether the gateway masquerades traffic for this Private Network (Dynamic NAT). + EnableMasquerade bool `json:"enable_masquerade"` + + // Status: current status of the Public Gateway's connection to the Private Network. + // Default value: unknown + Status GatewayNetworkStatus `json:"status"` + + // DHCP: DHCP configuration for the connected Private Network. + DHCP *DHCP `json:"dhcp"` + + // EnableDHCP: defines whether DHCP is enabled on the connected Private Network. + EnableDHCP bool `json:"enable_dhcp"` + + // Address: address of the Gateway (in CIDR form) to use when DHCP is not used. + Address *scw.IPNet `json:"address"` + + // IpamConfig: iPAM IP configuration used. + IpamConfig *IpamConfig `json:"ipam_config"` + + // Zone: zone of the GatewayNetwork connection. + Zone scw.Zone `json:"zone"` +} + +// GatewayType: gateway type. +type GatewayType struct { + // Name: public Gateway type name. + Name string `json:"name"` + + // Bandwidth: bandwidth, in bps, of the Public Gateway. This is the public bandwidth to the outer Internet, and the internal bandwidth to each connected Private Networks. + Bandwidth uint64 `json:"bandwidth"` + + // Zone: zone the Public Gateway type is available in. + Zone scw.Zone `json:"zone"` +} + +// IP: ip. +type IP struct { + // ID: IP address ID. + ID string `json:"id"` + + // OrganizationID: owning Organization. + OrganizationID string `json:"organization_id"` + + // ProjectID: owning Project. + ProjectID string `json:"project_id"` + + // CreatedAt: IP address creation date. + CreatedAt *time.Time `json:"created_at"` + + // UpdatedAt: IP address last modification date. + UpdatedAt *time.Time `json:"updated_at"` + + // Tags: tags associated with the IP address. + Tags []string `json:"tags"` + + // Address: the IP address itself. + Address net.IP `json:"address"` + + // Reverse: reverse domain name for the IP address. + Reverse *string `json:"reverse"` + + // GatewayID: public Gateway associated with the IP address. + GatewayID *string `json:"gateway_id"` + + // Zone: zone of the IP address. + Zone scw.Zone `json:"zone"` +} + +// CreateDHCPRequest: create dhcp request. +type CreateDHCPRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // ProjectID: project to create the DHCP configuration in. + ProjectID string `json:"project_id"` + + // Subnet: subnet for the DHCP server. + Subnet scw.IPNet `json:"subnet"` + + // Address: IP address of the DHCP server. This will be the gateway's address in the Private Network. Defaults to the first address of the subnet. + Address *net.IP `json:"address,omitempty"` + + // PoolLow: low IP (inclusive) of the dynamic address pool. Must be in the config's subnet. Defaults to the second address of the subnet. + PoolLow *net.IP `json:"pool_low,omitempty"` + + // PoolHigh: high IP (inclusive) of the dynamic address pool. Must be in the config's subnet. Defaults to the last address of the subnet. + PoolHigh *net.IP `json:"pool_high,omitempty"` + + // EnableDynamic: defines whether to enable dynamic pooling of IPs. When false, only pre-existing DHCP reservations will be handed out. Defaults to true. + EnableDynamic *bool `json:"enable_dynamic,omitempty"` + + // ValidLifetime: how long DHCP entries will be valid for. Defaults to 1h (3600s). + ValidLifetime *scw.Duration `json:"valid_lifetime,omitempty"` + + // RenewTimer: after how long a renew will be attempted. Must be 30s lower than `rebind_timer`. Defaults to 50m (3000s). + RenewTimer *scw.Duration `json:"renew_timer,omitempty"` + + // RebindTimer: after how long a DHCP client will query for a new lease if previous renews fail. Must be 30s lower than `valid_lifetime`. Defaults to 51m (3060s). + RebindTimer *scw.Duration `json:"rebind_timer,omitempty"` + + // PushDefaultRoute: defines whether the gateway should push a default route to DHCP clients or only hand out IPs. Defaults to true. + PushDefaultRoute *bool `json:"push_default_route,omitempty"` + + // PushDNSServer: defines whether the gateway should push custom DNS servers to clients. This allows for Instance hostname -> IP resolution. Defaults to true. + PushDNSServer *bool `json:"push_dns_server,omitempty"` + + // DNSServersOverride: array of DNS server IP addresses used to override the DNS server list pushed to DHCP clients, instead of the gateway itself. + DNSServersOverride *[]string `json:"dns_servers_override,omitempty"` + + // DNSSearch: array of search paths in addition to the pushed DNS configuration. + DNSSearch *[]string `json:"dns_search,omitempty"` + + // DNSLocalName: tLD given to hostnames in the Private Network. Allowed characters are `a-z0-9-.`. Defaults to the slugified Private Network name if created along a GatewayNetwork, or else to `priv`. + DNSLocalName *string `json:"dns_local_name,omitempty"` +} + +// CreateGatewayNetworkRequestIpamConfig: create gateway network request ipam config. +type CreateGatewayNetworkRequestIpamConfig struct { + // PushDefaultRoute: enabling the default route also enables masquerading. + PushDefaultRoute bool `json:"push_default_route"` + + // IpamIPID: use this IPAM-booked IP ID as the Gateway's IP in this Private Network. + IpamIPID *string `json:"ipam_ip_id"` +} + +// DHCPEntry: dhcp entry. +type DHCPEntry struct { + // ID: DHCP entry ID. + ID string `json:"id"` + + // CreatedAt: DHCP entry creation date. + CreatedAt *time.Time `json:"created_at"` + + // UpdatedAt: DHCP entry last modification date. + UpdatedAt *time.Time `json:"updated_at"` + + // GatewayNetworkID: owning GatewayNetwork. + GatewayNetworkID string `json:"gateway_network_id"` + + // MacAddress: mAC address of the client device. + MacAddress string `json:"mac_address"` + + // IPAddress: assigned IP address. + IPAddress net.IP `json:"ip_address"` + + // Hostname: hostname of the client device. + Hostname string `json:"hostname"` + + // Type: entry type, either static (DHCP reservation) or dynamic (DHCP lease). + // Default value: unknown + Type DHCPEntryType `json:"type"` + + // Zone: zone of this DHCP entry. + Zone scw.Zone `json:"zone"` +} + +// Gateway: gateway. +type Gateway struct { + // ID: ID of the gateway. + ID string `json:"id"` + + // OrganizationID: owning Organization. + OrganizationID string `json:"organization_id"` + + // ProjectID: owning Project. + ProjectID string `json:"project_id"` + + // CreatedAt: gateway creation date. + CreatedAt *time.Time `json:"created_at"` + + // UpdatedAt: gateway last modification date. + UpdatedAt *time.Time `json:"updated_at"` + + // Type: gateway type (commercial offer). + Type *GatewayType `json:"type"` + + // Status: current status of the gateway. + // Default value: unknown + Status GatewayStatus `json:"status"` + + // Name: name of the gateway. + Name string `json:"name"` + + // Tags: tags associated with the gateway. + Tags []string `json:"tags"` + + // IP: public IP address of the gateway. + IP *IP `json:"ip"` + + // GatewayNetworks: gatewayNetwork objects attached to the gateway (each one represents a connection to a Private Network). + GatewayNetworks []*GatewayNetwork `json:"gateway_networks"` + + // UpstreamDNSServers: array of DNS server IP addresses to override the gateway's default recursive DNS servers. + UpstreamDNSServers []string `json:"upstream_dns_servers"` + + // Version: version of the running gateway software. + Version *string `json:"version"` + + // CanUpgradeTo: newly available gateway software version that can be updated to. + CanUpgradeTo *string `json:"can_upgrade_to"` + + // BastionEnabled: defines whether SSH bastion is enabled on the gateway. + BastionEnabled bool `json:"bastion_enabled"` + + // BastionPort: port of the SSH bastion. + BastionPort uint32 `json:"bastion_port"` + + // SMTPEnabled: defines whether SMTP traffic is allowed to pass through the gateway. + SMTPEnabled bool `json:"smtp_enabled"` + + // IsLegacy: defines whether the gateway uses non-IPAM IP configurations. + IsLegacy bool `json:"is_legacy"` + + // IPMobilityEnabled: defines whether the gateway uses routed IPs (IP mobility) instead of NAT IPs. + IPMobilityEnabled bool `json:"ip_mobility_enabled"` + + // Zone: zone of the gateway. + Zone scw.Zone `json:"zone"` +} + +// PATRule: pat rule. +type PATRule struct { + // ID: pAT rule ID. + ID string `json:"id"` + + // GatewayID: gateway the PAT rule applies to. + GatewayID string `json:"gateway_id"` + + // CreatedAt: pAT rule creation date. + CreatedAt *time.Time `json:"created_at"` + + // UpdatedAt: pAT rule last modification date. + UpdatedAt *time.Time `json:"updated_at"` + + // PublicPort: public port to listen on. + PublicPort uint32 `json:"public_port"` + + // PrivateIP: private IP address to forward data to. + PrivateIP net.IP `json:"private_ip"` + + // PrivatePort: private port to translate to. + PrivatePort uint32 `json:"private_port"` + + // Protocol: protocol the rule applies to. + // Default value: unknown + Protocol PATRuleProtocol `json:"protocol"` + + // Zone: zone of the PAT rule. + Zone scw.Zone `json:"zone"` +} + +// SetDHCPEntriesRequestEntry: set dhcp entries request entry. +type SetDHCPEntriesRequestEntry struct { + // MacAddress: mAC address to give a static entry to. A matching entry will be upgraded to a reservation, and a matching reservation will be updated. + MacAddress string `json:"mac_address"` + + // IPAddress: IP address to give to the device. + IPAddress net.IP `json:"ip_address"` +} + +// SetPATRulesRequestRule: set pat rules request rule. +type SetPATRulesRequestRule struct { + // PublicPort: public port to listen on. Uniquely identifies the rule, and a matching rule will be updated with the new parameters. + PublicPort uint32 `json:"public_port"` + + // PrivateIP: private IP to forward data to. + PrivateIP net.IP `json:"private_ip"` + + // PrivatePort: private port to translate to. + PrivatePort uint32 `json:"private_port"` + + // Protocol: protocol the rule should apply to. + // Default value: unknown + Protocol PATRuleProtocol `json:"protocol"` +} + +// UpdateGatewayNetworkRequestIpamConfig: update gateway network request ipam config. +type UpdateGatewayNetworkRequestIpamConfig struct { + // PushDefaultRoute: enabling the default route also enables masquerading. + PushDefaultRoute *bool `json:"push_default_route"` + + // IpamIPID: use this IPAM-booked IP ID as the Gateway's IP in this Private Network. + IpamIPID *string `json:"ipam_ip_id"` +} + +// CreateDHCPEntryRequest: create dhcp entry request. +type CreateDHCPEntryRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayNetworkID: gatewayNetwork on which to create a DHCP reservation. + GatewayNetworkID string `json:"gateway_network_id"` + + // MacAddress: mAC address to give a static entry to. + MacAddress string `json:"mac_address"` + + // IPAddress: IP address to give to the device. + IPAddress net.IP `json:"ip_address"` +} + +// CreateGatewayNetworkRequest: create gateway network request. +type CreateGatewayNetworkRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayID: public Gateway to connect. + GatewayID string `json:"gateway_id"` + + // PrivateNetworkID: private Network to connect. + PrivateNetworkID string `json:"private_network_id"` + + // EnableMasquerade: note: this setting is ignored when passing `ipam_config`. + EnableMasquerade bool `json:"enable_masquerade"` + + // EnableDHCP: defaults to `true` if either `dhcp_id` or `dhcp` are present. If set to `true`, either `dhcp_id` or `dhcp` must be present. + // Note: this setting is ignored when passing `ipam_config`. + EnableDHCP *bool `json:"enable_dhcp,omitempty"` + + // DHCPID: ID of an existing DHCP configuration object to use for this GatewayNetwork. + // Precisely one of DHCPID, DHCP, Address, IpamConfig must be set. + DHCPID *string `json:"dhcp_id,omitempty"` + + // DHCP: new DHCP configuration object to use for this GatewayNetwork. + // Precisely one of DHCPID, DHCP, Address, IpamConfig must be set. + DHCP *CreateDHCPRequest `json:"dhcp,omitempty"` + + // Address: static IP address in CIDR format to to use without DHCP. + // Precisely one of DHCPID, DHCP, Address, IpamConfig must be set. + Address *scw.IPNet `json:"address,omitempty"` + + // IpamConfig: note: all or none of the GatewayNetworks for a single gateway can use the IPAM. DHCP and IPAM configurations cannot be mixed. Some products may require that the Public Gateway uses the IPAM, to ensure correct functionality. + // Precisely one of DHCPID, DHCP, Address, IpamConfig must be set. + IpamConfig *CreateGatewayNetworkRequestIpamConfig `json:"ipam_config,omitempty"` +} + +// CreateGatewayRequest: create gateway request. +type CreateGatewayRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // ProjectID: scaleway Project to create the gateway in. + ProjectID string `json:"project_id"` + + // Name: name for the gateway. + Name string `json:"name"` + + // Tags: tags for the gateway. + Tags []string `json:"tags"` + + // Type: gateway type (commercial offer type). + Type string `json:"type"` + + // UpstreamDNSServers: array of DNS server IP addresses to override the gateway's default recursive DNS servers. + UpstreamDNSServers []string `json:"upstream_dns_servers"` + + // IPID: existing IP address to attach to the gateway. + IPID *string `json:"ip_id,omitempty"` + + // EnableSMTP: defines whether SMTP traffic should be allowed pass through the gateway. + EnableSMTP bool `json:"enable_smtp"` + + // EnableBastion: defines whether SSH bastion should be enabled the gateway. + EnableBastion bool `json:"enable_bastion"` + + // BastionPort: port of the SSH bastion. + BastionPort *uint32 `json:"bastion_port,omitempty"` +} + +// CreateIPRequest: create ip request. +type CreateIPRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // ProjectID: project to create the IP address in. + ProjectID string `json:"project_id"` + + // Tags: tags to give to the IP address. + Tags []string `json:"tags"` +} + +// CreatePATRuleRequest: create pat rule request. +type CreatePATRuleRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayID: ID of the Gateway on which to create the rule. + GatewayID string `json:"gateway_id"` + + // PublicPort: public port to listen on. + PublicPort uint32 `json:"public_port"` + + // PrivateIP: private IP to forward data to. + PrivateIP net.IP `json:"private_ip"` + + // PrivatePort: private port to translate to. + PrivatePort uint32 `json:"private_port"` + + // Protocol: protocol the rule should apply to. + // Default value: unknown + Protocol PATRuleProtocol `json:"protocol"` +} + +// DeleteDHCPEntryRequest: delete dhcp entry request. +type DeleteDHCPEntryRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // DHCPEntryID: ID of the DHCP entry to delete. + DHCPEntryID string `json:"-"` +} + +// DeleteDHCPRequest: delete dhcp request. +type DeleteDHCPRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // DHCPID: DHCP configuration ID to delete. + DHCPID string `json:"-"` +} + +// DeleteGatewayNetworkRequest: delete gateway network request. +type DeleteGatewayNetworkRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayNetworkID: ID of the GatewayNetwork to delete. + GatewayNetworkID string `json:"-"` + + // CleanupDHCP: defines whether to clean up attached DHCP configurations (if any, and if not attached to another Gateway Network). + CleanupDHCP bool `json:"cleanup_dhcp"` +} + +// DeleteGatewayRequest: delete gateway request. +type DeleteGatewayRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayID: ID of the gateway to delete. + GatewayID string `json:"-"` + + // CleanupDHCP: defines whether to clean up attached DHCP configurations (if any, and if not attached to another Gateway Network). + CleanupDHCP bool `json:"cleanup_dhcp"` +} + +// DeleteIPRequest: delete ip request. +type DeleteIPRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // IPID: ID of the IP address to delete. + IPID string `json:"-"` +} + +// DeletePATRuleRequest: delete pat rule request. +type DeletePATRuleRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // PatRuleID: ID of the PAT rule to delete. + PatRuleID string `json:"-"` +} + +// EnableIPMobilityRequest: enable ip mobility request. +type EnableIPMobilityRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayID: ID of the gateway to upgrade to IP mobility. + GatewayID string `json:"-"` +} + +// GetDHCPEntryRequest: get dhcp entry request. +type GetDHCPEntryRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // DHCPEntryID: ID of the DHCP entry to fetch. + DHCPEntryID string `json:"-"` +} + +// GetDHCPRequest: get dhcp request. +type GetDHCPRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // DHCPID: ID of the DHCP configuration to fetch. + DHCPID string `json:"-"` +} + +// GetGatewayNetworkRequest: get gateway network request. +type GetGatewayNetworkRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayNetworkID: ID of the GatewayNetwork to fetch. + GatewayNetworkID string `json:"-"` +} + +// GetGatewayRequest: get gateway request. +type GetGatewayRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayID: ID of the gateway to fetch. + GatewayID string `json:"-"` +} + +// GetIPRequest: get ip request. +type GetIPRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // IPID: ID of the IP address to get. + IPID string `json:"-"` +} + +// GetPATRuleRequest: get pat rule request. +type GetPATRuleRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // PatRuleID: ID of the PAT rule to get. + PatRuleID string `json:"-"` +} + +// ListDHCPEntriesRequest: list dhcp entries request. +type ListDHCPEntriesRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // OrderBy: order in which to return results. + // Default value: created_at_asc + OrderBy ListDHCPEntriesRequestOrderBy `json:"-"` + + // Page: page number. + Page *int32 `json:"-"` + + // PageSize: DHCP entries per page. + PageSize *uint32 `json:"-"` + + // GatewayNetworkID: filter for entries on this GatewayNetwork. + GatewayNetworkID *string `json:"-"` + + // MacAddress: filter for entries with this MAC address. + MacAddress *string `json:"-"` + + // IPAddress: filter for entries with this IP address. + IPAddress *net.IP `json:"-"` + + // Hostname: filter for entries with this hostname substring. + Hostname *string `json:"-"` + + // Type: filter for entries of this type. + // Default value: unknown + Type DHCPEntryType `json:"-"` +} + +// ListDHCPEntriesResponse: list dhcp entries response. +type ListDHCPEntriesResponse struct { + // DHCPEntries: DHCP entries in this page. + DHCPEntries []*DHCPEntry `json:"dhcp_entries"` + + // TotalCount: total count of DHCP entries matching the filter. + TotalCount uint32 `json:"total_count"` +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListDHCPEntriesResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListDHCPEntriesResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListDHCPEntriesResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.DHCPEntries = append(r.DHCPEntries, results.DHCPEntries...) + r.TotalCount += uint32(len(results.DHCPEntries)) + return uint32(len(results.DHCPEntries)), nil +} + +// ListDHCPsRequest: list dhc ps request. +type ListDHCPsRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // OrderBy: order in which to return results. + // Default value: created_at_asc + OrderBy ListDHCPsRequestOrderBy `json:"-"` + + // Page: page number. + Page *int32 `json:"-"` + + // PageSize: DHCP configurations per page. + PageSize *uint32 `json:"-"` + + // OrganizationID: include only DHCP configuration objects in this Organization. + OrganizationID *string `json:"-"` + + // ProjectID: include only DHCP configuration objects in this Project. + ProjectID *string `json:"-"` + + // Address: filter for DHCP configuration objects with this DHCP server IP address (the gateway's address in the Private Network). + Address *net.IP `json:"-"` + + // HasAddress: filter for DHCP configuration objects with subnets containing this IP address. + HasAddress *net.IP `json:"-"` +} + +// ListDHCPsResponse: list dhc ps response. +type ListDHCPsResponse struct { + // Dhcps: first page of DHCP configuration objects. + Dhcps []*DHCP `json:"dhcps"` + + // TotalCount: total count of DHCP configuration objects matching the filter. + TotalCount uint32 `json:"total_count"` +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListDHCPsResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListDHCPsResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListDHCPsResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.Dhcps = append(r.Dhcps, results.Dhcps...) + r.TotalCount += uint32(len(results.Dhcps)) + return uint32(len(results.Dhcps)), nil +} + +// ListGatewayNetworksRequest: list gateway networks request. +type ListGatewayNetworksRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // OrderBy: order in which to return results. + // Default value: created_at_asc + OrderBy ListGatewayNetworksRequestOrderBy `json:"-"` + + // Page: page number. + Page *int32 `json:"-"` + + // PageSize: gatewayNetworks per page. + PageSize *uint32 `json:"-"` + + // GatewayID: filter for GatewayNetworks connected to this gateway. + GatewayID *string `json:"-"` + + // PrivateNetworkID: filter for GatewayNetworks connected to this Private Network. + PrivateNetworkID *string `json:"-"` + + // EnableMasquerade: filter for GatewayNetworks with this `enable_masquerade` setting. + EnableMasquerade *bool `json:"-"` + + // DHCPID: filter for GatewayNetworks using this DHCP configuration. + DHCPID *string `json:"-"` + + // Status: filter for GatewayNetworks with this current status this status. Use `unknown` to include all statuses. + // Default value: unknown + Status GatewayNetworkStatus `json:"-"` +} + +// ListGatewayNetworksResponse: list gateway networks response. +type ListGatewayNetworksResponse struct { + // GatewayNetworks: gatewayNetworks on this page. + GatewayNetworks []*GatewayNetwork `json:"gateway_networks"` + + // TotalCount: total GatewayNetworks count matching the filter. + TotalCount uint32 `json:"total_count"` +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListGatewayNetworksResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListGatewayNetworksResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListGatewayNetworksResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.GatewayNetworks = append(r.GatewayNetworks, results.GatewayNetworks...) + r.TotalCount += uint32(len(results.GatewayNetworks)) + return uint32(len(results.GatewayNetworks)), nil +} + +// ListGatewayTypesRequest: list gateway types request. +type ListGatewayTypesRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` +} + +// ListGatewayTypesResponse: list gateway types response. +type ListGatewayTypesResponse struct { + // Types: available types of Public Gateway. + Types []*GatewayType `json:"types"` +} + +// ListGatewaysRequest: list gateways request. +type ListGatewaysRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // OrderBy: order in which to return results. + // Default value: created_at_asc + OrderBy ListGatewaysRequestOrderBy `json:"-"` + + // Page: page number to return. + Page *int32 `json:"-"` + + // PageSize: gateways per page. + PageSize *uint32 `json:"-"` + + // OrganizationID: include only gateways in this Organization. + OrganizationID *string `json:"-"` + + // ProjectID: include only gateways in this Project. + ProjectID *string `json:"-"` + + // Name: filter for gateways which have this search term in their name. + Name *string `json:"-"` + + // Tags: filter for gateways with these tags. + Tags []string `json:"-"` + + // Type: filter for gateways of this type. + Type *string `json:"-"` + + // Status: filter for gateways with this current status. Use `unknown` to include all statuses. + // Default value: unknown + Status GatewayStatus `json:"-"` + + // PrivateNetworkID: filter for gateways attached to this Private nNetwork. + PrivateNetworkID *string `json:"-"` +} + +// ListGatewaysResponse: list gateways response. +type ListGatewaysResponse struct { + // Gateways: gateways on this page. + Gateways []*Gateway `json:"gateways"` + + // TotalCount: total count of gateways matching the filter. + TotalCount uint32 `json:"total_count"` +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListGatewaysResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListGatewaysResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListGatewaysResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.Gateways = append(r.Gateways, results.Gateways...) + r.TotalCount += uint32(len(results.Gateways)) + return uint32(len(results.Gateways)), nil +} + +// ListIPsRequest: list i ps request. +type ListIPsRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // OrderBy: order in which to return results. + // Default value: created_at_asc + OrderBy ListIPsRequestOrderBy `json:"-"` + + // Page: page number. + Page *int32 `json:"-"` + + // PageSize: IP addresses per page. + PageSize *uint32 `json:"-"` + + // OrganizationID: filter for IP addresses in this Organization. + OrganizationID *string `json:"-"` + + // ProjectID: filter for IP addresses in this Project. + ProjectID *string `json:"-"` + + // Tags: filter for IP addresses with these tags. + Tags []string `json:"-"` + + // Reverse: filter for IP addresses that have a reverse containing this string. + Reverse *string `json:"-"` + + // IsFree: filter based on whether the IP is attached to a gateway or not. + IsFree *bool `json:"-"` +} + +// ListIPsResponse: list i ps response. +type ListIPsResponse struct { + // IPs: IP addresses on this page. + IPs []*IP `json:"ips"` + + // TotalCount: total count of IP addresses matching the filter. + TotalCount uint32 `json:"total_count"` +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListIPsResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListIPsResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListIPsResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.IPs = append(r.IPs, results.IPs...) + r.TotalCount += uint32(len(results.IPs)) + return uint32(len(results.IPs)), nil +} + +// ListPATRulesRequest: list pat rules request. +type ListPATRulesRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // OrderBy: order in which to return results. + // Default value: created_at_asc + OrderBy ListPATRulesRequestOrderBy `json:"-"` + + // Page: page number. + Page *int32 `json:"-"` + + // PageSize: pAT rules per page. + PageSize *uint32 `json:"-"` + + // GatewayID: filter for PAT rules on this Gateway. + GatewayID *string `json:"-"` + + // PrivateIP: filter for PAT rules targeting this private ip. + PrivateIP *net.IP `json:"-"` + + // Protocol: filter for PAT rules with this protocol. + // Default value: unknown + Protocol PATRuleProtocol `json:"-"` +} + +// ListPATRulesResponse: list pat rules response. +type ListPATRulesResponse struct { + // PatRules: array of PAT rules matching the filter. + PatRules []*PATRule `json:"pat_rules"` + + // TotalCount: total count of PAT rules matching the filter. + TotalCount uint32 `json:"total_count"` +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListPATRulesResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListPATRulesResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListPATRulesResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.PatRules = append(r.PatRules, results.PatRules...) + r.TotalCount += uint32(len(results.PatRules)) + return uint32(len(results.PatRules)), nil +} + +// RefreshSSHKeysRequest: refresh ssh keys request. +type RefreshSSHKeysRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayID: ID of the gateway to refresh SSH keys on. + GatewayID string `json:"-"` +} + +// SetDHCPEntriesRequest: set dhcp entries request. +type SetDHCPEntriesRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayNetworkID: ID of the Gateway Network on which to set DHCP reservation list. + GatewayNetworkID string `json:"gateway_network_id"` + + // DHCPEntries: new list of DHCP reservations. + DHCPEntries []*SetDHCPEntriesRequestEntry `json:"dhcp_entries"` +} + +// SetDHCPEntriesResponse: set dhcp entries response. +type SetDHCPEntriesResponse struct { + // DHCPEntries: list of DHCP entries. + DHCPEntries []*DHCPEntry `json:"dhcp_entries"` +} + +// SetPATRulesRequest: set pat rules request. +type SetPATRulesRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayID: ID of the gateway on which to set the PAT rules. + GatewayID string `json:"gateway_id"` + + // PatRules: new list of PAT rules. + PatRules []*SetPATRulesRequestRule `json:"pat_rules"` +} + +// SetPATRulesResponse: set pat rules response. +type SetPATRulesResponse struct { + // PatRules: list of PAT rules. + PatRules []*PATRule `json:"pat_rules"` +} + +// UpdateDHCPEntryRequest: update dhcp entry request. +type UpdateDHCPEntryRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // DHCPEntryID: ID of the DHCP entry to update. + DHCPEntryID string `json:"-"` + + // IPAddress: new IP address to give to the device. + IPAddress *net.IP `json:"ip_address,omitempty"` +} + +// UpdateDHCPRequest: update dhcp request. +type UpdateDHCPRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // DHCPID: DHCP configuration to update. + DHCPID string `json:"-"` + + // Subnet: subnet for the DHCP server. + Subnet *scw.IPNet `json:"subnet,omitempty"` + + // Address: IP address of the DHCP server. This will be the Public Gateway's address in the Private Network. It must be part of config's subnet. + Address *net.IP `json:"address,omitempty"` + + // PoolLow: low IP (inclusive) of the dynamic address pool. Must be in the config's subnet. + PoolLow *net.IP `json:"pool_low,omitempty"` + + // PoolHigh: high IP (inclusive) of the dynamic address pool. Must be in the config's subnet. + PoolHigh *net.IP `json:"pool_high,omitempty"` + + // EnableDynamic: defines whether to enable dynamic pooling of IPs. When false, only pre-existing DHCP reservations will be handed out. Defaults to true. + EnableDynamic *bool `json:"enable_dynamic,omitempty"` + + // ValidLifetime: how long DHCP entries will be valid for. + ValidLifetime *scw.Duration `json:"valid_lifetime,omitempty"` + + // RenewTimer: after how long a renew will be attempted. Must be 30s lower than `rebind_timer`. + RenewTimer *scw.Duration `json:"renew_timer,omitempty"` + + // RebindTimer: after how long a DHCP client will query for a new lease if previous renews fail. Must be 30s lower than `valid_lifetime`. + RebindTimer *scw.Duration `json:"rebind_timer,omitempty"` + + // PushDefaultRoute: defines whether the gateway should push a default route to DHCP clients, or only hand out IPs. + PushDefaultRoute *bool `json:"push_default_route,omitempty"` + + // PushDNSServer: defines whether the gateway should push custom DNS servers to clients. This allows for instance hostname -> IP resolution. + PushDNSServer *bool `json:"push_dns_server,omitempty"` + + // DNSServersOverride: array of DNS server IP addresses used to override the DNS server list pushed to DHCP clients, instead of the gateway itself. + DNSServersOverride *[]string `json:"dns_servers_override,omitempty"` + + // DNSSearch: array of search paths in addition to the pushed DNS configuration. + DNSSearch *[]string `json:"dns_search,omitempty"` + + // DNSLocalName: tLD given to hostnames in the Private Networks. If an instance with hostname `foo` gets a lease, and this is set to `bar`, `foo.bar` will resolve. Allowed characters are `a-z0-9-.`. + DNSLocalName *string `json:"dns_local_name,omitempty"` +} + +// UpdateGatewayNetworkRequest: update gateway network request. +type UpdateGatewayNetworkRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayNetworkID: ID of the GatewayNetwork to update. + GatewayNetworkID string `json:"-"` + + // EnableMasquerade: note: this setting is ignored when passing `ipam_config`. + EnableMasquerade *bool `json:"enable_masquerade,omitempty"` + + // EnableDHCP: defaults to `true` if `dhcp_id` is present. If set to `true`, `dhcp_id` must be present. + // Note: this setting is ignored when passing `ipam_config`. + EnableDHCP *bool `json:"enable_dhcp,omitempty"` + + // DHCPID: ID of the new DHCP configuration object to use with this GatewayNetwork. + // Precisely one of DHCPID, Address, IpamConfig must be set. + DHCPID *string `json:"dhcp_id,omitempty"` + + // Address: new static IP address. + // Precisely one of DHCPID, Address, IpamConfig must be set. + Address *scw.IPNet `json:"address,omitempty"` + + // IpamConfig: note: all or none of the GatewayNetworks for a single gateway can use the IPAM. DHCP and IPAM configurations cannot be mixed. Some products may require that the Public Gateway uses the IPAM, to ensure correct functionality. + // Precisely one of DHCPID, Address, IpamConfig must be set. + IpamConfig *UpdateGatewayNetworkRequestIpamConfig `json:"ipam_config,omitempty"` +} + +// UpdateGatewayRequest: update gateway request. +type UpdateGatewayRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayID: ID of the gateway to update. + GatewayID string `json:"-"` + + // Name: name for the gateway. + Name *string `json:"name,omitempty"` + + // Tags: tags for the gateway. + Tags *[]string `json:"tags,omitempty"` + + // UpstreamDNSServers: array of DNS server IP addresses to override the gateway's default recursive DNS servers. + UpstreamDNSServers *[]string `json:"upstream_dns_servers,omitempty"` + + // EnableBastion: defines whether SSH bastion should be enabled the gateway. + EnableBastion *bool `json:"enable_bastion,omitempty"` + + // BastionPort: port of the SSH bastion. + BastionPort *uint32 `json:"bastion_port,omitempty"` + + // EnableSMTP: defines whether SMTP traffic should be allowed to pass through the gateway. + EnableSMTP *bool `json:"enable_smtp,omitempty"` +} + +// UpdateIPRequest: update ip request. +type UpdateIPRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // IPID: ID of the IP address to update. + IPID string `json:"-"` + + // Tags: tags to give to the IP address. + Tags *[]string `json:"tags,omitempty"` + + // Reverse: reverse to set on the address. Empty string to unset. + Reverse *string `json:"reverse,omitempty"` + + // GatewayID: gateway to attach the IP address to. Empty string to detach. + GatewayID *string `json:"gateway_id,omitempty"` +} + +// UpdatePATRuleRequest: update pat rule request. +type UpdatePATRuleRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // PatRuleID: ID of the PAT rule to update. + PatRuleID string `json:"-"` + + // PublicPort: public port to listen on. + PublicPort *uint32 `json:"public_port,omitempty"` + + // PrivateIP: private IP to forward data to. + PrivateIP *net.IP `json:"private_ip,omitempty"` + + // PrivatePort: private port to translate to. + PrivatePort *uint32 `json:"private_port,omitempty"` + + // Protocol: protocol the rule should apply to. + // Default value: unknown + Protocol PATRuleProtocol `json:"protocol"` +} + +// UpgradeGatewayRequest: upgrade gateway request. +type UpgradeGatewayRequest struct { + // Zone: zone to target. If none is passed will use default zone from the config. + Zone scw.Zone `json:"-"` + + // GatewayID: ID of the gateway to upgrade. + GatewayID string `json:"-"` +} + +// Public Gateways API. +type API struct { + client *scw.Client +} + +// NewAPI returns a API object from a Scaleway client. +func NewAPI(client *scw.Client) *API { + return &API{ + client: client, + } +} +func (s *API) Zones() []scw.Zone { + return []scw.Zone{scw.ZoneFrPar1, scw.ZoneFrPar2, scw.ZoneNlAms1, scw.ZoneNlAms2, scw.ZoneNlAms3, scw.ZonePlWaw1, scw.ZonePlWaw2, scw.ZonePlWaw3} +} + +// ListGateways: List Public Gateways in a given Scaleway Organization or Project. By default, results are displayed in ascending order of creation date. +func (s *API) ListGateways(req *ListGatewaysRequest, opts ...scw.RequestOption) (*ListGatewaysResponse, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + defaultPageSize, exist := s.client.GetDefaultPageSize() + if (req.PageSize == nil || *req.PageSize == 0) && exist { + req.PageSize = &defaultPageSize + } + + query := url.Values{} + parameter.AddToQuery(query, "order_by", req.OrderBy) + parameter.AddToQuery(query, "page", req.Page) + parameter.AddToQuery(query, "page_size", req.PageSize) + parameter.AddToQuery(query, "organization_id", req.OrganizationID) + parameter.AddToQuery(query, "project_id", req.ProjectID) + parameter.AddToQuery(query, "name", req.Name) + parameter.AddToQuery(query, "tags", req.Tags) + parameter.AddToQuery(query, "type", req.Type) + parameter.AddToQuery(query, "status", req.Status) + parameter.AddToQuery(query, "private_network_id", req.PrivateNetworkID) + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateways", + Query: query, + } + + var resp ListGatewaysResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// GetGateway: Get details of a Public Gateway, specified by its gateway ID. The response object contains full details of the gateway, including its **name**, **type**, **status** and more. +func (s *API) GetGateway(req *GetGatewayRequest, opts ...scw.RequestOption) (*Gateway, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.GatewayID) == "" { + return nil, errors.New("field GatewayID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateways/" + fmt.Sprint(req.GatewayID) + "", + } + + var resp Gateway + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// CreateGateway: Create a new Public Gateway in the specified Scaleway Project, defining its **name**, **type** and other configuration details such as whether to enable SSH bastion. +func (s *API) CreateGateway(req *CreateGatewayRequest, opts ...scw.RequestOption) (*Gateway, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + if req.Name == "" { + req.Name = namegenerator.GetRandomName("gw") + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateways", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Gateway + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// UpdateGateway: Update the parameters of an existing Public Gateway, for example, its **name**, **tags**, **SSH bastion configuration**, and **DNS servers**. +func (s *API) UpdateGateway(req *UpdateGatewayRequest, opts ...scw.RequestOption) (*Gateway, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.GatewayID) == "" { + return nil, errors.New("field GatewayID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateways/" + fmt.Sprint(req.GatewayID) + "", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Gateway + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// DeleteGateway: Delete an existing Public Gateway, specified by its gateway ID. This action is irreversible. +func (s *API) DeleteGateway(req *DeleteGatewayRequest, opts ...scw.RequestOption) error { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + query := url.Values{} + parameter.AddToQuery(query, "cleanup_dhcp", req.CleanupDHCP) + + if fmt.Sprint(req.Zone) == "" { + return errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.GatewayID) == "" { + return errors.New("field GatewayID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateways/" + fmt.Sprint(req.GatewayID) + "", + Query: query, + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +// UpgradeGateway: Upgrade a given Public Gateway to the newest software version. This applies the latest bugfixes and features to your Public Gateway, but its service will be interrupted during the update. +func (s *API) UpgradeGateway(req *UpgradeGatewayRequest, opts ...scw.RequestOption) (*Gateway, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.GatewayID) == "" { + return nil, errors.New("field GatewayID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateways/" + fmt.Sprint(req.GatewayID) + "/upgrade", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Gateway + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// EnableIPMobility: Upgrade a Public Gateway to IP mobility (move from NAT IP to routed IP). This is idempotent: repeated calls after the first will return no error but have no effect. +func (s *API) EnableIPMobility(req *EnableIPMobilityRequest, opts ...scw.RequestOption) error { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.GatewayID) == "" { + return errors.New("field GatewayID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateways/" + fmt.Sprint(req.GatewayID) + "/enable-ip-mobility", + } + + err = scwReq.SetBody(req) + if err != nil { + return err + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +// ListGatewayNetworks: List the connections between Public Gateways and Private Networks (a connection = a GatewayNetwork). You can choose to filter by `gateway-id` to list all Private Networks attached to the specified Public Gateway, or by `private_network_id` to list all Public Gateways attached to the specified Private Network. Other query parameters are also available. The result is an array of GatewayNetwork objects, each giving details of the connection between a given Public Gateway and a given Private Network. +func (s *API) ListGatewayNetworks(req *ListGatewayNetworksRequest, opts ...scw.RequestOption) (*ListGatewayNetworksResponse, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + defaultPageSize, exist := s.client.GetDefaultPageSize() + if (req.PageSize == nil || *req.PageSize == 0) && exist { + req.PageSize = &defaultPageSize + } + + query := url.Values{} + parameter.AddToQuery(query, "order_by", req.OrderBy) + parameter.AddToQuery(query, "page", req.Page) + parameter.AddToQuery(query, "page_size", req.PageSize) + parameter.AddToQuery(query, "gateway_id", req.GatewayID) + parameter.AddToQuery(query, "private_network_id", req.PrivateNetworkID) + parameter.AddToQuery(query, "enable_masquerade", req.EnableMasquerade) + parameter.AddToQuery(query, "dhcp_id", req.DHCPID) + parameter.AddToQuery(query, "status", req.Status) + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateway-networks", + Query: query, + } + + var resp ListGatewayNetworksResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// GetGatewayNetwork: Get details of a given connection between a Public Gateway and a Private Network (this connection = a GatewayNetwork), specified by its `gateway_network_id`. The response object contains details of the connection including the IDs of the Public Gateway and Private Network, the dates the connection was created/updated and its configuration settings. +func (s *API) GetGatewayNetwork(req *GetGatewayNetworkRequest, opts ...scw.RequestOption) (*GatewayNetwork, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.GatewayNetworkID) == "" { + return nil, errors.New("field GatewayNetworkID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateway-networks/" + fmt.Sprint(req.GatewayNetworkID) + "", + } + + var resp GatewayNetwork + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// CreateGatewayNetwork: Attach a specific Public Gateway to a specific Private Network (create a GatewayNetwork). You can configure parameters for the connection including DHCP settings, whether to enable masquerade (dynamic NAT), and more. +func (s *API) CreateGatewayNetwork(req *CreateGatewayNetworkRequest, opts ...scw.RequestOption) (*GatewayNetwork, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateway-networks", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp GatewayNetwork + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// UpdateGatewayNetwork: Update the configuration parameters of a connection between a given Public Gateway and Private Network (the connection = a GatewayNetwork). Updatable parameters include DHCP settings and whether to enable traffic masquerade (dynamic NAT). +func (s *API) UpdateGatewayNetwork(req *UpdateGatewayNetworkRequest, opts ...scw.RequestOption) (*GatewayNetwork, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.GatewayNetworkID) == "" { + return nil, errors.New("field GatewayNetworkID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateway-networks/" + fmt.Sprint(req.GatewayNetworkID) + "", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp GatewayNetwork + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// DeleteGatewayNetwork: Detach a given Public Gateway from a given Private Network, i.e. delete a GatewayNetwork specified by a gateway_network_id. +func (s *API) DeleteGatewayNetwork(req *DeleteGatewayNetworkRequest, opts ...scw.RequestOption) error { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + query := url.Values{} + parameter.AddToQuery(query, "cleanup_dhcp", req.CleanupDHCP) + + if fmt.Sprint(req.Zone) == "" { + return errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.GatewayNetworkID) == "" { + return errors.New("field GatewayNetworkID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateway-networks/" + fmt.Sprint(req.GatewayNetworkID) + "", + Query: query, + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +// ListDHCPs: List DHCP configurations, optionally filtering by Organization, Project, Public Gateway IP address or more. The response is an array of DHCP configuration objects, each identified by a DHCP ID and containing configuration settings for the assignment of IP addresses to devices on a Private Network attached to a Public Gateway. Note that the response does not contain the IDs of any Private Network / Public Gateway the configuration is attached to. Use the `List Public Gateway connections to Private Networks` method for that purpose, filtering on DHCP ID. +func (s *API) ListDHCPs(req *ListDHCPsRequest, opts ...scw.RequestOption) (*ListDHCPsResponse, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + defaultPageSize, exist := s.client.GetDefaultPageSize() + if (req.PageSize == nil || *req.PageSize == 0) && exist { + req.PageSize = &defaultPageSize + } + + query := url.Values{} + parameter.AddToQuery(query, "order_by", req.OrderBy) + parameter.AddToQuery(query, "page", req.Page) + parameter.AddToQuery(query, "page_size", req.PageSize) + parameter.AddToQuery(query, "organization_id", req.OrganizationID) + parameter.AddToQuery(query, "project_id", req.ProjectID) + parameter.AddToQuery(query, "address", req.Address) + parameter.AddToQuery(query, "has_address", req.HasAddress) + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/dhcps", + Query: query, + } + + var resp ListDHCPsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// GetDHCP: Get a DHCP configuration object, identified by its DHCP ID. The response object contains configuration settings for the assignment of IP addresses to devices on a Private Network attached to a Public Gateway. Note that the response does not contain the IDs of any Private Network / Public Gateway the configuration is attached to. Use the `List Public Gateway connections to Private Networks` method for that purpose, filtering on DHCP ID. +func (s *API) GetDHCP(req *GetDHCPRequest, opts ...scw.RequestOption) (*DHCP, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.DHCPID) == "" { + return nil, errors.New("field DHCPID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/dhcps/" + fmt.Sprint(req.DHCPID) + "", + } + + var resp DHCP + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// CreateDHCP: Create a new DHCP configuration object, containing settings for the assignment of IP addresses to devices on a Private Network attached to a Public Gateway. The response object includes the ID of the DHCP configuration object. You can use this ID as part of a call to `Create a Public Gateway connection to a Private Network` or `Update a Public Gateway connection to a Private Network` to directly apply this DHCP configuration. +func (s *API) CreateDHCP(req *CreateDHCPRequest, opts ...scw.RequestOption) (*DHCP, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/dhcps", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp DHCP + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// UpdateDHCP: Update a DHCP configuration object, identified by its DHCP ID. +func (s *API) UpdateDHCP(req *UpdateDHCPRequest, opts ...scw.RequestOption) (*DHCP, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.DHCPID) == "" { + return nil, errors.New("field DHCPID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/dhcps/" + fmt.Sprint(req.DHCPID) + "", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp DHCP + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// DeleteDHCP: Delete a DHCP configuration object, identified by its DHCP ID. Note that you cannot delete a DHCP configuration object that is currently being used by a Gateway Network. +func (s *API) DeleteDHCP(req *DeleteDHCPRequest, opts ...scw.RequestOption) error { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.DHCPID) == "" { + return errors.New("field DHCPID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/dhcps/" + fmt.Sprint(req.DHCPID) + "", + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +// ListDHCPEntries: List DHCP entries, whether dynamically assigned and/or statically reserved. DHCP entries can be filtered by the Gateway Network they are on, their MAC address, IP address, type or hostname. +func (s *API) ListDHCPEntries(req *ListDHCPEntriesRequest, opts ...scw.RequestOption) (*ListDHCPEntriesResponse, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + defaultPageSize, exist := s.client.GetDefaultPageSize() + if (req.PageSize == nil || *req.PageSize == 0) && exist { + req.PageSize = &defaultPageSize + } + + query := url.Values{} + parameter.AddToQuery(query, "order_by", req.OrderBy) + parameter.AddToQuery(query, "page", req.Page) + parameter.AddToQuery(query, "page_size", req.PageSize) + parameter.AddToQuery(query, "gateway_network_id", req.GatewayNetworkID) + parameter.AddToQuery(query, "mac_address", req.MacAddress) + parameter.AddToQuery(query, "ip_address", req.IPAddress) + parameter.AddToQuery(query, "hostname", req.Hostname) + parameter.AddToQuery(query, "type", req.Type) + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/dhcp-entries", + Query: query, + } + + var resp ListDHCPEntriesResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// GetDHCPEntry: Get a DHCP entry, specified by its DHCP entry ID. +func (s *API) GetDHCPEntry(req *GetDHCPEntryRequest, opts ...scw.RequestOption) (*DHCPEntry, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.DHCPEntryID) == "" { + return nil, errors.New("field DHCPEntryID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/dhcp-entries/" + fmt.Sprint(req.DHCPEntryID) + "", + } + + var resp DHCPEntry + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// CreateDHCPEntry: Create a static DHCP reservation, specifying the Gateway Network for the reservation, the MAC address of the target device and the IP address to assign this device. The response is a DHCP entry object, confirming the ID and configuration details of the static DHCP reservation. +func (s *API) CreateDHCPEntry(req *CreateDHCPEntryRequest, opts ...scw.RequestOption) (*DHCPEntry, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/dhcp-entries", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp DHCPEntry + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// UpdateDHCPEntry: Update the IP address for a DHCP entry, specified by its DHCP entry ID. You can update an existing DHCP entry of any type (`reservation` (static), `lease` (dynamic) or `unknown`), but in manually updating the IP address the entry will necessarily be of type `reservation` after the update. +func (s *API) UpdateDHCPEntry(req *UpdateDHCPEntryRequest, opts ...scw.RequestOption) (*DHCPEntry, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.DHCPEntryID) == "" { + return nil, errors.New("field DHCPEntryID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/dhcp-entries/" + fmt.Sprint(req.DHCPEntryID) + "", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp DHCPEntry + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// SetDHCPEntries: Set the list of DHCP reservations attached to a Gateway Network. Reservations are identified by their MAC address, and will sync the current DHCP entry list to the given list, creating, updating or deleting DHCP entries accordingly. +func (s *API) SetDHCPEntries(req *SetDHCPEntriesRequest, opts ...scw.RequestOption) (*SetDHCPEntriesResponse, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PUT", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/dhcp-entries", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp SetDHCPEntriesResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// DeleteDHCPEntry: Delete a static DHCP reservation, identified by its DHCP entry ID. Note that you cannot delete DHCP entries of type `lease`, these are deleted automatically when their time-to-live expires. +func (s *API) DeleteDHCPEntry(req *DeleteDHCPEntryRequest, opts ...scw.RequestOption) error { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.DHCPEntryID) == "" { + return errors.New("field DHCPEntryID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/dhcp-entries/" + fmt.Sprint(req.DHCPEntryID) + "", + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +// ListPATRules: List PAT rules. You can filter by gateway ID to list all PAT rules for a particular gateway, or filter for PAT rules targeting a specific IP address or using a specific protocol. +func (s *API) ListPATRules(req *ListPATRulesRequest, opts ...scw.RequestOption) (*ListPATRulesResponse, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + defaultPageSize, exist := s.client.GetDefaultPageSize() + if (req.PageSize == nil || *req.PageSize == 0) && exist { + req.PageSize = &defaultPageSize + } + + query := url.Values{} + parameter.AddToQuery(query, "order_by", req.OrderBy) + parameter.AddToQuery(query, "page", req.Page) + parameter.AddToQuery(query, "page_size", req.PageSize) + parameter.AddToQuery(query, "gateway_id", req.GatewayID) + parameter.AddToQuery(query, "private_ip", req.PrivateIP) + parameter.AddToQuery(query, "protocol", req.Protocol) + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/pat-rules", + Query: query, + } + + var resp ListPATRulesResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// GetPATRule: Get a PAT rule, specified by its PAT rule ID. The response object gives full details of the PAT rule, including the Public Gateway it belongs to and the configuration settings in terms of public / private ports, private IP and protocol. +func (s *API) GetPATRule(req *GetPATRuleRequest, opts ...scw.RequestOption) (*PATRule, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.PatRuleID) == "" { + return nil, errors.New("field PatRuleID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/pat-rules/" + fmt.Sprint(req.PatRuleID) + "", + } + + var resp PATRule + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// CreatePATRule: Create a new PAT rule on a specified Public Gateway, defining the protocol to use, public port to listen on, and private port / IP address to map to. +func (s *API) CreatePATRule(req *CreatePATRuleRequest, opts ...scw.RequestOption) (*PATRule, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/pat-rules", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp PATRule + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// UpdatePATRule: Update a PAT rule, specified by its PAT rule ID. Configuration settings including private/public port, private IP address and protocol can all be updated. +func (s *API) UpdatePATRule(req *UpdatePATRuleRequest, opts ...scw.RequestOption) (*PATRule, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.PatRuleID) == "" { + return nil, errors.New("field PatRuleID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/pat-rules/" + fmt.Sprint(req.PatRuleID) + "", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp PATRule + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// SetPATRules: Set a definitive list of PAT rules attached to a Public Gateway. Each rule is identified by its public port and protocol. This will sync the current PAT rule list on the gateway with the new list, creating, updating or deleting PAT rules accordingly. +func (s *API) SetPATRules(req *SetPATRulesRequest, opts ...scw.RequestOption) (*SetPATRulesResponse, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PUT", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/pat-rules", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp SetPATRulesResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// DeletePATRule: Delete a PAT rule, identified by its PAT rule ID. This action is irreversible. +func (s *API) DeletePATRule(req *DeletePATRuleRequest, opts ...scw.RequestOption) error { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.PatRuleID) == "" { + return errors.New("field PatRuleID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/pat-rules/" + fmt.Sprint(req.PatRuleID) + "", + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +// ListGatewayTypes: List the different Public Gateway commercial offer types available at Scaleway. The response is an array of objects describing the name and technical details of each available gateway type. +func (s *API) ListGatewayTypes(req *ListGatewayTypesRequest, opts ...scw.RequestOption) (*ListGatewayTypesResponse, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateway-types", + } + + var resp ListGatewayTypesResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// ListIPs: List Public Gateway flexible IP addresses. A number of filter options are available for limiting results in the response. +func (s *API) ListIPs(req *ListIPsRequest, opts ...scw.RequestOption) (*ListIPsResponse, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + defaultPageSize, exist := s.client.GetDefaultPageSize() + if (req.PageSize == nil || *req.PageSize == 0) && exist { + req.PageSize = &defaultPageSize + } + + query := url.Values{} + parameter.AddToQuery(query, "order_by", req.OrderBy) + parameter.AddToQuery(query, "page", req.Page) + parameter.AddToQuery(query, "page_size", req.PageSize) + parameter.AddToQuery(query, "organization_id", req.OrganizationID) + parameter.AddToQuery(query, "project_id", req.ProjectID) + parameter.AddToQuery(query, "tags", req.Tags) + parameter.AddToQuery(query, "reverse", req.Reverse) + parameter.AddToQuery(query, "is_free", req.IsFree) + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/ips", + Query: query, + } + + var resp ListIPsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// GetIP: Get details of a Public Gateway flexible IP address, identified by its IP ID. The response object contains information including which (if any) Public Gateway using this IP address, the reverse and various other metadata. +func (s *API) GetIP(req *GetIPRequest, opts ...scw.RequestOption) (*IP, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.IPID) == "" { + return nil, errors.New("field IPID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/ips/" + fmt.Sprint(req.IPID) + "", + } + + var resp IP + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// CreateIP: Create (reserve) a new flexible IP address that can be used for a Public Gateway in a specified Scaleway Project. +func (s *API) CreateIP(req *CreateIPRequest, opts ...scw.RequestOption) (*IP, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/ips", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp IP + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// UpdateIP: Update details of an existing flexible IP address, including its tags, reverse and the Public Gateway it is assigned to. +func (s *API) UpdateIP(req *UpdateIPRequest, opts ...scw.RequestOption) (*IP, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.IPID) == "" { + return nil, errors.New("field IPID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/ips/" + fmt.Sprint(req.IPID) + "", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp IP + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// DeleteIP: Delete a flexible IP address from your account. This action is irreversible. +func (s *API) DeleteIP(req *DeleteIPRequest, opts ...scw.RequestOption) error { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.IPID) == "" { + return errors.New("field IPID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/ips/" + fmt.Sprint(req.IPID) + "", + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +// RefreshSSHKeys: Refresh the SSH keys of a given Public Gateway, specified by its gateway ID. This adds any new SSH keys in the gateway's Scaleway Project to the gateway itself. +func (s *API) RefreshSSHKeys(req *RefreshSSHKeysRequest, opts ...scw.RequestOption) (*Gateway, error) { + var err error + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + if fmt.Sprint(req.GatewayID) == "" { + return nil, errors.New("field GatewayID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc-gw/v1/zones/" + fmt.Sprint(req.Zone) + "/gateways/" + fmt.Sprint(req.GatewayID) + "/refresh-ssh-keys", + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Gateway + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} diff --git a/vendor/github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1/vpcgw_utils.go b/vendor/github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1/vpcgw_utils.go new file mode 100644 index 0000000000000..b9007edf113c0 --- /dev/null +++ b/vendor/github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1/vpcgw_utils.go @@ -0,0 +1,170 @@ +package vpcgw + +import ( + "time" + + "github.com/scaleway/scaleway-sdk-go/internal/async" + "github.com/scaleway/scaleway-sdk-go/internal/errors" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +const ( + defaultTimeout = 5 * time.Minute + defaultRetryInterval = 15 * time.Second +) + +// WaitForGatewayRequest is used by WaitForGateway method +type WaitForGatewayRequest struct { + GatewayID string + Zone scw.Zone + Timeout *time.Duration + RetryInterval *time.Duration +} + +// WaitForGateway waits for the gateway to be in a "terminal state" before returning. +// This function can be used to wait for a gateway to be ready for example. +func (s *API) WaitForGateway(req *WaitForGatewayRequest, opts ...scw.RequestOption) (*Gateway, error) { + timeout := defaultTimeout + if req.Timeout != nil { + timeout = *req.Timeout + } + retryInterval := defaultRetryInterval + if req.RetryInterval != nil { + retryInterval = *req.RetryInterval + } + + terminalStatus := map[GatewayStatus]struct{}{ + GatewayStatusUnknown: {}, + GatewayStatusStopped: {}, + GatewayStatusRunning: {}, + GatewayStatusFailed: {}, + GatewayStatusDeleted: {}, + GatewayStatusLocked: {}, + } + + gateway, err := async.WaitSync(&async.WaitSyncConfig{ + Get: func() (interface{}, bool, error) { + ns, err := s.GetGateway(&GetGatewayRequest{ + Zone: req.Zone, + GatewayID: req.GatewayID, + }, opts...) + if err != nil { + return nil, false, err + } + + _, isTerminal := terminalStatus[ns.Status] + + return ns, isTerminal, err + }, + Timeout: timeout, + IntervalStrategy: async.LinearIntervalStrategy(retryInterval), + }) + if err != nil { + return nil, errors.Wrap(err, "waiting for gateway failed") + } + + return gateway.(*Gateway), nil +} + +// WaitForGatewayNetworkRequest is used by WaitForGatewayNetwork method +type WaitForGatewayNetworkRequest struct { + GatewayNetworkID string + Zone scw.Zone + Timeout *time.Duration + RetryInterval *time.Duration +} + +// WaitForGatewayNetwork waits for the gateway network to be in a "terminal state" before returning. +// This function can be used to wait for a gateway network to be ready for example. +func (s *API) WaitForGatewayNetwork(req *WaitForGatewayNetworkRequest, opts ...scw.RequestOption) (*GatewayNetwork, error) { + timeout := defaultTimeout + if req.Timeout != nil { + timeout = *req.Timeout + } + retryInterval := defaultRetryInterval + if req.RetryInterval != nil { + retryInterval = *req.RetryInterval + } + + terminalStatus := map[GatewayNetworkStatus]struct{}{ + GatewayNetworkStatusReady: {}, + GatewayNetworkStatusUnknown: {}, + GatewayNetworkStatusDeleted: {}, + } + + gatewayNetwork, err := async.WaitSync(&async.WaitSyncConfig{ + Get: func() (interface{}, bool, error) { + ns, err := s.GetGatewayNetwork(&GetGatewayNetworkRequest{ + Zone: req.Zone, + GatewayNetworkID: req.GatewayNetworkID, + }, opts...) + if err != nil { + return nil, false, err + } + + _, isTerminal := terminalStatus[ns.Status] + + return ns, isTerminal, err + }, + Timeout: timeout, + IntervalStrategy: async.LinearIntervalStrategy(retryInterval), + }) + if err != nil { + return nil, errors.Wrap(err, "waiting for gateway network failed") + } + + return gatewayNetwork.(*GatewayNetwork), nil +} + +// WaitForDHCPEntriesRequest is used by WaitForDHCPEntries method +type WaitForDHCPEntriesRequest struct { + GatewayNetworkID *string + MacAddress string + + Zone scw.Zone + Timeout *time.Duration + RetryInterval *time.Duration +} + +// WaitForDHCPEntries waits for at least one dhcp entry with the correct mac address. +// This function can be used to wait for an instance to use dhcp +func (s *API) WaitForDHCPEntries(req *WaitForDHCPEntriesRequest, opts ...scw.RequestOption) (*ListDHCPEntriesResponse, error) { + timeout := defaultTimeout + if req.Timeout != nil { + timeout = *req.Timeout + } + retryInterval := defaultRetryInterval + if req.RetryInterval != nil { + retryInterval = *req.RetryInterval + } + + dhcpEntries, err := async.WaitSync(&async.WaitSyncConfig{ + Get: func() (interface{}, bool, error) { + entries, err := s.ListDHCPEntries(&ListDHCPEntriesRequest{ + Zone: req.Zone, + GatewayNetworkID: req.GatewayNetworkID, + MacAddress: &req.MacAddress, + }, opts...) + if err != nil { + return nil, false, err + } + + containsMacAddress := false + for _, entry := range entries.DHCPEntries { + if entry.MacAddress == req.MacAddress { + containsMacAddress = true + break + } + } + + return entries, containsMacAddress, err + }, + Timeout: timeout, + IntervalStrategy: async.LinearIntervalStrategy(retryInterval), + }) + if err != nil { + return nil, errors.Wrap(err, "waiting for gateway network failed") + } + + return dhcpEntries.(*ListDHCPEntriesResponse), nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 9bf13a976bd60..ba13536af96a4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1053,6 +1053,8 @@ github.com/scaleway/scaleway-sdk-go/api/ipam/v1alpha1 github.com/scaleway/scaleway-sdk-go/api/lb/v1 github.com/scaleway/scaleway-sdk-go/api/marketplace/v2 github.com/scaleway/scaleway-sdk-go/api/std +github.com/scaleway/scaleway-sdk-go/api/vpc/v2 +github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1 github.com/scaleway/scaleway-sdk-go/internal/async github.com/scaleway/scaleway-sdk-go/internal/auth github.com/scaleway/scaleway-sdk-go/internal/errors