diff --git a/.gitignore b/.gitignore index 7381ff6f0b060..4ed4cb81ac6c3 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,9 @@ _output # Used by E2E testing _artifacts _rundir +zzz-dev-Mia-Cross +zzz-testing +zzz-dev-Mia-Cross +zzz-testing +hack/upload +logs diff --git a/cmd/kops-controller/controllers/scalewayipam.go b/cmd/kops-controller/controllers/scalewayipam.go new file mode 100644 index 0000000000000..38e9e1f491cff --- /dev/null +++ b/cmd/kops-controller/controllers/scalewayipam.go @@ -0,0 +1,131 @@ +/* +Copyright 2021 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 controllers + +import ( + "context" + "fmt" + "strings" + + "github.com/go-logr/logr" + "github.com/scaleway/scaleway-sdk-go/api/ipam/v1" + "github.com/scaleway/scaleway-sdk-go/scw" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/klog/v2" + kopsv "k8s.io/kops" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +// NewScalewayIPAMReconciler is the constructor for a IPAMReconciler +func NewScalewayIPAMReconciler(mgr manager.Manager) (*ScalewayIPAMReconciler, error) { + klog.Info("Starting scaleway ipam controller") + r := &ScalewayIPAMReconciler{ + client: mgr.GetClient(), + log: ctrl.Log.WithName("controllers").WithName("IPAM"), + } + + coreClient, err := corev1client.NewForConfig(mgr.GetConfig()) + if err != nil { + return nil, fmt.Errorf("error building corev1 client: %v", err) + } + r.coreV1Client = coreClient + + profile, err := scaleway.CreateValidScalewayProfile() + if err != nil { + return nil, err + } + scwClient, err := scw.NewClient( + scw.WithProfile(profile), + scw.WithUserAgent(scaleway.KopsUserAgentPrefix+kopsv.Version), + ) + if err != nil { + return nil, fmt.Errorf("creating client for Scaleway IPAM controller: %w", err) + } + r.scwClient = scwClient + + return r, nil +} + +// ScalewayIPAMReconciler observes Node objects, and labels them with the correct labels for the instancegroup +// This used to be done by the kubelet, but is moving to a central controller for greater security in 1.16 +type ScalewayIPAMReconciler struct { + // client is the controller-runtime client + client client.Client + + // log is a logr + log logr.Logger + + // coreV1Client is a client-go client for patching nodes + coreV1Client *corev1client.CoreV1Client + + scwClient *scw.Client +} + +// +kubebuilder:rbac:groups=,resources=nodes,verbs=get;list;watch;patch +// Reconcile is the main reconciler function that observes node changes. +func (r *ScalewayIPAMReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = r.log.WithValues("ipam-controller", req.NamespacedName) + + node := &corev1.Node{} + if err := r.client.Get(ctx, req.NamespacedName, node); err != nil { + klog.Warningf("unable to fetch node %s: %v", node.Name, err) + if apierrors.IsNotFound(err) { + // we'll ignore not-found errors, since they can't be fixed by an immediate + // requeue (we'll need to wait for a new notification), and we can get them + // on deleted requests. + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + if len(node.Spec.PodCIDRs) == 0 { + // CCM Node Controller has not done its thing yet + if node.Spec.ProviderID == "" { + klog.Infof("Node %q has empty provider ID", node.Name) + return ctrl.Result{}, nil + } + + // providerID scaleway://instance/fr-par-1/instance-id + uuid := strings.Split(node.Spec.ProviderID, "/") + if len(uuid) != 3 { + return ctrl.Result{}, fmt.Errorf("unexpected format for server id %s", node.Spec.ProviderID) + } + serverID := uuid[2] + zone := scw.Zone(uuid[1]) + + ip, err := scaleway.GetIPAMPublicIP(ipam.NewAPI(r.scwClient), serverID, zone) + if err != nil { + return ctrl.Result{}, fmt.Errorf("could not get IPAM public IP for server %s: %w", serverID, err) + } + if err := patchNodePodCIDRs(r.coreV1Client, ctx, node, ip); err != nil { + return ctrl.Result{}, err + } + } + + return ctrl.Result{}, nil +} + +func (r *ScalewayIPAMReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&corev1.Node{}). + Complete(r) +} diff --git a/cmd/kops-controller/main.go b/cmd/kops-controller/main.go index a72ca0ca3ec93..deebb600965fc 100644 --- a/cmd/kops-controller/main.go +++ b/cmd/kops-controller/main.go @@ -385,6 +385,12 @@ func setupCloudIPAM(ctx context.Context, mgr manager.Manager, opt *config.Option return fmt.Errorf("creating gce IPAM controller: %w", err) } controller = ipamController + //case "scaleway": + // ipamController, err := controllers.NewScalewayIPAMReconciler(mgr) + // if err != nil { + // return fmt.Errorf("creating scaleway IPAM controller: %w", err) + // } + // controller = ipamController default: return fmt.Errorf("kOps IPAM controller is not supported on cloud %q", opt.Cloud) } diff --git a/go.mod b/go.mod index 8110d37b6ca61..1873e89fb9e3c 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/pelletier/go-toml v1.9.5 github.com/pkg/sftp v1.13.6 github.com/prometheus/client_golang v1.19.0 - github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26 + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26.0.20240422085138-c14591c368f2 github.com/sergi/go-diff v1.3.1 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index acc59a2b085be..b8533be44b8e8 100644 --- a/go.sum +++ b/go.sum @@ -595,8 +595,8 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26 h1:F+GIVtGqCFxPxO46ujf8cEOP574MBoRm3gNbPXECbxs= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26.0.20240422085138-c14591c368f2 h1:9ZT4vcFKGvp1dcr5CJkVQivBkecrZybOtpnOfrJ0crc= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26.0.20240422085138-c14591c368f2/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= diff --git a/hack/upload b/hack/upload index 7179fc4fc3d9c..64d046b5ac6d4 100755 --- a/hack/upload +++ b/hack/upload @@ -56,10 +56,24 @@ fi if [[ "${DEST:0:6}" == "scw://" ]]; then SCW_BUCKET=$(echo "${DEST}" | cut -c 7-) - echo "--> s3cmd put ${SRC} s3://$SCW_BUCKET --recursive ${PUBLIC:+--acl-public} --progress" - s3cmd put ${SRC} s3://$SCW_BUCKET --recursive ${PUBLIC:+--acl-public} --progress - exit 0 -fi + if [[ $(pwd) == "/home/leila/Desktop/kops" ]]; then + REGION="par" + elif [[ $(pwd) == "/home/leila/Desktop/kops_v2" ]]; then + REGION="ams" + fi + if [[ $SCW_BUCKET == "kops-images-testing" ]]; then + PROFILE="kops" + REGION="par" + else + PROFILE="normal" + fi + echo "--> rclone sync ${SRC} $PROFILE-$REGION://$SCW_BUCKET --progress" + rclone sync ${SRC} $PROFILE-$REGION://$SCW_BUCKET --progress + # echo "--> s3cmd put ${SRC} s3://$SCW_BUCKET --recursive ${PUBLIC:+--acl-public} --progress" + # s3cmd put ${SRC} s3://$SCW_BUCKET --recursive ${PUBLIC:+--acl-public} --progress + # fi + exit 0 + fi echo "Unsupported destination - supports s3://, gs:// and oss:// urls: ${DEST}" exit 1 diff --git a/nodeup/pkg/model/context.go b/nodeup/pkg/model/context.go index 93f49da257db0..fc96a63d1333d 100644 --- a/nodeup/pkg/model/context.go +++ b/nodeup/pkg/model/context.go @@ -29,6 +29,8 @@ import ( "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/blang/semver/v4" hcloudmetadata "github.com/hetznercloud/hcloud-go/hcloud/metadata" + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + "github.com/scaleway/scaleway-sdk-go/scw" "k8s.io/klog/v2" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops/model" @@ -36,6 +38,7 @@ import ( "k8s.io/kops/pkg/apis/nodeup" "k8s.io/kops/pkg/systemd" "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" "k8s.io/kops/upup/pkg/fi/nodeup/nodetasks" "k8s.io/kops/upup/pkg/fi/utils" "k8s.io/kops/util/pkg/architectures" @@ -604,6 +607,24 @@ func (c *NodeupModelContext) GetMetadataLocalIP(ctx context.Context) (string, er } } + case kops.CloudProviderScaleway: + metadataAPI := instance.NewMetadataAPI() + metadata, err := metadataAPI.GetMetadata() + if err != nil { + return "", fmt.Errorf("failed to retrieve server metadata: %w", err) + } + + zone, err := scw.ParseZone(metadata.Location.ZoneID) + if err != nil { + return "", fmt.Errorf("unable to parse Scaleway zone: %w", err) + } + + ip, err := scaleway.GetIPAMPublicIP(nil, metadata.ID, zone) + if err != nil { + return "", fmt.Errorf("failed to retrieve server IP: %w", err) + } + internalIP = ip + default: return "", fmt.Errorf("getting local IP from metadata is not supported for cloud provider: %q", c.BootConfig.CloudProvider) } diff --git a/nodeup/pkg/model/kube_apiserver.go b/nodeup/pkg/model/kube_apiserver.go index fdf784bba249e..19ccc18688de2 100644 --- a/nodeup/pkg/model/kube_apiserver.go +++ b/nodeup/pkg/model/kube_apiserver.go @@ -67,7 +67,7 @@ func (b *KubeAPIServerBuilder) Build(c *fi.NodeupModelBuilderContext) error { kubeAPIServer = *b.NodeupConfig.APIServerConfig.KubeAPIServer } - if b.CloudProvider() == kops.CloudProviderHetzner { + if b.CloudProvider() == kops.CloudProviderHetzner || b.CloudProvider() == kops.CloudProviderScaleway { localIP, err := b.GetMetadataLocalIP(c.Context()) if err != nil { return err diff --git a/nodeup/pkg/model/kubelet.go b/nodeup/pkg/model/kubelet.go index 6bfa7c073aaa6..52f863ae9a5b4 100644 --- a/nodeup/pkg/model/kubelet.go +++ b/nodeup/pkg/model/kubelet.go @@ -30,6 +30,8 @@ import ( awsconfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + "github.com/scaleway/scaleway-sdk-go/scw" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/klog/v2" @@ -89,12 +91,29 @@ func (b *KubeletBuilder) Build(c *fi.NodeupModelBuilderContext) error { return err } providerID = fmt.Sprintf("aws:///%s/%s", instanceIdentity.AvailabilityZone, instanceIdentity.InstanceID) + + } else if b.CloudProvider() == kops.CloudProviderScaleway { + klog.Info("DOING STUFF FOR SCW") + metadataAPI := instance.NewMetadataAPI() + metadata, err := metadataAPI.GetMetadata() + if err != nil { + return fmt.Errorf("failed to retrieve server metadata: %w", err) + } + instanceID := metadata.ID + zone, err := scw.ParseZone(metadata.Location.ZoneID) + if err != nil { + return fmt.Errorf("unable to parse Scaleway zone: %w", err) + } + providerID = fmt.Sprintf("scaleway://instance/%s/%s", zone, instanceID) + klog.Infof("PROVIDER ID = %s", providerID) } + klog.Info("DID THE STUFF, NOW APPLY IT") t, err := buildKubeletComponentConfig(kubeletConfig, providerID) if err != nil { return err } + klog.Info("APPLIED !") c.AddTask(t) } @@ -206,13 +225,13 @@ func (b *KubeletBuilder) Build(c *fi.NodeupModelBuilderContext) error { } /* Kubelet incorrectly interprets this value when CgroupDriver is systemd - See https://github.com/kubernetes/kubernetes/issues/101189 - { - cgroup := kubeletConfig.KubeReservedCgroup - if cgroup != "" { - c.EnsureTask(b.buildCgroupService(cgroup)) - } - } + See https://github.com/kubernetes/kubernetes/issues/101189 + { + cgroup := kubeletConfig.KubeReservedCgroup + if cgroup != "" { + c.EnsureTask(b.buildCgroupService(cgroup)) + } + } */ { @@ -223,12 +242,12 @@ func (b *KubeletBuilder) Build(c *fi.NodeupModelBuilderContext) error { } /* This suffers from the same issue as KubeReservedCgroup - { - cgroup := kubeletConfig.SystemReservedCgroup - if cgroup != "" { - c.EnsureTask(b.buildCgroupService(cgroup)) - } - } + { + cgroup := kubeletConfig.SystemReservedCgroup + if cgroup != "" { + c.EnsureTask(b.buildCgroupService(cgroup)) + } + } */ } diff --git a/pkg/model/components/etcdmanager/model.go b/pkg/model/components/etcdmanager/model.go index 38d4aa741941e..246cf2bd28b3c 100644 --- a/pkg/model/components/etcdmanager/model.go +++ b/pkg/model/components/etcdmanager/model.go @@ -171,7 +171,8 @@ metadata: spec: containers: - name: etcd-manager - image: registry.k8s.io/etcdadm/etcd-manager-slim:v3.0.20230925 + # TODO(Mia-Cross): revert this change with the etcd-manager's next release + image: rg.nl-ams.scw.cloud/kops-v2/etcd-manager:latest resources: requests: cpu: 100m diff --git a/pkg/model/components/kubedns.go b/pkg/model/components/kubedns.go index 91626548d2009..00a83453c6819 100644 --- a/pkg/model/components/kubedns.go +++ b/pkg/model/components/kubedns.go @@ -17,6 +17,7 @@ limitations under the License. package components import ( + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/kops/pkg/apis/kops" @@ -37,6 +38,19 @@ func (b *KubeDnsOptionsBuilder) BuildOptions(o interface{}) error { if clusterSpec.KubeDNS == nil { clusterSpec.KubeDNS = &kops.KubeDNSConfig{} + if clusterSpec.CloudProvider.Scaleway != nil { + clusterSpec.KubeDNS.Tolerations = []corev1.Toleration{ + { + Key: "node-role.kubernetes.io/control-plane", + Operator: corev1.TolerationOpExists, + Effect: corev1.TaintEffectNoSchedule, + }, + { + Key: "CriticalAddonsOnly", + Operator: corev1.TolerationOpExists, + }, + } + } } if clusterSpec.KubeDNS.CacheMaxSize == 0 { @@ -80,6 +94,10 @@ func (b *KubeDnsOptionsBuilder) BuildOptions(o interface{}) error { } } + //if clusterSpec.GetCloudProvider() == kops.CloudProviderScaleway { + // clusterSpec.KubeDNS.UpstreamNameservers = []string{"100.64.0.10"} + //} + nodeLocalDNS := clusterSpec.KubeDNS.NodeLocalDNS if nodeLocalDNS == nil { nodeLocalDNS = &kops.NodeLocalDNSConfig{} diff --git a/pkg/model/scalewaymodel/instances.go b/pkg/model/scalewaymodel/instances.go index 3c53663634143..518a203c13d47 100644 --- a/pkg/model/scalewaymodel/instances.go +++ b/pkg/model/scalewaymodel/instances.go @@ -78,6 +78,7 @@ func (b *InstanceModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { 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) } diff --git a/protokube/pkg/protokube/scaleway_volumes.go b/protokube/pkg/protokube/scaleway_volumes.go index fd9749eea59e5..3317748f00b8b 100644 --- a/protokube/pkg/protokube/scaleway_volumes.go +++ b/protokube/pkg/protokube/scaleway_volumes.go @@ -21,7 +21,7 @@ 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/api/ipam/v1" "github.com/scaleway/scaleway-sdk-go/scw" "k8s.io/klog/v2" kopsv "k8s.io/kops" diff --git a/tests/e2e/go.mod b/tests/e2e/go.mod index 5ae4ee9f77e0d..283a2ad6d9e21 100644 --- a/tests/e2e/go.mod +++ b/tests/e2e/go.mod @@ -11,6 +11,7 @@ require ( github.com/blang/semver/v4 v4.0.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/octago/sflags v0.2.0 + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26.0.20240422085138-c14591c368f2 github.com/spf13/pflag v1.0.5 golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f k8s.io/api v0.30.0 diff --git a/tests/e2e/go.sum b/tests/e2e/go.sum index a062682e5c6cb..add1925cc22e8 100644 --- a/tests/e2e/go.sum +++ b/tests/e2e/go.sum @@ -870,6 +870,8 @@ github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGq github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= github.com/sassoftware/relic/v7 v7.5.5 h1:2ZUM6ovo3STCAp0hZnO9nQY9lOB8OyfneeYIi4YUxMU= github.com/sassoftware/relic/v7 v7.5.5/go.mod h1:NxwtWxWxlUa9as2qZi635Ye6bBT/tGnMALLq7dSfOOU= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26.0.20240422085138-c14591c368f2 h1:9ZT4vcFKGvp1dcr5CJkVQivBkecrZybOtpnOfrJ0crc= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26.0.20240422085138-c14591c368f2/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/secure-systems-lab/go-securesystemslib v0.6.0 h1:T65atpAVCJQK14UA57LMdZGpHi4QYSH/9FZyNGqMYIA= github.com/secure-systems-lab/go-securesystemslib v0.6.0/go.mod h1:8Mtpo9JKks/qhPG4HGZ2LGMvrPbzuxwfz/f/zLfEWkk= diff --git a/tests/e2e/kubetest2-kops/deployer/common.go b/tests/e2e/kubetest2-kops/deployer/common.go index 635e3a3b14201..ca114d0f80716 100644 --- a/tests/e2e/kubetest2-kops/deployer/common.go +++ b/tests/e2e/kubetest2-kops/deployer/common.go @@ -102,6 +102,18 @@ func (d *deployer) initialize() error { } else if d.SSHPrivateKeyPath == "" && os.Getenv("KUBE_SSH_KEY_PATH") != "" { d.SSHPrivateKeyPath = os.Getenv("KUBE_SSH_KEY_PATH") } + case "scaleway": + if d.SSHPrivateKeyPath == "" || d.SSHPublicKeyPath == "" { + publicKeyPath, privateKeyPath, err := util.CreateSSHKeyPair(d.ClusterName) + if err != nil { + return err + } + d.SSHPublicKeyPath = publicKeyPath + d.SSHPrivateKeyPath = privateKeyPath + } + if d.SSHUser == "" { + d.SSHUser = "root" + } } if d.commonOptions.ShouldBuild() { @@ -157,6 +169,7 @@ func (d *deployer) verifyKopsFlags() error { case "aws": case "gce": case "digitalocean": + case "scaleway": default: return errors.New("unsupported --cloud-provider value") } @@ -207,6 +220,25 @@ func (d *deployer) env() []string { klog.Warningf("DO env var %s is empty..", k) } } + } else if d.CloudProvider == "scaleway" { + // Pass through some env vars if set + if accessKey := os.Getenv("SCW_ACCESS_KEY"); accessKey != "" { + vars = append(vars, "SCW_ACCESS_KEY="+accessKey) + vars = append(vars, "S3_ACCESS_KEY_ID="+accessKey) + } else { + klog.Warningf("SCW_ACCESS_KEY is empty") + } + if secretKey := os.Getenv("SCW_SECRET_KEY"); secretKey != "" { + vars = append(vars, "SCW_SECRET_KEY="+secretKey) + vars = append(vars, "S3_SECRET_ACCESS_KEY="+secretKey) + } else { + klog.Warningf("SCW_SECRET_KEY is empty") + } + if projectID := os.Getenv("SCW_DEFAULT_PROJECT_ID"); projectID != "" { + vars = append(vars, "SCW_DEFAULT_PROJECT_ID="+projectID) + } else { + klog.Warningf("SCW_DEFAULT_PROJECT_ID is empty") + } } if d.KopsBaseURL != "" { vars = append(vars, fmt.Sprintf("KOPS_BASE_URL=%v", d.KopsBaseURL)) @@ -301,6 +333,8 @@ func (d *deployer) stateStore() string { ss = "gs://" + gce.GCSBucketName(d.GCPProject, "state") case "digitalocean": ss = "do://e2e-kops-space" + case "scaleway": + ss = "scw://kops-state-store-testing" } } return ss diff --git a/tests/e2e/kubetest2-kops/deployer/up.go b/tests/e2e/kubetest2-kops/deployer/up.go index f8fdd95e5b105..3a6776f1fd3b0 100644 --- a/tests/e2e/kubetest2-kops/deployer/up.go +++ b/tests/e2e/kubetest2-kops/deployer/up.go @@ -32,6 +32,7 @@ import ( "k8s.io/kops/tests/e2e/kubetest2-kops/aws" "k8s.io/kops/tests/e2e/kubetest2-kops/do" "k8s.io/kops/tests/e2e/kubetest2-kops/gce" + "k8s.io/kops/tests/e2e/kubetest2-kops/scaleway" "k8s.io/kops/tests/e2e/pkg/kops" "k8s.io/kops/tests/e2e/pkg/util" "k8s.io/kops/tests/e2e/pkg/version" @@ -201,6 +202,9 @@ func (d *deployer) createCluster(zones []string, adminAccess string, yes bool) e case "digitalocean": args = appendIfUnset(args, "--master-size", "c2-16vcpu-32gb") args = appendIfUnset(args, "--node-size", "c2-16vcpu-32gb") + case "scaleway": + args = appendIfUnset(args, "--master-size", "PRO2-S") + args = appendIfUnset(args, "--node-size", "PRO2-S") } args = appendIfUnset(args, "--master-volume-size", "48") @@ -341,6 +345,8 @@ func (d *deployer) zones() ([]string, error) { return gce.RandomZones(1) case "digitalocean": return do.RandomZones(1) + case "scaleway": + return scaleway.RandomZones(1) } return nil, fmt.Errorf("unsupported CloudProvider: %v", d.CloudProvider) } diff --git a/tests/e2e/kubetest2-kops/scaleway/zones.go b/tests/e2e/kubetest2-kops/scaleway/zones.go new file mode 100644 index 0000000000000..cbc77fa9de240 --- /dev/null +++ b/tests/e2e/kubetest2-kops/scaleway/zones.go @@ -0,0 +1,62 @@ +/* +Copyright 2021 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 scaleway + +import ( + "fmt" + "math/rand" + "time" + + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + "github.com/scaleway/scaleway-sdk-go/api/lb/v1" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +func zoneIsInZones(zone scw.Zone, zones []scw.Zone) bool { + for _, z := range zones { + if zone == z { + return true + } + } + return false +} + +// RandomZones returns a random availability zone among the ones where the products needed are available +func RandomZones(count int) ([]string, error) { + if count > 1 { + return nil, fmt.Errorf("expected 1 zone, got %d", count) + } + + instanceAPI := &instance.API{} + instanceAPIZones := instanceAPI.Zones() + lbAPI := &lb.ZonedAPI{} + lbAPIZones := lbAPI.Zones() + + allAvailableZones := []scw.Zone(nil) + for _, zone := range append(instanceAPIZones, lbAPIZones...) { + if !zoneIsInZones(zone, allAvailableZones) && zoneIsInZones(zone, instanceAPIZones) && zoneIsInZones(zone, lbAPIZones) { + allAvailableZones = append(allAvailableZones, zone) + } + } + + rand.Seed(time.Now().UnixNano()) + n := rand.Intn(1000) % len(allAvailableZones) + chosenZone := allAvailableZones[n] + chosenZones := []string{chosenZone.String()} + + return chosenZones, nil +} diff --git a/tests/e2e/pkg/tester/tester.go b/tests/e2e/pkg/tester/tester.go index cbfe163307d14..cbf9e99f8b2a1 100644 --- a/tests/e2e/pkg/tester/tester.go +++ b/tests/e2e/pkg/tester/tester.go @@ -25,12 +25,14 @@ import ( "strings" "github.com/octago/sflags/gen/gpflag" + "github.com/scaleway/scaleway-sdk-go/scw" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" unversioned "k8s.io/kops/pkg/apis/kops" api "k8s.io/kops/pkg/apis/kops/v1alpha2" "k8s.io/kops/tests/e2e/pkg/kops" "k8s.io/kops/upup/pkg/fi/cloudup/gce" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" "sigs.k8s.io/kubetest2/pkg/artifacts" "sigs.k8s.io/kubetest2/pkg/testers/ginkgo" ) @@ -151,6 +153,7 @@ func (t *Tester) addProviderFlag() error { case "aws", "gce": provider = cluster.Spec.LegacyCloudProvider case "digitalocean": + case "scaleway": default: klog.Warningf("unhandled cluster.spec.cloudProvider %q for determining ginkgo Provider", cluster.Spec.LegacyCloudProvider) } @@ -255,6 +258,12 @@ func (t *Tester) addRegionFlag() error { region = zone[:len(zone)-1] case "gce": region = cluster.Spec.Subnets[0].Region + case "scaleway": + scwRegion, err := scaleway.ParseRegionFromZone(scw.Zone(cluster.Spec.Subnets[0].Zone)) + if err != nil { + return fmt.Errorf("adding region flag: %w", err) + } + region = string(scwRegion) default: klog.Warningf("unhandled region detection for cloud provider: %v", cluster.Spec.LegacyCloudProvider) } diff --git a/tests/e2e/scenarios/scaleway/run-test b/tests/e2e/scenarios/scaleway/run-test new file mode 100755 index 0000000000000..7f30fc30ba687 --- /dev/null +++ b/tests/e2e/scenarios/scaleway/run-test @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# Copyright 2021 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. + +set -o errexit +set -o nounset +set -o pipefail +set -o xtrace + +REPO_ROOT=$(git rev-parse --show-toplevel); +KOPSPATH=$REPO_ROOT/.build/dist/$(go env GOOS)/$(go env GOARCH) +PATH=$KOPSPATH:$PATH + +#KUBETEST2_COMMON_ARGS="" +#KUBETEST2_COMMON_ARGS="${KUBETEST2_COMMON_ARGS} --admin-access=${ADMIN_ACCESS:-}" + +export GO111MODULE=on +make test-e2e-install + +rm -r /kops/_artifacts + +kubetest2-kops -v 10 \ + --up \ + --cloud-provider=scaleway \ + --cluster-name="e2e-test.k8s.local" \ + --kops-binary-path=${KOPSPATH}/kops \ + --env JOB_NAME=pull-kops-e2e-kubernetes-scw-kubetest2 \ + --env KOPS_FEATURE_FLAGS=Scaleway \ + --env S3_REGION=fr-par \ + --env S3_ENDPOINT=https://s3.fr-par.scw.cloud \ + --env KOPS_STATE_STORE=scw://kops-state-store-testing \ + --env KOPS_BASE_URL=https://s3.fr-par.scw.cloud/kops-images-testing/kops/1.29.0-alpha.3 \ + --create-args "--networking=cilium" \ + --kubernetes-version=v1.25.5 \ + --test=kops \ + -- \ + --test-package-marker=v1.29.0-alpha.3 \ + --test-package-version=v1.28.2 \ + --parallel 25 \ + --skip-regex="\[Slow\]|\[Serial\]|\[Disruptive\]|\[Flaky\]|\[Feature:.+\]|\[HPA\]|Dashboard|RuntimeClass|RuntimeHandler|nfs|NFS|Services.*functioning.*NodePort|Services.*rejected.*endpoints|Services.*NodePort.*listening.*same.*port|TCP.CLOSE_WAIT|should.*run.*through.*the.*lifecycle.*of.*Pods.*and.*PodStatus" \ + > >(tee log-kubetest-stdout.log) 2> >(tee log-kubetest-stderr.log >&2) +# | tee log-kubetest.txt +# --ssh-public-key=$REPO_ROOT/tests/integration/update_cluster/minimal_scaleway/id_rsa.pub \ +# --kops-version-marker=https://storage.googleapis.com/k8s-staging-kops/kops/releases/markers/release-1.20/latest-ci.txt \ \ No newline at end of file diff --git a/tests/integration/update_cluster/minimal_scaleway/id_rsa.pub b/tests/integration/update_cluster/minimal_scaleway/id_rsa.pub index 19767aea155d8..846b1592d32f6 100644 --- a/tests/integration/update_cluster/minimal_scaleway/id_rsa.pub +++ b/tests/integration/update_cluster/minimal_scaleway/id_rsa.pub @@ -1 +1 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDKqbVEozfAqng0gx8HTUu69EppcE5SWet6MpwrGShqMVUC4wkoiuVtJDPhMmWmdt7B7Ttc5pvnAZAZaQ6TKMguyBoAyS7qOTLU9/hM803XtSiwQUftOXiJfmsqAXEc8yDyb7UnrF8X7aA3gQJsnQBGJGdp+C88dPHNZenw4PnQc8BNYTCXG9d8F5vJ3xQ5qbiG4HVNoQ2CZh2ht+GedZJ3hl9lMJ24kE/cbMCLKxabMP4ROetECG6PU251jnm84NA8rm0Av/JMmn/c9CFAe0D0D1dGDlHWPsk4mbhGKJ0yU0YliatmPfmgSasismbYzIFf7VPq91ARzRUbavd1fYMBmkMsce0YR/5FdtrpzRhqDzuvwQgQRsoTcttdvp0puFcrtNefMfk8NCbBedIlkzOFxfGiBbe6jde4wqsqEnSrNHwZ2b+Er8z7vjcDPBqYk3gubmMBCrYxg6o1lOS6tTN0kJDUlyKO2AN1ZDr3mpkbhkvZV/N7gLglcClM0X5X7iM= leila@leila-ThinkPad-T14s-Gen-2i \ No newline at end of file +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDDJ3lhaLGWT/YYhVMQFNo9YH1q4MGgO3qOX0dCU6bnTTchOHs0wByRuWhq7OnLt4H2JXSn3FEnjtnFDV9NvrTpg9fkovUdWJJCt7JYcEUcDSy5+G1LyO/PEDVyxuHmBiyek92JU+Kl9QCARaZRjvhYuvH4hCI0HaMYap/livITnUDyI+OM6hEbkXXjzHr6s+5Fwy/ztH4rgq8f+ZmejmjczpDD/6LQzsVtcrAF5SMaUvjuwfOhBpyWtwtwfi35k8ac0+CPBI/s6bnAGgtg6ylhC5Er2YFSKpg2S0hdR42pzjdlB3eXB1eKlU7IRAaEdrR56PSzUcrefOJ9DRsQ39PKaS5j/IJvrEUL9TOvF5PmIuWBW1fWh9BWAveivE/rhC9i96QiU7sFxVq2p6sR5tu9o0UEY9xLHaYbG2DfRDb7TXlPS3uBZ/yDRv7cJtH6cEEaAiGhCXBJ108xCpM0Dbva/jfzwW7rBRqJDRZsN1Kas2S2saTzPNHj4ANG6wKDWEc= \ No newline at end of file diff --git a/tests/integration/update_cluster/minimal_scaleway/kubernetes.tf b/tests/integration/update_cluster/minimal_scaleway/kubernetes.tf index e71149eb5a657..2ff5f7f2dda22 100644 --- a/tests/integration/update_cluster/minimal_scaleway/kubernetes.tf +++ b/tests/integration/update_cluster/minimal_scaleway/kubernetes.tf @@ -170,9 +170,9 @@ resource "aws_s3_object" "scw-minimal-k8s-local-addons-scaleway-csi-driver-addon 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" - public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDKqbVEozfAqng0gx8HTUu69EppcE5SWet6MpwrGShqMVUC4wkoiuVtJDPhMmWmdt7B7Ttc5pvnAZAZaQ6TKMguyBoAyS7qOTLU9/hM803XtSiwQUftOXiJfmsqAXEc8yDyb7UnrF8X7aA3gQJsnQBGJGdp+C88dPHNZenw4PnQc8BNYTCXG9d8F5vJ3xQ5qbiG4HVNoQ2CZh2ht+GedZJ3hl9lMJ24kE/cbMCLKxabMP4ROetECG6PU251jnm84NA8rm0Av/JMmn/c9CFAe0D0D1dGDlHWPsk4mbhGKJ0yU0YliatmPfmgSasismbYzIFf7VPq91ARzRUbavd1fYMBmkMsce0YR/5FdtrpzRhqDzuvwQgQRsoTcttdvp0puFcrtNefMfk8NCbBedIlkzOFxfGiBbe6jde4wqsqEnSrNHwZ2b+Er8z7vjcDPBqYk3gubmMBCrYxg6o1lOS6tTN0kJDUlyKO2AN1ZDr3mpkbhkvZV/N7gLglcClM0X5X7iM= leila@leila-ThinkPad-T14s-Gen-2i" +resource "scaleway_iam_ssh_key" "kubernetes-scw-minimal-k8s-local-ae_ea_e9_42_75_4b_cd_2a_0d_68_c8_5a_af_7c_b1_c4" { + name = "kubernetes.scw-minimal.k8s.local-ae:ea:e9:42:75:4b:cd:2a:0d:68:c8:5a:af:7c:b1:c4" + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDDJ3lhaLGWT/YYhVMQFNo9YH1q4MGgO3qOX0dCU6bnTTchOHs0wByRuWhq7OnLt4H2JXSn3FEnjtnFDV9NvrTpg9fkovUdWJJCt7JYcEUcDSy5+G1LyO/PEDVyxuHmBiyek92JU+Kl9QCARaZRjvhYuvH4hCI0HaMYap/livITnUDyI+OM6hEbkXXjzHr6s+5Fwy/ztH4rgq8f+ZmejmjczpDD/6LQzsVtcrAF5SMaUvjuwfOhBpyWtwtwfi35k8ac0+CPBI/s6bnAGgtg6ylhC5Er2YFSKpg2S0hdR42pzjdlB3eXB1eKlU7IRAaEdrR56PSzUcrefOJ9DRsQ39PKaS5j/IJvrEUL9TOvF5PmIuWBW1fWh9BWAveivE/rhC9i96QiU7sFxVq2p6sR5tu9o0UEY9xLHaYbG2DfRDb7TXlPS3uBZ/yDRv7cJtH6cEEaAiGhCXBJ108xCpM0Dbva/jfzwW7rBRqJDRZsN1Kas2S2saTzPNHj4ANG6wKDWEc=" } resource "scaleway_instance_ip" "control-plane-fr-par-1-0" { diff --git a/upup/models/cloudup/resources/addons/coredns.addons.k8s.io/k8s-1.12.yaml.template b/upup/models/cloudup/resources/addons/coredns.addons.k8s.io/k8s-1.12.yaml.template index ee44c9367c91d..931dd167b417d 100644 --- a/upup/models/cloudup/resources/addons/coredns.addons.k8s.io/k8s-1.12.yaml.template +++ b/upup/models/cloudup/resources/addons/coredns.addons.k8s.io/k8s-1.12.yaml.template @@ -66,6 +66,7 @@ data: {{ KubeDNS.ExternalCoreFile | indent 4 }} {{- else }} .:53 { + log errors health { lameduck 5s 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..bac2bdb42fb1b 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 @@ -46,16 +46,6 @@ spec: operator: "Exists" - key: "node-role.kubernetes.io/master" effect: NoSchedule - - key: "node-role.kubernetes.io/control-plane" - effect: NoSchedule - - key: node.kubernetes.io/not-ready - effect: NoExecute - operator: Exists - tolerationSeconds: 300 - - key: node.kubernetes.io/unreachable - effect: NoExecute - operator: Exists - tolerationSeconds: 300 containers: - name: scaleway-cloud-controller-manager image: scaleway/scaleway-cloud-controller-manager:latest @@ -182,4 +172,4 @@ roleRef: subjects: - kind: ServiceAccount name: cloud-controller-manager - namespace: kube-system + namespace: kube-system \ No newline at end of file diff --git a/upup/pkg/fi/cloudup/scaleway/cloud.go b/upup/pkg/fi/cloudup/scaleway/cloud.go index 092734df43809..5f6200a95691d 100644 --- a/upup/pkg/fi/cloudup/scaleway/cloud.go +++ b/upup/pkg/fi/cloudup/scaleway/cloud.go @@ -24,7 +24,7 @@ import ( domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1" iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1" "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/api/ipam/v1" "github.com/scaleway/scaleway-sdk-go/api/lb/v1" "github.com/scaleway/scaleway-sdk-go/api/marketplace/v2" "github.com/scaleway/scaleway-sdk-go/scw" @@ -36,6 +36,7 @@ import ( "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/cloudinstances" "k8s.io/kops/upup/pkg/fi" + "k8s.io/utils/net" ) const ( @@ -81,6 +82,7 @@ type ScwCloud interface { GetClusterSSHKeys(clusterName string) ([]*iam.SSHKey, error) GetClusterVolumes(clusterName string) ([]*instance.Volume, error) GetServerIP(serverID string, zone scw.Zone) (string, error) + GetServerPrivateIP(serverID string, zone scw.Zone) (string, error) DeleteDNSRecord(record *domain.Record, clusterName string) error DeleteLoadBalancer(loadBalancer *lb.LB) error @@ -333,6 +335,9 @@ func (s *scwCloudImplementation) GetApiIngressStatus(cluster *kops.Cluster) ([]f for _, loadBalancer := range responseLoadBalancers.LBs { for _, lbIP := range loadBalancer.IP { + if net.IsIPv6String(lbIP.IPAddress) { + continue + } ingresses = append(ingresses, fi.ApiIngressStatus{IP: lbIP.IPAddress}) } } @@ -407,11 +412,20 @@ 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) + + externalIP, err := s.GetServerIP(server.ID, server.Zone) + if err != nil { + return nil, fmt.Errorf("getting server public IP: %w", err) + } + cloudInstance.ExternalIP = externalIP + 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 = privateIP + if privateIP == "" { + cloudInstance.PrivateIP = externalIP } - cloudInstance.PrivateIP = ip } return cloudInstanceGroup, nil @@ -496,23 +510,43 @@ func (s *scwCloudImplementation) GetClusterVolumes(clusterName string) ([]*insta } func (s *scwCloudImplementation) GetServerIP(serverID string, zone scw.Zone) (string, error) { + return GetIPAMPublicIP(s.ipamAPI, serverID, zone) +} + +func (s *scwCloudImplementation) GetServerPrivateIP(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) } + server, err := s.instanceAPI.GetServer(&instance.GetServerRequest{ + Zone: zone, + ServerID: serverID, + }) + if err != nil { + return "", fmt.Errorf("getting server %s: %w", serverID, err) + } + + if len(server.Server.PrivateNics) == 0 { + // If there is no private NIC, there is no private IP so no need to return an error + return "", nil + } + if len(server.Server.PrivateNics) > 1 { + return "", fmt.Errorf("expected 1 private nic for server %s, got %d", serverID, len(server.Server.PrivateNics)) + } + privateNicID := server.Server.PrivateNics[0].ID + ips, err := s.ipamAPI.ListIPs(&ipam.ListIPsRequest{ Region: region, IsIPv6: fi.PtrTo(false), - ResourceID: &serverID, - Zonal: fi.PtrTo(zone.String()), + ResourceID: &privateNicID, }, scw.WithAllPages()) if err != nil { - return "", fmt.Errorf("listing IPs for server %s: %w", serverID, err) + return "", fmt.Errorf("listing IPs for private nic %s: %w", privateNicID, err) } if len(ips.IPs) < 1 { - return "", fmt.Errorf("could not find IP for server %s", serverID) + return "", fmt.Errorf("could not find IP for private nic %s", privateNicID) } 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()) diff --git a/upup/pkg/fi/cloudup/scaleway/utils.go b/upup/pkg/fi/cloudup/scaleway/utils.go index 5390164f5ff6a..d403857a546ef 100644 --- a/upup/pkg/fi/cloudup/scaleway/utils.go +++ b/upup/pkg/fi/cloudup/scaleway/utils.go @@ -23,8 +23,11 @@ import ( "os" "strings" + "github.com/scaleway/scaleway-sdk-go/api/ipam/v1" "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 +173,44 @@ func CreateValidScalewayProfile() (*scw.Profile, error) { return &profile, nil } + +func GetIPAMPublicIP(ipamAPI *ipam.API, serverID string, zone scw.Zone) (string, error) { + if ipamAPI == 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 +} diff --git a/upup/pkg/fi/cloudup/scaleway/verifier.go b/upup/pkg/fi/cloudup/scaleway/verifier.go index 0045830bb4fdb..b7ced0924dd36 100644 --- a/upup/pkg/fi/cloudup/scaleway/verifier.go +++ b/upup/pkg/fi/cloudup/scaleway/verifier.go @@ -25,7 +25,7 @@ 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/api/ipam/v1" "github.com/scaleway/scaleway-sdk-go/scw" kopsv "k8s.io/kops" "k8s.io/kops/pkg/bootstrap" diff --git a/vendor/github.com/scaleway/scaleway-sdk-go/api/instance/v1/instance_sdk.go b/vendor/github.com/scaleway/scaleway-sdk-go/api/instance/v1/instance_sdk.go index 10ba03753f37b..03d83b08e94bd 100644 --- a/vendor/github.com/scaleway/scaleway-sdk-go/api/instance/v1/instance_sdk.go +++ b/vendor/github.com/scaleway/scaleway-sdk-go/api/instance/v1/instance_sdk.go @@ -1341,8 +1341,8 @@ type Server struct { // RoutedIPEnabled: true to configure the instance so it uses the new routed IP mode. RoutedIPEnabled bool `json:"routed_ip_enabled"` - // EnableIPv6: true if IPv6 is enabled. - EnableIPv6 bool `json:"enable_ipv6"` + // Deprecated: EnableIPv6: true if IPv6 is enabled (deprecated and always `False` when `routed_ip_enabled` is `True`). + EnableIPv6 *bool `json:"enable_ipv6"` // Hostname: instance host name. Hostname string `json:"hostname"` @@ -1353,10 +1353,10 @@ type Server struct { // Protected: defines whether the Instance protection option is activated. Protected bool `json:"protected"` - // PrivateIP: private IP address of the Instance. + // PrivateIP: private IP address of the Instance (deprecated and always `null` when `routed_ip_enabled` is `True`). PrivateIP *string `json:"private_ip"` - // PublicIP: information about the public IP. + // Deprecated: PublicIP: information about the public IP (deprecated in favor of `public_ips`). PublicIP *ServerIP `json:"public_ip"` // PublicIPs: information about all the public IPs attached to the server. @@ -1375,7 +1375,7 @@ type Server struct { // Location: instance location. Location *ServerLocation `json:"location"` - // IPv6: instance IPv6 address. + // Deprecated: IPv6: instance IPv6 address (deprecated when `routed_ip_enabled` is `True`). IPv6 *ServerIPv6 `json:"ipv6"` // Deprecated: Bootscript: instance bootscript. @@ -2090,10 +2090,10 @@ type CreateServerRequest struct { // Volumes: volumes attached to the server. Volumes map[string]*VolumeServerTemplate `json:"volumes,omitempty"` - // EnableIPv6: true if IPv6 is enabled on the server. - EnableIPv6 bool `json:"enable_ipv6,omitempty"` + // Deprecated: EnableIPv6: true if IPv6 is enabled on the server (deprecated and always `False` when `routed_ip_enabled` is `True`). + EnableIPv6 *bool `json:"enable_ipv6,omitempty"` - // PublicIP: ID of the reserved IP to attach to the Instance. + // Deprecated: PublicIP: ID of the reserved IP to attach to the Instance. PublicIP *string `json:"public_ip,omitempty"` // PublicIPs: a list of reserved IP IDs to attach to the Instance. @@ -2956,12 +2956,15 @@ type ListServersRequest struct { // Name: filter Instances by name (eg. "server1" will return "server100" and "server1" but not "foo"). Name *string `json:"-"` - // PrivateIP: list Instances by private_ip. + // Deprecated: PrivateIP: list Instances by private_ip. PrivateIP *net.IP `json:"-"` // WithoutIP: list Instances that are not attached to a public IP. WithoutIP *bool `json:"-"` + // WithIP: list Instances by IP (both private_ip and public_ip are supported). + WithIP *net.IP `json:"-"` + // CommercialType: list Instances of this commercial type. CommercialType *string `json:"-"` @@ -3596,6 +3599,7 @@ type UpdateServerRequest struct { // PublicIPs: a list of reserved IP IDs to attach to the Instance. PublicIPs *[]string `json:"public_ips,omitempty"` + // Deprecated EnableIPv6 *bool `json:"enable_ipv6,omitempty"` Protected *bool `json:"protected,omitempty"` @@ -3797,8 +3801,8 @@ type setServerRequest struct { // RoutedIPEnabled: true to configure the instance so it uses the new routed IP mode (once this is set to True you cannot set it back to False). RoutedIPEnabled *bool `json:"routed_ip_enabled,omitempty"` - // EnableIPv6: true if IPv6 is enabled. - EnableIPv6 bool `json:"enable_ipv6"` + // Deprecated: EnableIPv6: true if IPv6 is enabled (deprecated and always `False` when `routed_ip_enabled` is `True`). + EnableIPv6 *bool `json:"enable_ipv6,omitempty"` // Hostname: instance host name. Hostname string `json:"hostname"` @@ -3809,10 +3813,10 @@ type setServerRequest struct { // Protected: instance protection option is activated. Protected bool `json:"protected"` - // PrivateIP: instance private IP address. + // Deprecated: PrivateIP: instance private IP address (deprecated and always `null` when `routed_ip_enabled` is `True`). PrivateIP *string `json:"private_ip,omitempty"` - // PublicIP: information about the public IP. + // Deprecated: PublicIP: information about the public IP (deprecated in favor of `public_ips`). PublicIP *ServerIP `json:"public_ip,omitempty"` // PublicIPs: information about all the public IPs attached to the server. @@ -3828,7 +3832,7 @@ type setServerRequest struct { // Location: instance location. Location *ServerLocation `json:"location,omitempty"` - // IPv6: instance IPv6 address. + // Deprecated: IPv6: instance IPv6 address (deprecated when `routed_ip_enabled` is `True`). IPv6 *ServerIPv6 `json:"ipv6,omitempty"` // Deprecated: Bootscript: instance bootscript. @@ -4031,6 +4035,7 @@ func (s *API) ListServers(req *ListServersRequest, opts ...scw.RequestOption) (* parameter.AddToQuery(query, "name", req.Name) parameter.AddToQuery(query, "private_ip", req.PrivateIP) parameter.AddToQuery(query, "without_ip", req.WithoutIP) + parameter.AddToQuery(query, "with_ip", req.WithIP) parameter.AddToQuery(query, "commercial_type", req.CommercialType) parameter.AddToQuery(query, "state", req.State) if len(req.Tags) != 0 { diff --git a/vendor/github.com/scaleway/scaleway-sdk-go/api/ipam/v1/ipam_sdk.go b/vendor/github.com/scaleway/scaleway-sdk-go/api/ipam/v1/ipam_sdk.go new file mode 100644 index 0000000000000..901e6d3097f90 --- /dev/null +++ b/vendor/github.com/scaleway/scaleway-sdk-go/api/ipam/v1/ipam_sdk.go @@ -0,0 +1,530 @@ +// This file was automatically generated. DO NOT EDIT. +// If you have any remark or suggestion do not hesitate to open an issue. + +// Package ipam provides methods and message types of the ipam v1 API. +package ipam + +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 ListIPsRequestOrderBy string + +const ( + ListIPsRequestOrderByCreatedAtDesc = ListIPsRequestOrderBy("created_at_desc") + ListIPsRequestOrderByCreatedAtAsc = ListIPsRequestOrderBy("created_at_asc") + ListIPsRequestOrderByUpdatedAtDesc = ListIPsRequestOrderBy("updated_at_desc") + ListIPsRequestOrderByUpdatedAtAsc = ListIPsRequestOrderBy("updated_at_asc") + ListIPsRequestOrderByAttachedAtDesc = ListIPsRequestOrderBy("attached_at_desc") + ListIPsRequestOrderByAttachedAtAsc = ListIPsRequestOrderBy("attached_at_asc") +) + +func (enum ListIPsRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "created_at_desc" + } + 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 ResourceType string + +const ( + ResourceTypeUnknownType = ResourceType("unknown_type") + ResourceTypeInstanceServer = ResourceType("instance_server") + ResourceTypeInstanceIP = ResourceType("instance_ip") + ResourceTypeInstancePrivateNic = ResourceType("instance_private_nic") + ResourceTypeLBServer = ResourceType("lb_server") + ResourceTypeFipIP = ResourceType("fip_ip") + ResourceTypeVpcGateway = ResourceType("vpc_gateway") + ResourceTypeVpcGatewayNetwork = ResourceType("vpc_gateway_network") + ResourceTypeK8sNode = ResourceType("k8s_node") + ResourceTypeK8sCluster = ResourceType("k8s_cluster") + ResourceTypeRdbInstance = ResourceType("rdb_instance") + ResourceTypeRedisCluster = ResourceType("redis_cluster") + ResourceTypeBaremetalServer = ResourceType("baremetal_server") + ResourceTypeBaremetalPrivateNic = ResourceType("baremetal_private_nic") + ResourceTypeLlmDeployment = ResourceType("llm_deployment") +) + +func (enum ResourceType) String() string { + if enum == "" { + // return default value if empty + return "unknown_type" + } + return string(enum) +} + +func (enum ResourceType) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ResourceType) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ResourceType(ResourceType(tmp).String()) + return nil +} + +// Resource: resource. +type Resource struct { + // Type: type of resource the IP is attached to. + // Default value: unknown_type + Type ResourceType `json:"type"` + + // ID: ID of the resource the IP is attached to. + ID string `json:"id"` + + // MacAddress: mAC of the resource the IP is attached to. + MacAddress *string `json:"mac_address"` + + // Name: when the IP is in a Private Network, then a DNS record is available to resolve the resource name to this IP. + Name *string `json:"name"` +} + +// Reverse: reverse. +type Reverse struct { + // Hostname: reverse domain name. + Hostname string `json:"hostname"` + + // Address: IP corresponding to the hostname. + Address *net.IP `json:"address"` +} + +// Source: source. +type Source struct { + // Zonal: this source is global. + // Precisely one of Zonal, PrivateNetworkID, SubnetID must be set. + Zonal *string `json:"zonal,omitempty"` + + // PrivateNetworkID: this source is specific. + // Precisely one of Zonal, PrivateNetworkID, SubnetID must be set. + PrivateNetworkID *string `json:"private_network_id,omitempty"` + + // SubnetID: this source is specific. + // Precisely one of Zonal, PrivateNetworkID, SubnetID must be set. + SubnetID *string `json:"subnet_id,omitempty"` +} + +// IP: ip. +type IP struct { + // ID: IP ID. + ID string `json:"id"` + + // Address: iPv4 or IPv6 address in CIDR notation. + Address scw.IPNet `json:"address"` + + // ProjectID: scaleway Project the IP belongs to. + ProjectID string `json:"project_id"` + + // IsIPv6: defines whether the IP is an IPv6 (false = IPv4). + IsIPv6 bool `json:"is_ipv6"` + + // CreatedAt: date the IP was booked. + CreatedAt *time.Time `json:"created_at"` + + // UpdatedAt: date the IP was last modified. + UpdatedAt *time.Time `json:"updated_at"` + + // Source: source pool where the IP was booked in. + Source *Source `json:"source"` + + // Resource: resource which the IP is attached to. + Resource *Resource `json:"resource"` + + // Tags: tags for the IP. + Tags []string `json:"tags"` + + // Reverses: array of reverses associated with the IP. + Reverses []*Reverse `json:"reverses"` + + // Region: region of the IP. + Region scw.Region `json:"region"` + + // Zone: zone of the IP, if zonal. + Zone *scw.Zone `json:"zone"` +} + +// BookIPRequest: book ip request. +type BookIPRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // ProjectID: when creating an IP in a Private Network, the Project must match the Private Network's Project. + ProjectID string `json:"project_id"` + + // Source: source in which to book the IP. Not all sources are available for booking. + Source *Source `json:"source"` + + // IsIPv6: request an IPv6 instead of an IPv4. + IsIPv6 bool `json:"is_ipv6"` + + // Address: note that only the Private Network source allows you to pick a specific IP. If the requested IP is already booked, then the call will fail. + Address *net.IP `json:"address,omitempty"` + + // Tags: tags for the IP. + Tags []string `json:"tags"` +} + +// GetIPRequest: get ip request. +type GetIPRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // IPID: IP ID. + IPID string `json:"-"` +} + +// ListIPsRequest: list i ps request. +type ListIPsRequest 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 IPs. + // Default value: created_at_desc + OrderBy ListIPsRequestOrderBy `json:"-"` + + // Page: page number to return, from the paginated results. + Page *int32 `json:"-"` + + // PageSize: maximum number of IPs to return per page. + PageSize *uint32 `json:"-"` + + // ProjectID: project ID to filter for. Only IPs belonging to this Project will be returned. + ProjectID *string `json:"-"` + + // Zonal: zone to filter for. Only IPs that are zonal, and in this zone, will be returned. + // Precisely one of Zonal, PrivateNetworkID must be set. + Zonal *string `json:"zonal,omitempty"` + + // PrivateNetworkID: only IPs that are private, and in this Private Network, will be returned. + // Precisely one of Zonal, PrivateNetworkID must be set. + PrivateNetworkID *string `json:"private_network_id,omitempty"` + + // Attached: defines whether to filter only for IPs which are attached to a resource. + Attached *bool `json:"-"` + + // ResourceID: resource ID to filter for. Only IPs attached to this resource will be returned. + ResourceID *string `json:"-"` + + // ResourceType: resource type to filter for. Only IPs attached to this type of resource will be returned. + // Default value: unknown_type + ResourceType ResourceType `json:"-"` + + // MacAddress: mAC address to filter for. Only IPs attached to a resource with this MAC address will be returned. + MacAddress *string `json:"-"` + + // Tags: tags to filter for, only IPs with one or more matching tags will be returned. + Tags []string `json:"-"` + + // OrganizationID: organization ID to filter for. Only IPs belonging to this Organization will be returned. + OrganizationID *string `json:"-"` + + // IsIPv6: defines whether to filter only for IPv4s or IPv6s. + IsIPv6 *bool `json:"-"` + + // ResourceName: attached resource name to filter for, only IPs attached to a resource with this string within their name will be returned. + ResourceName *string `json:"-"` +} + +// ListIPsResponse: list i ps response. +type ListIPsResponse struct { + TotalCount uint64 `json:"total_count"` + + IPs []*IP `json:"ips"` +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListIPsResponse) UnsafeGetTotalCount() uint64 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListIPsResponse) UnsafeAppend(res interface{}) (uint64, 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 += uint64(len(results.IPs)) + return uint64(len(results.IPs)), nil +} + +// ReleaseIPRequest: release ip request. +type ReleaseIPRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // IPID: IP ID. + IPID string `json:"-"` +} + +// UpdateIPRequest: update ip request. +type UpdateIPRequest struct { + // Region: region to target. If none is passed will use default region from the config. + Region scw.Region `json:"-"` + + // IPID: IP ID. + IPID string `json:"-"` + + // Tags: tags for the IP. + Tags *[]string `json:"tags,omitempty"` + + // Reverses: array of reverse domain names associated with an IP in the subnet of the current IP. + Reverses []*Reverse `json:"reverses"` +} + +// This API allows you to manage IP addresses with Scaleway's IP Address Management tool. +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} +} + +// BookIP: Book a new IP from the specified source. Currently IPs can only be booked from a Private Network. +func (s *API) BookIP(req *BookIPRequest, opts ...scw.RequestOption) (*IP, 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 fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/ipam/v1/regions/" + fmt.Sprint(req.Region) + "/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 +} + +// ReleaseIP: Release an IP not currently attached to a resource, and returns it to the available IP pool. +func (s *API) ReleaseIP(req *ReleaseIPRequest, 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.IPID) == "" { + return errors.New("field IPID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/ipam/v1/regions/" + fmt.Sprint(req.Region) + "/ips/" + fmt.Sprint(req.IPID) + "", + } + + err = scwReq.SetBody(req) + if err != nil { + return err + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +// GetIP: Retrieve details of an existing IP, specified by its IP ID. +func (s *API) GetIP(req *GetIPRequest, opts ...scw.RequestOption) (*IP, 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.IPID) == "" { + return nil, errors.New("field IPID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/ipam/v1/regions/" + fmt.Sprint(req.Region) + "/ips/" + fmt.Sprint(req.IPID) + "", + } + + var resp IP + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// UpdateIP: Update parameters including tags of the specified IP. +func (s *API) UpdateIP(req *UpdateIPRequest, opts ...scw.RequestOption) (*IP, 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.IPID) == "" { + return nil, errors.New("field IPID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/ipam/v1/regions/" + fmt.Sprint(req.Region) + "/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 +} + +// ListIPs: List existing IPs in the specified region using various filters. For example, you can filter for IPs within a specified Private Network, or for public IPs within a specified Project. By default, the IPs 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) ListIPs(req *ListIPsRequest, opts ...scw.RequestOption) (*ListIPsResponse, 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, "project_id", req.ProjectID) + parameter.AddToQuery(query, "attached", req.Attached) + parameter.AddToQuery(query, "resource_id", req.ResourceID) + parameter.AddToQuery(query, "resource_type", req.ResourceType) + parameter.AddToQuery(query, "mac_address", req.MacAddress) + parameter.AddToQuery(query, "tags", req.Tags) + parameter.AddToQuery(query, "organization_id", req.OrganizationID) + parameter.AddToQuery(query, "is_ipv6", req.IsIPv6) + parameter.AddToQuery(query, "resource_name", req.ResourceName) + parameter.AddToQuery(query, "zonal", req.Zonal) + parameter.AddToQuery(query, "private_network_id", req.PrivateNetworkID) + + if fmt.Sprint(req.Region) == "" { + return nil, errors.New("field Region cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/ipam/v1/regions/" + fmt.Sprint(req.Region) + "/ips", + Query: query, + } + + var resp ListIPsResponse + + 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/ipam/v1alpha1/ipam_sdk.go b/vendor/github.com/scaleway/scaleway-sdk-go/api/ipam/v1alpha1/ipam_sdk.go deleted file mode 100644 index 05e789b03e0b7..0000000000000 --- a/vendor/github.com/scaleway/scaleway-sdk-go/api/ipam/v1alpha1/ipam_sdk.go +++ /dev/null @@ -1,318 +0,0 @@ -// This file was automatically generated. DO NOT EDIT. -// If you have any remark or suggestion do not hesitate to open an issue. - -// Package ipam provides methods and message types of the ipam v1alpha1 API. -package ipam - -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 ListIPsRequestOrderBy string - -const ( - ListIPsRequestOrderByCreatedAtDesc = ListIPsRequestOrderBy("created_at_desc") - ListIPsRequestOrderByCreatedAtAsc = ListIPsRequestOrderBy("created_at_asc") - ListIPsRequestOrderByUpdatedAtDesc = ListIPsRequestOrderBy("updated_at_desc") - ListIPsRequestOrderByUpdatedAtAsc = ListIPsRequestOrderBy("updated_at_asc") - ListIPsRequestOrderByAttachedAtDesc = ListIPsRequestOrderBy("attached_at_desc") - ListIPsRequestOrderByAttachedAtAsc = ListIPsRequestOrderBy("attached_at_asc") -) - -func (enum ListIPsRequestOrderBy) String() string { - if enum == "" { - // return default value if empty - return "created_at_desc" - } - 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 ResourceType string - -const ( - ResourceTypeUnknownType = ResourceType("unknown_type") - ResourceTypeInstanceServer = ResourceType("instance_server") - ResourceTypeInstanceIP = ResourceType("instance_ip") - ResourceTypeInstancePrivateNic = ResourceType("instance_private_nic") - ResourceTypeLBServer = ResourceType("lb_server") - ResourceTypeFipIP = ResourceType("fip_ip") - ResourceTypeVpcGateway = ResourceType("vpc_gateway") - ResourceTypeVpcGatewayNetwork = ResourceType("vpc_gateway_network") - ResourceTypeK8sNode = ResourceType("k8s_node") - ResourceTypeK8sCluster = ResourceType("k8s_cluster") - ResourceTypeRdbInstance = ResourceType("rdb_instance") - ResourceTypeRedisCluster = ResourceType("redis_cluster") - ResourceTypeBaremetalServer = ResourceType("baremetal_server") - ResourceTypeBaremetalPrivateNic = ResourceType("baremetal_private_nic") - ResourceTypeLlmDeployment = ResourceType("llm_deployment") -) - -func (enum ResourceType) String() string { - if enum == "" { - // return default value if empty - return "unknown_type" - } - return string(enum) -} - -func (enum ResourceType) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"%s"`, enum)), nil -} - -func (enum *ResourceType) UnmarshalJSON(data []byte) error { - tmp := "" - - if err := json.Unmarshal(data, &tmp); err != nil { - return err - } - - *enum = ResourceType(ResourceType(tmp).String()) - return nil -} - -// Resource: resource. -type Resource struct { - // Type: default value: unknown_type - Type ResourceType `json:"type"` - - ID string `json:"id"` - - MacAddress *string `json:"mac_address"` - - Name *string `json:"name"` -} - -// Reverse: reverse. -type Reverse struct { - Hostname string `json:"hostname"` - - Address *net.IP `json:"address"` -} - -// IP: ip. -type IP struct { - ID string `json:"id"` - - Address scw.IPNet `json:"address"` - - ProjectID string `json:"project_id"` - - IsIPv6 bool `json:"is_ipv6"` - - CreatedAt *time.Time `json:"created_at"` - - UpdatedAt *time.Time `json:"updated_at"` - - // Precisely one of Regional, Zonal, ZonalNat, SubnetID must be set. - Regional *bool `json:"regional,omitempty"` - - // Precisely one of Regional, Zonal, ZonalNat, SubnetID must be set. - Zonal *string `json:"zonal,omitempty"` - - // Precisely one of Regional, Zonal, ZonalNat, SubnetID must be set. - ZonalNat *string `json:"zonal_nat,omitempty"` - - // Precisely one of Regional, Zonal, ZonalNat, SubnetID must be set. - SubnetID *string `json:"subnet_id,omitempty"` - - Resource *Resource `json:"resource"` - - Tags []string `json:"tags"` - - Reverses []*Reverse `json:"reverses"` - - // Region: region to target. If none is passed will use default region from the config. - Region scw.Region `json:"region"` - - // Zone: zone to target. If none is passed will use default zone from the config. - Zone *scw.Zone `json:"zone"` -} - -// ListIPsRequest: list i ps request. -type ListIPsRequest struct { - // Region: region to target. If none is passed will use default region from the config. - Region scw.Region `json:"-"` - - Page *int32 `json:"-"` - - PageSize *uint32 `json:"-"` - - // OrderBy: default value: created_at_desc - OrderBy ListIPsRequestOrderBy `json:"-"` - - ProjectID *string `json:"-"` - - OrganizationID *string `json:"-"` - - // Precisely one of Zonal, ZonalNat, Regional, PrivateNetworkID, SubnetID must be set. - Zonal *string `json:"zonal,omitempty"` - - // Precisely one of Zonal, ZonalNat, Regional, PrivateNetworkID, SubnetID must be set. - ZonalNat *string `json:"zonal_nat,omitempty"` - - // Precisely one of Zonal, ZonalNat, Regional, PrivateNetworkID, SubnetID must be set. - Regional *bool `json:"regional,omitempty"` - - // Precisely one of Zonal, ZonalNat, Regional, PrivateNetworkID, SubnetID must be set. - PrivateNetworkID *string `json:"private_network_id,omitempty"` - - // Precisely one of Zonal, ZonalNat, Regional, PrivateNetworkID, SubnetID must be set. - SubnetID *string `json:"subnet_id,omitempty"` - - Attached *bool `json:"-"` - - ResourceID *string `json:"-"` - - // ResourceType: default value: unknown_type - ResourceType ResourceType `json:"-"` - - MacAddress *string `json:"-"` - - Tags *[]string `json:"-"` - - IsIPv6 *bool `json:"-"` - - ResourceName *string `json:"-"` - - ResourceIDs []string `json:"-"` -} - -// ListIPsResponse: list i ps response. -type ListIPsResponse struct { - TotalCount uint64 `json:"total_count"` - - IPs []*IP `json:"ips"` -} - -// UnsafeGetTotalCount should not be used -// Internal usage only -func (r *ListIPsResponse) UnsafeGetTotalCount() uint64 { - return r.TotalCount -} - -// UnsafeAppend should not be used -// Internal usage only -func (r *ListIPsResponse) UnsafeAppend(res interface{}) (uint64, 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 += uint64(len(results.IPs)) - return uint64(len(results.IPs)), nil -} - -// IPAM 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} -} - -// ListIPs: Find IP addresses. -func (s *API) ListIPs(req *ListIPsRequest, opts ...scw.RequestOption) (*ListIPsResponse, 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, "page", req.Page) - parameter.AddToQuery(query, "page_size", req.PageSize) - parameter.AddToQuery(query, "order_by", req.OrderBy) - parameter.AddToQuery(query, "project_id", req.ProjectID) - parameter.AddToQuery(query, "organization_id", req.OrganizationID) - parameter.AddToQuery(query, "attached", req.Attached) - parameter.AddToQuery(query, "resource_id", req.ResourceID) - parameter.AddToQuery(query, "resource_type", req.ResourceType) - parameter.AddToQuery(query, "mac_address", req.MacAddress) - parameter.AddToQuery(query, "tags", req.Tags) - parameter.AddToQuery(query, "is_ipv6", req.IsIPv6) - parameter.AddToQuery(query, "resource_name", req.ResourceName) - parameter.AddToQuery(query, "resource_ids", req.ResourceIDs) - parameter.AddToQuery(query, "zonal", req.Zonal) - parameter.AddToQuery(query, "zonal_nat", req.ZonalNat) - parameter.AddToQuery(query, "regional", req.Regional) - parameter.AddToQuery(query, "private_network_id", req.PrivateNetworkID) - parameter.AddToQuery(query, "subnet_id", req.SubnetID) - - if fmt.Sprint(req.Region) == "" { - return nil, errors.New("field Region cannot be empty in request") - } - - scwReq := &scw.ScalewayRequest{ - Method: "GET", - Path: "/ipam/v1alpha1/regions/" + fmt.Sprint(req.Region) + "/ips", - Query: query, - } - - var resp ListIPsResponse - - err = s.client.Do(scwReq, &resp, opts...) - if err != nil { - return nil, err - } - return &resp, nil -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 741b6d99f767b..7eacfded85a55 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1035,13 +1035,13 @@ github.com/sagikazarmark/slog-shim # github.com/sahilm/fuzzy v0.1.0 ## explicit github.com/sahilm/fuzzy -# github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26 +# github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26.0.20240422085138-c14591c368f2 ## explicit; go 1.17 github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1 github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1 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/ipam/v1alpha1 +github.com/scaleway/scaleway-sdk-go/api/ipam/v1 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