diff --git a/.gitignore b/.gitignore index 9d6d2ebf0a576..6415cfa64f93e 100644 --- a/.gitignore +++ b/.gitignore @@ -93,3 +93,8 @@ _output # Used by E2E testing _artifacts _rundir + +# TODO(Mia-Cross): make this clean before PR#1 +s3: +zzzs3-state-store +.run/ \ No newline at end of file diff --git a/Makefile b/Makefile index 94a6f9a26aba8..7344eb2f8f10e 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,8 @@ # kops source root directory (without trailing /) KOPS_ROOT?=$(patsubst %/,%,$(abspath $(dir $(firstword $(MAKEFILE_LIST))))) -DOCKER_REGISTRY?=gcr.io/must-override -S3_BUCKET?=s3://must-override/ +DOCKER_REGISTRY?=rg.fr-par.scw.cloud +S3_BUCKET?=scw://kops-images/ UPLOAD_DEST?=$(S3_BUCKET) GCS_LOCATION?=gs://must-override GCS_URL=$(GCS_LOCATION:gs://%=https://storage.googleapis.com/%) diff --git a/dns-controller/cmd/dns-controller/main.go b/dns-controller/cmd/dns-controller/main.go index 265bd2fd07c24..0554c4c658545 100644 --- a/dns-controller/cmd/dns-controller/main.go +++ b/dns-controller/cmd/dns-controller/main.go @@ -37,6 +37,7 @@ import ( "k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/aws/route53" _ "k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/do" _ "k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/google/clouddns" + _ "k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/scaleway" "k8s.io/kops/pkg/wellknownports" "k8s.io/kops/protokube/pkg/gossip" gossipdns "k8s.io/kops/protokube/pkg/gossip/dns" diff --git a/dnsprovider/pkg/dnsprovider/providers/scaleway/dns.go b/dnsprovider/pkg/dnsprovider/providers/scaleway/dns.go new file mode 100644 index 0000000000000..542b90f7a87b6 --- /dev/null +++ b/dnsprovider/pkg/dnsprovider/providers/scaleway/dns.go @@ -0,0 +1,527 @@ +package dns + +import ( + "context" + "fmt" + "io" + "os" + + "golang.org/x/oauth2" + "k8s.io/klog/v2" + kopsv "k8s.io/kops" + "k8s.io/kops/dns-controller/pkg/dns" + "k8s.io/kops/dnsprovider/pkg/dnsprovider" + "k8s.io/kops/dnsprovider/pkg/dnsprovider/rrstype" + + "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +var _ dnsprovider.Interface = Interface{} + +const ( + ProviderName = "scaleway" +) + +func init() { + dnsprovider.RegisterDNSProvider(ProviderName, func(config io.Reader) (dnsprovider.Interface, error) { + client, err := newClient() + if err != nil { + return nil, err + } + + return NewProvider(client), nil + }) +} + +// TokenSource implements oauth2.TokenSource +type TokenSource struct { + AccessToken string +} + +// Token returns oauth2.Token +func (t *TokenSource) Token() (*oauth2.Token, error) { + token := &oauth2.Token{ + AccessToken: t.AccessToken, + } + return token, nil +} + +func newClient() (*scw.Client, error) { + if accessKey := os.Getenv("SCW_ACCESS_KEY"); accessKey == "" { + return nil, fmt.Errorf("SCW_ACCESS_KEY is required") + } + if secretKey := os.Getenv("SCW_SECRET_KEY"); secretKey == "" { + return nil, fmt.Errorf("SCW_SECRET_KEY is required") + } + + scwClient, err := scw.NewClient( + scw.WithUserAgent("kubernetes-kops/"+kopsv.Version), + scw.WithEnv(), + ) + if err != nil { + return nil, err + } + + return scwClient, nil +} + +// Interface implements dnsprovider.Interface +type Interface struct { + client *scw.Client +} + +// NewProvider returns an implementation of dnsprovider.Interface +func NewProvider(client *scw.Client) dnsprovider.Interface { + return &Interface{client: client} +} + +// Zones returns an implementation of dnsprovider.Zones +func (d Interface) Zones() (dnsprovider.Zones, bool) { + return &zones{ + client: d.client, + }, true +} + +// zones is an implementation of dnsprovider.Zones +type zones struct { + client *scw.Client +} + +// List returns a list of all dns zones +func (z *zones) List() ([]dnsprovider.Zone, error) { + domains, err := listDomains(z.client) + if err != nil { + return nil, err + } + + var newZone *zone + var zones []dnsprovider.Zone + for _, domainSummary := range domains { + newZone = &zone{ + name: domainSummary.Domain, + client: z.client, + } + zones = append(zones, newZone) + } + + return zones, nil +} + +// Add adds a new DNS zone +func (z *zones) Add(newZone dnsprovider.Zone) (dnsprovider.Zone, error) { + domainCreateRequest := &domain.CreateDNSZoneRequest{ + Subdomain: newZone.Name(), + Domain: os.Getenv("SCW_DNS_ZONE"), + } + + klog.V(8).Infof("Adding new DNS zone %s to domain %s", newZone.Name(), os.Getenv("SCW_DNS_ZONE")) + d, err := createDomain(z.client, domainCreateRequest) + if err != nil { + return nil, err + } + klog.V(4).Infof("Added new DNS zone %s to domain %s", d.Subdomain, d.Domain) + + return &zone{ + name: d.Subdomain, + client: z.client, + }, nil +} + +// Remove deletes a zone +func (z *zones) Remove(zone dnsprovider.Zone) error { + return deleteDomain(z.client, zone.Name()+"."+os.Getenv("SCW_DNS_ZONE")) +} + +// New returns a new implementation of dnsprovider.Zone +func (z *zones) New(name string) (dnsprovider.Zone, error) { + return &zone{ + name: name, + client: z.client, + }, nil +} + +// zone implements dnsprovider.Zone +type zone struct { + name string + client *scw.Client + //id string +} + +// Name returns the Name of a dns zone +func (z *zone) Name() string { + return z.name +} + +// ID returns the ID of a dns zone, here we use the name as an identifier +func (z *zone) ID() string { + return z.name +} + +// ResourceRecordSets returns an implementation of dnsprovider.ResourceRecordSets +func (z *zone) ResourceRecordSets() (dnsprovider.ResourceRecordSets, bool) { + return &resourceRecordSets{zone: z, client: z.client}, true +} + +// resourceRecordSets implements dnsprovider.ResourceRecordSet +type resourceRecordSets struct { + zone *zone + client *scw.Client +} + +// List returns a list of dnsprovider.ResourceRecordSet +func (r *resourceRecordSets) List() ([]dnsprovider.ResourceRecordSet, error) { + records, err := getRecords(r.client, r.zone.Name()) + if err != nil { + return nil, err + } + + var rrsets []dnsprovider.ResourceRecordSet + rrsetsWithoutDups := make(map[string]*resourceRecordSet) + + for _, record := range records { + // The scaleway API returns the record without the zone + // but the consumers of this interface expect the zone to be included + recordName := dns.EnsureDotSuffix(record.Name) + r.Zone().Name() + if set, ok := rrsetsWithoutDups[recordName]; !ok { + rrsetsWithoutDups[recordName] = &resourceRecordSet{ + name: recordName, + data: []string{record.Data}, + ttl: int(record.TTL), + recordType: rrstype.RrsType(record.Type), + } + } else { + set.data = append(set.data, record.Data) + } + } + + for _, set := range rrsetsWithoutDups { + rrsets = append(rrsets, set) + } + + return rrsets, nil +} + +// Get returns a list of dnsprovider.ResourceRecordSet that matches the name parameter +func (r *resourceRecordSets) Get(name string) ([]dnsprovider.ResourceRecordSet, error) { + records, err := r.List() + if err != nil { + return nil, err + } + + var recordSets []dnsprovider.ResourceRecordSet + for _, record := range records { + if record.Name() == name { + recordSets = append(recordSets, record) + } + } + + return recordSets, nil +} + +// New returns an implementation of dnsprovider.ResourceRecordSet +func (r *resourceRecordSets) New(name string, rrdatas []string, ttl int64, rrstype rrstype.RrsType) dnsprovider.ResourceRecordSet { + if len(rrdatas) == 0 { + return nil + } + + return &resourceRecordSet{ + name: name, + data: rrdatas, + ttl: int(ttl), + recordType: rrstype, + } +} + +// StartChangeset returns an implementation of dnsprovider.ResourceRecordChangeset +func (r *resourceRecordSets) StartChangeset() dnsprovider.ResourceRecordChangeset { + return &resourceRecordChangeset{ + client: r.client, + zone: r.zone, + rrsets: r, + additions: []dnsprovider.ResourceRecordSet{}, + removals: []dnsprovider.ResourceRecordSet{}, + upserts: []dnsprovider.ResourceRecordSet{}, + } +} + +// Zone returns the associated implementation of dnsprovider.Zone +func (r *resourceRecordSets) Zone() dnsprovider.Zone { + return r.zone +} + +// recordRecordSet implements dnsprovider.ResourceRecordSet which represents +// a single record associated with a zone +type resourceRecordSet struct { + name string + data []string + ttl int + recordType rrstype.RrsType +} + +// Name returns the name of a resource record set +func (r *resourceRecordSet) Name() string { + return r.name +} + +// Rrdatas returns a list of data associated with a resource record set +func (r *resourceRecordSet) Rrdatas() []string { + return r.data +} + +// Ttl returns the time-to-live of a record +func (r *resourceRecordSet) Ttl() int64 { + return int64(r.ttl) +} + +// Type returns the type of record a resource record set is +func (r *resourceRecordSet) Type() rrstype.RrsType { + return r.recordType +} + +// resourceRecordChangeset implements dnsprovider.ResourceRecordChangeset +type resourceRecordChangeset struct { + client *scw.Client + zone *zone + rrsets dnsprovider.ResourceRecordSets + + additions []dnsprovider.ResourceRecordSet + removals []dnsprovider.ResourceRecordSet + upserts []dnsprovider.ResourceRecordSet +} + +// Add adds a new resource record set to the list of additions to apply +func (r *resourceRecordChangeset) Add(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset { + r.additions = append(r.additions, rrset) + return r +} + +// Remove adds a new resource record set to the list of removals to apply +func (r *resourceRecordChangeset) Remove(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset { + r.removals = append(r.removals, rrset) + return r +} + +// Upsert adds a new resource record set to the list of upserts to apply +func (r *resourceRecordChangeset) Upsert(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset { + r.upserts = append(r.upserts, rrset) + return r +} + +// Apply adds new records stored in r.additions, updates records stored +// in r.upserts and deletes records stored in r.removals +func (r *resourceRecordChangeset) Apply(ctx context.Context) error { + // Empty changesets should be a relatively quick no-op + if r.IsEmpty() { + klog.V(4).Info("record change set is empty") + return nil + } + + klog.V(2).Info("applying changes in record change set") + updateRecordsRequest := []*domain.RecordChange(nil) + dnsZone := os.Getenv("SCW_DNS_ZONE") + api := domain.NewAPI(r.client) + + records, err := getRecords(r.client, r.zone.Name()) + if err != nil { + return err + } + + if len(r.additions) > 0 { + recordsToAdd := []*domain.Record(nil) + for _, rrset := range r.additions { + for _, rrdata := range rrset.Rrdatas() { + recordsToAdd = append(recordsToAdd, &domain.Record{ + Name: rrset.Name(), + Data: rrdata, + TTL: uint32(rrset.Ttl()), + Type: domain.RecordType(rrset.Type()), + }) + } + klog.V(8).Infof("adding new DNS record %s to zone %s", rrset.Name(), r.zone.name) + updateRecordsRequest = append(updateRecordsRequest, &domain.RecordChange{ + Add: &domain.RecordChangeAdd{ + Records: recordsToAdd, + }, + }) + } + } + + if len(r.upserts) > 0 { + for _, rrset := range r.upserts { + for _, rrdata := range rrset.Rrdatas() { + for _, record := range records { + if record.Name == rrset.Name() { + klog.V(8).Infof("changing DNS record %s of zone %s", rrset.Name(), r.zone.name) + updateRecordsRequest = append(updateRecordsRequest, &domain.RecordChange{ + Set: &domain.RecordChangeSet{ + ID: &record.ID, + Records: []*domain.Record{ + { + Name: rrset.Name(), + Data: rrdata, + TTL: uint32(rrset.Ttl()), + Type: domain.RecordType(rrset.Type()), + }, + }, + }, + }) + } + } + } + } + } + + if len(r.removals) > 0 { + for _, rrset := range r.removals { + for _, record := range records { + if record.Name == rrset.Name() && record.Data == rrset.Rrdatas()[0] { + klog.V(8).Infof("removing DNS record %s of zone %s", rrset.Name(), r.zone.name) + updateRecordsRequest = append(updateRecordsRequest, &domain.RecordChange{ + Delete: &domain.RecordChangeDelete{ + ID: &record.ID, + }, + }) + } + + } + } + } + + _, err = api.UpdateDNSZoneRecords(&domain.UpdateDNSZoneRecordsRequest{ + DNSZone: dnsZone, + Changes: updateRecordsRequest, + }) + if err != nil { + return fmt.Errorf("failed to apply resource record set: %w", err) + } + + klog.V(2).Info("record change sets successfully applied") + return nil +} + +// IsEmpty returns true if a changeset is empty, false otherwise +func (r *resourceRecordChangeset) IsEmpty() bool { + if len(r.additions) == 0 && len(r.removals) == 0 && len(r.upserts) == 0 { + return true + } + + return false +} + +// ResourceRecordSets returns the associated resourceRecordSets of a changeset +func (r *resourceRecordChangeset) ResourceRecordSets() dnsprovider.ResourceRecordSets { + return r.rrsets +} + +// listDomains returns a list of scaleway Domain objects +func listDomains(c *scw.Client) ([]*domain.DNSZone, error) { + api := domain.NewAPI(c) + + domains, err := api.ListDNSZones(&domain.ListDNSZonesRequest{ + //Domain: "", + //DNSZone: "", + }, scw.WithAllPages()) + + if err != nil { + return nil, fmt.Errorf("failed to list domains: %v", err) + } + + return domains.DNSZones, err +} + +// createDomain creates a domain provided scw.DomainCreateRequest +func createDomain(c *scw.Client, createRequest *domain.CreateDNSZoneRequest) (*domain.DNSZone, error) { + api := domain.NewAPI(c) + + dnsZone, err := api.CreateDNSZone(createRequest) + + if err != nil { + return nil, err + } + + return dnsZone, nil +} + +// deleteDomain deletes a domain given its name +func deleteDomain(c *scw.Client, name string) error { + api := domain.NewAPI(c) + + _, err := api.DeleteDNSZone(&domain.DeleteDNSZoneRequest{ + DNSZone: name, + }) + if err != nil { + return err + } + + return nil +} + +// getRecords returns a list of scaleway records given a zone name (the name of the record doesn't end with the zone name) +func getRecords(c *scw.Client, zoneName string) ([]*domain.Record, error) { + api := domain.NewAPI(c) + + records, err := api.ListDNSZoneRecords(&domain.ListDNSZoneRecordsRequest{ + DNSZone: zoneName, + }, scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("failed to list records: %v", err) + } + + return records.Records, err +} + +//// getRecordsByName returns a list of domain Records based on the provided zone and name +//func getRecordsByName(client *scw.Client, zoneName, recordName string) ([]*domain.Record, error) { +// api := domain.NewAPI(client) +// +// records, err := api.ListDNSZoneRecords(&domain.ListDNSZoneRecordsRequest{ +// DNSZone: zoneName, +// Name: recordName, +// }, scw.WithAllPages()) +// if err != nil { +// return nil, fmt.Errorf("failed to list records: %v", err) +// } +// +// return records.Records, err +//} + +// createRecord creates a record given an associated zone and an UpdateDNSZoneRecordsRequest +func createRecord(c *scw.Client, recordsCreateRequest *domain.UpdateDNSZoneRecordsRequest) ([]string, error) { + api := domain.NewAPI(c) + + resp, err := api.UpdateDNSZoneRecords(recordsCreateRequest) + if err != nil { + return nil, fmt.Errorf("error creating record: %v", err) + } + + recordsIds := []string(nil) + for _, record := range resp.Records { + recordsIds = append(recordsIds, record.ID) + } + + return recordsIds, nil +} + +// deleteRecord deletes a record given an associated zone and a record ID +func deleteRecord(c *scw.Client, zoneName string, recordID string) error { + api := domain.NewAPI(c) + + recordDeleteRequest := &domain.UpdateDNSZoneRecordsRequest{ + DNSZone: zoneName, + Changes: []*domain.RecordChange{ + { + Delete: &domain.RecordChangeDelete{ + ID: &recordID, + }, + }, + }, + } + + _, err := api.UpdateDNSZoneRecords(recordDeleteRequest) + if err != nil { + return fmt.Errorf("error deleting record: %v", err) + } + + return nil +} diff --git a/dnsprovider/pkg/dnsprovider/providers/scaleway/dns_test.go b/dnsprovider/pkg/dnsprovider/providers/scaleway/dns_test.go new file mode 100644 index 0000000000000..ac7f71f3b533e --- /dev/null +++ b/dnsprovider/pkg/dnsprovider/providers/scaleway/dns_test.go @@ -0,0 +1,345 @@ +package dns + +import ( + "context" + "os" + "testing" + + domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1" + "github.com/scaleway/scaleway-sdk-go/scw" + "k8s.io/kops/dnsprovider/pkg/dnsprovider" + "k8s.io/kops/dnsprovider/pkg/dnsprovider/rrstype" +) + +const ( + validScalewayProfileName = "normal" + validDNSZone = "leila.sieben.fr" +) + +func createValidTestClient(t *testing.T) *scw.Client { + err := os.Setenv("SCW_DNS_ZONE", validDNSZone) + if err != nil { + t.Errorf("error setting DNS_ZONE in environment: %v", err) + } + config, _ := scw.LoadConfig() + profile := config.Profiles[validScalewayProfileName] + client, err := scw.NewClient(scw.WithProfile(profile)) + if err != nil { + t.Errorf("error creating client: %v", err) + } + return client +} + +func createInvalidTestClient(t *testing.T) *scw.Client { + client, err := scw.NewClient(scw.WithoutAuth()) + if err != nil { + t.Errorf("error creating client: %v", err) + } + return client +} + +func getDNSProviderZones(client *scw.Client) dnsprovider.Zones { + dnsProvider := NewProvider(client) + zs, _ := dnsProvider.Zones() + return zs +} + +func TestZonesListValid(t *testing.T) { + client := createValidTestClient(t) + z := &zones{client: client} + + zoneList, err := z.List() + + if err != nil { + t.Errorf("error listing zones: %v", err) + } + if len(zoneList) < 1 { + t.Errorf("expected at least 1 zone, got 0") + } + zone := zoneList[0] + if zone.Name() != validDNSZone { + t.Errorf("expected %s as zone name, got: %s", validDNSZone, zone.Name()) + } +} + +func TestZonesListShouldFail(t *testing.T) { + client := createInvalidTestClient(t) + z := &zones{client: client} + + zoneList, err := z.List() + + if err == nil { + t.Errorf("expected non-nil err") + } + if zoneList != nil { + t.Errorf("expected nil zone, got %v", zoneList) + } +} + +func TestAddValid(t *testing.T) { + client := createValidTestClient(t) + zs := getDNSProviderZones(client) + + inZone := &zone{name: "kops-dns-test", client: client} + outZone, err := zs.Add(inZone) + + if err != nil { + t.Errorf("unexpected err: %v", err) + } + if outZone == nil { + t.Errorf("zone is nil, exiting test early") + } + if outZone.Name() != "kops-dns-test" { + t.Errorf("unexpected zone name: %s", outZone.Name()) + } +} + +func TestAddShouldFail(t *testing.T) { + client := createValidTestClient(t) + err := os.Setenv("SCW_DNS_ZONE", "invalid.domain") + zs := getDNSProviderZones(client) + + inZone := &zone{name: "kops-dns-test", client: client} + outZone, err := zs.Add(inZone) + + if outZone != nil { + t.Errorf("expected zone to be nil, got :%v", outZone) + } + if err == nil { + t.Errorf("expected non-nil err: %v", err) + } +} + +func TestRemoveValid(t *testing.T) { + client := createValidTestClient(t) + zs := getDNSProviderZones(client) + + inZone := &zone{name: "kops-dns-test", client: client} + err := zs.Remove(inZone) + + if err != nil { + t.Errorf("unexpected err: %v", err) + } +} + +func TestRemoveShouldFail(t *testing.T) { + client := createValidTestClient(t) + err := os.Setenv("SCW_DNS_ZONE", "invalid.domain") + zs := getDNSProviderZones(client) + + inZone := &zone{name: "kops-dns-test", client: client} + err = zs.Remove(inZone) + + if err == nil { + t.Errorf("expected non-nil err: %v", err) + } +} + +func TestNewZone(t *testing.T) { + client := createValidTestClient(t) + zs := getDNSProviderZones(client) + + zone, err := zs.New("kops-dns-test") + + if err != nil { + t.Errorf("error creating zone: %v", err) + return + } + if zone == nil { + t.Errorf("zone is nil, exiting test early") + } + if zone.Name() != "kops-dns-test" { + t.Errorf("unexpected zone name: %v", zone.Name()) + } +} + +func TestNewResourceRecordSet(t *testing.T) { + client := createValidTestClient(t) + zs := getDNSProviderZones(client) + + recordsIds, err := createRecord(client, &domain.UpdateDNSZoneRecordsRequest{ + DNSZone: validDNSZone, + Changes: []*domain.RecordChange{ + { + Add: &domain.RecordChangeAdd{ + Records: []*domain.Record{ + { + Name: "test", + Data: "127.0.0.1", + TTL: 3600, + Type: "A", + }, + }, + }, + }, + }, + }) + if err != nil { + t.Errorf("error creating record: %v", err) + } + + zone, err := zs.New(validDNSZone) + if err != nil { + t.Errorf("error creating zone: %v", err) + + } + if zone == nil { + t.Errorf("zone is nil, exiting test early") + } + if zone.Name() != validDNSZone { + t.Errorf("unexpected zone name: %v", zone.Name()) + } + + rrset, _ := zone.ResourceRecordSets() + rrsets, err := rrset.List() + + if err != nil { + t.Errorf("error listing resource record sets: %v", err) + } + if len(rrsets) < 1 { + t.Errorf("unexpected number of records: %d", len(rrsets)) + } + + records, err := rrset.Get("test." + validDNSZone) + if err != nil { + t.Errorf("unexpected error getting resource record set: %v", err) + } + + if len(records) != 1 { + t.Errorf("unexpected records from resource record set: %d, expected 1 record", len(records)) + } + if records[0].Name() != "test."+validDNSZone { + t.Errorf("unexpected record name: %s, expected 'test'", records[0].Name()) + } + if len(records[0].Rrdatas()) != 1 { + t.Errorf("unexpected number of resource record data: %d", len(records[0].Rrdatas())) + } + if records[0].Rrdatas()[0] != "127.0.0.1" { + t.Errorf("unexpected resource record data: %s", records[0].Rrdatas()[0]) + } + if records[0].Ttl() != 3600 { + t.Errorf("unexpected record TTL: %d, expected 3600", records[0].Ttl()) + } + if records[0].Type() != rrstype.A { + t.Errorf("unexpected resource record type: %s, expected %s", records[0].Type(), rrstype.A) + } + + // Cleaning up created zones + for _, id := range recordsIds { + err = deleteRecord(client, validDNSZone, id) + if err != nil { + t.Errorf("error deleting record: %v", err) + } + } +} + +func TestResourceRecordChangeset(t *testing.T) { + ctx := context.Background() + client := createValidTestClient(t) + zs := getDNSProviderZones(client) + + recordsIds, err := createRecord(client, &domain.UpdateDNSZoneRecordsRequest{ + DNSZone: validDNSZone, + Changes: []*domain.RecordChange{ + { + Add: &domain.RecordChangeAdd{ + Records: []*domain.Record{ + { + Name: "test", + Data: "127.0.0.1", + TTL: 3600, + Type: "A", + }, + { + Name: "to-remove", + Data: "127.0.0.1", + TTL: 3600, + Type: "A", + }, + { + Name: "to-upsert", + Data: "127.0.0.1", + TTL: 3600, + Type: "A", + }, + }, + }, + }, + }, + }) + if err != nil { + t.Errorf("error creating record: %v", err) + } + + zone, err := zs.New(validDNSZone) + if err != nil { + t.Errorf("error creating zone: %v", err) + } + if zone == nil { + t.Errorf("zone is nil, exiting test early") + } + if zone.Name() != validDNSZone { + t.Errorf("unexpected zone name: %v", zone.Name()) + } + + rrset, _ := zone.ResourceRecordSets() + changeset := rrset.StartChangeset() + + if !changeset.IsEmpty() { + t.Error("expected empty changeset") + } + + record := rrset.New("to-add", []string{"127.0.0.1"}, 3600, rrstype.A) + changeset.Add(record) + + record = rrset.New("to-remove", []string{"127.0.0.1"}, 3600, rrstype.A) + changeset.Remove(record) + + record = rrset.New("to-upsert", []string{"127.0.0.1"}, 3601, rrstype.A) + changeset.Upsert(record) + + err = changeset.Apply(ctx) + if err != nil { + t.Errorf("error applying changeset: %v", err) + } + + records, err := rrset.Get("test." + validDNSZone) + if err != nil { + t.Errorf("unexpected error getting resource record set: %v", err) + } + records, err = rrset.Get("to-upsert." + validDNSZone) + if err != nil { + t.Errorf("unexpected error getting resource record set: %v", err) + } + if records[0].Ttl() != 3601 { + t.Errorf("unexpected record TTL: %d, expected 3601", records[0].Ttl()) + } + records, err = rrset.Get("to-remove." + validDNSZone) + if records != nil { + t.Errorf("record set 'to-remove' should have been deleted") + } + records, err = rrset.Get("to-add." + validDNSZone) + if err != nil { + t.Errorf("unexpected error getting resource record set: %v", err) + } + + // Cleaning up created zones + api := domain.NewAPI(client) + addedRecords, err := api.ListDNSZoneRecords(&domain.ListDNSZoneRecordsRequest{ + DNSZone: validDNSZone, + Name: records[0].Name(), + }) + for _, addedRecord := range addedRecords.Records { + err = deleteRecord(client, validDNSZone, addedRecord.ID) + if err != nil { + t.Errorf("error deleting record: %v", err) + } + } + for _, id := range recordsIds { + err = deleteRecord(client, validDNSZone, id) + if err != nil { + t.Errorf("error deleting record: %v", err) + } + } + +} diff --git a/docs/getting_started/scaleway.md b/docs/getting_started/scaleway.md new file mode 100644 index 0000000000000..77e74393261b6 --- /dev/null +++ b/docs/getting_started/scaleway.md @@ -0,0 +1,60 @@ +# Getting Started with kops on Scaleway + +**WARNING**: scaleway support on kops is currently **alpha**, which means that scaleway support is in the early stages of development and subject to change, please use with caution. + +## Scaleway requirements + +* [kops version >= 1.18 installed](../install.md) +* [kubectl installed](../install.md) +* [Scaleway access/secret key](https://www.scaleway.com/en/docs/generate-api-keys/) +* [Setup your SSH key](https://www.scaleway.com/en/docs/configure-new-ssh-key/) + +## Environment Variables + +It is important to set the following [environment variables](https://github.com/scaleway/scaleway-sdk-go/blob/master/scw/README.md): +```bash +# this is required since Scaleway support is currently in alpha so it is feature gated +export KOPS_FEATURE_FLAGS="Scaleway" +export SCW_ACCESS_KEY="my-access-key" +export SCW_SECRET_KEY="my-secret-key" +export SCW_DEFAULT_PROJECT_ID="my-project-id" +# Configure the bucket name to store kops state +export KOPS_STATE_STORE=scw:// # where is the name of the bucket you set earlier +# Scaleway Object Storage is S3 compatible so we just override some S3 configurations to talk to our bucket +export S3_REGION=fr-par # or another scaleway region providing Object Storage +export S3_ENDPOINT=s3.$S3_REGION.scw.cloud # define provider endpoint +export S3_ACCESS_KEY_ID="my-access-key" # where is the Spaces API Access Key for your bucket +export S3_SECRET_ACCESS_KEY="my-secret-key" # where is the Spaces API Secret Key for your bucket +``` + +## Creating a Single Master Cluster + +In the following examples, `example.com` should be replaced with the Scaleway domain you created when going through the [Requirements](#requirements). // TODO(Mia-Cross): fix broken anchor +Note that you kops will only be able to successfully provision clusters in regions that support block storage (AMS3, BLR1, FRA1, LON1, NYC1, NYC3, SFO2, SGP1 and TOR1). + +```bash +# debian (the default) + flannel overlay cluster in fr-par-1 using default instance type +kops create cluster --cloud=scaleway --name=mycluster.k8s.local --networking=flannel --zones=fr-par-1 --ssh-public-key=~/.ssh/id_ed25519.pub +kops update cluster my-cluster.example.com --yes +# ubuntu + weave overlay cluster in nl-ams-1 using GP1-S instance type. +kops create cluster --cloud=scaleway --name=mycluster.k8s.local --image=ubuntu_focal --networking=weave --zones=nl-ams-1 --ssh-public-key=~/.ssh/id_ed25519.pub --node-size=gp1-s +kops update cluster my-cluster.example.com --yes +# to delete a cluster +kops delete cluster my-cluster.example.com --yes +``` + +## Creating a Multi-Master HA Cluster + +In the below example, `dev5.k8s.local` should be replaced with any cluster name that ends with `.k8s.local` such that a gossip based cluster is created. +Ensure the master-count is odd-numbered. A load balancer is created dynamically front-facing the master instances. + +```bash +# debian (the default) + flannel overlay cluster in tor1 with 3 master setup and a public load balancer. +kops create cluster --cloud=scaleway --name=dev5.k8s.local --networking=cilium --api-loadbalancer-type=public --master-count=3 --zones=fr-par-1 --ssh-public-key=~/.ssh/id_rsa.pub --yes +# to delete a cluster - this will also delete the load balancer associated with the cluster. +kops delete cluster dev5.k8s.local --yes +``` + +# Next steps + +Now that you have a working _kops_ cluster, read through the [recommendations for production setups guide](production.md) to learn more about how to configure _kops_ for production workloads. \ No newline at end of file diff --git a/docs/state.md b/docs/state.md index 8376d0e221a18..79d137d5735b4 100644 --- a/docs/state.md +++ b/docs/state.md @@ -17,6 +17,7 @@ As of now the following state stores are supported: * Google Cloud (`gs://`) * Kubernetes (`k8s://`) * OpenStack Swift (`swift://`) +* Scaleway (`scw://`) The state store is just files; you can copy the files down and put them into git (or your preferred version control system). @@ -179,3 +180,6 @@ gcsClient, err := storage.New(httpClient) ``` +## Scaleway (scw://) + +Scaleway storage is configured as a flavor of a S3 store. For more information on how to create a bucket with Scaleway, visit [this page](https://www.scaleway.com/en/docs/storage/object/quickstart/). diff --git a/hack/upload b/hack/upload index 10a685dc9fb0c..760f0f5675697 100755 --- a/hack/upload +++ b/hack/upload @@ -50,8 +50,13 @@ 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 + if [[ $(pwd) == "/root/kops" ]]; then + echo "--> rclone sync ${SRC} normal://$SCW_BUCKET --progress" + rclone sync ${SRC} normal://$SCW_BUCKET --progress + else + 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 diff --git a/pkg/model/master_volumes.go b/pkg/model/master_volumes.go index bee9ab73681ca..fc15763cd15f7 100644 --- a/pkg/model/master_volumes.go +++ b/pkg/model/master_volumes.go @@ -398,19 +398,22 @@ func (b *MasterVolumeBuilder) addAzureVolume( } func (b *MasterVolumeBuilder) addScalewayVolume(c *fi.CloudupModelBuilderContext, name string, volumeSize int32, zone string, etcd kops.EtcdClusterSpec, m kops.EtcdMemberSpec, allMembers []string) { - tags := []string{ + volumeTags := []string{ fmt.Sprintf("%s=%s", scaleway.TagClusterName, b.Cluster.ObjectMeta.Name), fmt.Sprintf("%s=%s", scaleway.TagNameEtcdClusterPrefix, etcd.Name), fmt.Sprintf("%s=%s", scaleway.TagNameRolePrefix, scaleway.TagRoleControlPlane), fmt.Sprintf("%s=%s", scaleway.TagInstanceGroup, fi.ValueOf(m.InstanceGroup)), } + for k, v := range b.CloudTags(b.ClusterName(), false) { + volumeTags = append(volumeTags, fmt.Sprintf("%s=%s", k, v)) + } t := &scalewaytasks.Volume{ Name: fi.PtrTo(name), Lifecycle: b.Lifecycle, Size: fi.PtrTo(int64(volumeSize) * 1e9), Zone: &zone, - Tags: tags, + Tags: volumeTags, Type: fi.PtrTo(string(instance.VolumeVolumeTypeBSSD)), } c.AddTask(t) diff --git a/pkg/model/scalewaymodel/OWNERS b/pkg/model/scalewaymodel/OWNERS new file mode 100644 index 0000000000000..d5b228c52625d --- /dev/null +++ b/pkg/model/scalewaymodel/OWNERS @@ -0,0 +1,3 @@ +# See the OWNERS docs at https://go.k8s.io/owners +labels: +- area/provider/scaleway diff --git a/pkg/model/scalewaymodel/api_loadbalancer.go b/pkg/model/scalewaymodel/api_loadbalancer.go index 93fe3454b7112..cd145eff11e49 100644 --- a/pkg/model/scalewaymodel/api_loadbalancer.go +++ b/pkg/model/scalewaymodel/api_loadbalancer.go @@ -57,11 +57,13 @@ func (b *APILoadBalancerModelBuilder) Build(c *fi.CloudupModelBuilderContext) er if err != nil { return fmt.Errorf("building load-balancer task: %w", err) } - lbTags := []string(nil) + lbTags := []string{ + fmt.Sprintf("%s=%s", scaleway.TagClusterName, b.ClusterName()), + fmt.Sprintf("%s=%s", scaleway.TagNameRolePrefix, scaleway.TagRoleControlPlane), + } for k, v := range b.CloudTags(b.ClusterName(), false) { lbTags = append(lbTags, fmt.Sprintf("%s=%s", k, v)) } - lbTags = append(lbTags, fmt.Sprintf("%s=%s", scaleway.TagNameRolePrefix, scaleway.TagRoleControlPlane)) loadBalancerName := "api." + b.ClusterName() loadBalancer := &scalewaytasks.LoadBalancer{ @@ -100,6 +102,13 @@ func (b *APILoadBalancerModelBuilder) Build(c *fi.CloudupModelBuilderContext) er c.AddTask(lbFrontend) + //if b.Cluster.Spec.NetworkID != "" { + // loadBalancer.VPCId = fi.PtrTo(b.Cluster.Spec.NetworkID) + //} else if b.Cluster.Spec.NetworkCIDR != "" { + // loadBalancer.VPCName = fi.PtrTo(b.ClusterName()) + // loadBalancer.NetworkCIDR = fi.PtrTo(b.Cluster.Spec.NetworkCIDR) + //} + if dns.IsGossipClusterName(b.Cluster.Name) || b.Cluster.UsesPrivateDNS() || b.Cluster.UsesNoneDNS() { // Ensure the LB hostname is included in the TLS certificate, // if we're not going to use an alias for it diff --git a/pkg/model/scalewaymodel/context.go b/pkg/model/scalewaymodel/context.go index 799f0b1e0b4f6..f1299f402b7ad 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.Network { + name := b.ClusterName() + return &scalewaytasks.Network{Name: &name} +} diff --git a/pkg/model/scalewaymodel/instances.go b/pkg/model/scalewaymodel/instances.go index f7510f3c90def..ab6ad27e9a9b2 100644 --- a/pkg/model/scalewaymodel/instances.go +++ b/pkg/model/scalewaymodel/instances.go @@ -36,38 +36,43 @@ type InstanceModelBuilder struct { var _ fi.CloudupModelBuilder = &InstanceModelBuilder{} -func (d *InstanceModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { - for _, ig := range d.InstanceGroups { - name := d.AutoscalingGroupName(ig) +func (b *InstanceModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { + for _, ig := range b.InstanceGroups { + name := ig.Name zone, err := scw.ParseZone(ig.Spec.Subnets[0]) if err != nil { return fmt.Errorf("error building instance task for %q: %w", name, err) } - userData, err := d.BootstrapScriptBuilder.ResourceNodeUp(c, ig) + userData, err := b.BootstrapScriptBuilder.ResourceNodeUp(c, ig) if err != nil { return fmt.Errorf("error building bootstrap script for %q: %w", name, err) } + instanceTags := []string{ + scaleway.TagInstanceGroup + "=" + ig.Name, + scaleway.TagClusterName + "=" + b.Cluster.Name, + } + for k, v := range b.CloudTags(b.ClusterName(), false) { + instanceTags = append(instanceTags, fmt.Sprintf("%s=%s", k, v)) + } + instance := scalewaytasks.Instance{ Count: int(fi.ValueOf(ig.Spec.MinSize)), Name: fi.PtrTo(name), - Lifecycle: d.Lifecycle, + Lifecycle: b.Lifecycle, Zone: fi.PtrTo(string(zone)), CommercialType: fi.PtrTo(ig.Spec.MachineType), Image: fi.PtrTo(ig.Spec.Image), UserData: &userData, - Tags: []string{ - scaleway.TagInstanceGroup + "=" + ig.Name, - scaleway.TagClusterName + "=" + d.Cluster.Name, - }, + Tags: instanceTags, } if ig.IsControlPlane() { instance.Tags = append(instance.Tags, scaleway.TagNameRolePrefix+"="+scaleway.TagRoleControlPlane) instance.Role = fi.PtrTo(scaleway.TagRoleControlPlane) } else { - instance.Role = fi.PtrTo(scaleway.TagRoleWorker) + instance.Role = fi.PtrTo(scaleway.TagRoleNode) } c.AddTask(&instance) diff --git a/pkg/model/scalewaymodel/network.go b/pkg/model/scalewaymodel/network.go new file mode 100644 index 0000000000000..4df09fdff6796 --- /dev/null +++ b/pkg/model/scalewaymodel/network.go @@ -0,0 +1,32 @@ +package scalewaymodel + +import ( + "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 { + + ipRange := b.Cluster.Spec.Networking.NetworkCIDR + if ipRange == "" { + ipRange = "192.168.1.0/24" + } + + network := &scalewaytasks.Network{ + Name: fi.PtrTo(b.ClusterName()), + Zone: fi.PtrTo(b.Cluster.Spec.Networking.Subnets[0].Zone), + Lifecycle: b.Lifecycle, + IPRange: fi.PtrTo(ipRange), + Tags: []string{scaleway.TagClusterName + "=" + b.ClusterName()}, + } + c.AddTask(network) + + return nil +} diff --git a/pkg/nodeidentity/scaleway/identify.go b/pkg/nodeidentity/scaleway/identify.go index 89fc2165d8092..f9af6d2aa4bcb 100644 --- a/pkg/nodeidentity/scaleway/identify.go +++ b/pkg/nodeidentity/scaleway/identify.go @@ -137,17 +137,13 @@ func stringKeyFunc(obj interface{}) (string, error) { // getServer queries Scaleway for the server with the specified ID, returning an error if not found func (i *nodeIdentifier) getServer(ctx context.Context, id string) (*instance.Server, error) { api := instance.NewAPI(i.client) - zone, exists := i.client.GetDefaultZone() - if !exists { - return nil, fmt.Errorf("client default zone is empty") - } uuid := strings.Split(id, "/") if len(uuid) != 3 { return nil, fmt.Errorf("unexpected format for server id %s", id) } server, err := api.GetServer(&instance.GetServerRequest{ ServerID: uuid[2], - Zone: scw.Zone(zone), + Zone: scw.Zone(uuid[1]), }, scw.WithContext(ctx)) if err != nil || server == nil { return nil, fmt.Errorf("failed to get server %s: %w", id, err) diff --git a/pkg/resources/scaleway/resources.go b/pkg/resources/scaleway/resources.go index ab2dfd3e5e461..09cf7d437cd6a 100644 --- a/pkg/resources/scaleway/resources.go +++ b/pkg/resources/scaleway/resources.go @@ -17,20 +17,29 @@ limitations under the License. package scaleway import ( - "k8s.io/kops/pkg/resources" - "k8s.io/kops/upup/pkg/fi" - "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" + "fmt" + "strings" + 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" "github.com/scaleway/scaleway-sdk-go/api/lb/v1" + "github.com/scaleway/scaleway-sdk-go/api/vpc/v1" + "github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1" + "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" ) const ( + resourceTypeDNSRecord = "dns-record" + resourceTypeGateway = "gateway" resourceTypeLoadBalancer = "load-balancer" resourceTypeServer = "server" resourceTypeSSHKey = "ssh-key" resourceTypeVolume = "volume" + resourceTypeVPC = "vpc" ) type listFn func(fi.Cloud, string) ([]*resources.Resource, error) @@ -40,10 +49,13 @@ func ListResources(cloud scaleway.ScwCloud, clusterInfo resources.ClusterInfo) ( clusterName := clusterInfo.Name listFunctions := []listFn{ + listDNSRecords, + listGateways, listLoadBalancers, listServers, listSSHKeys, listVolumes, + listVPCs, } for _, fn := range listFunctions { @@ -59,6 +71,72 @@ func ListResources(cloud scaleway.ScwCloud, clusterInfo resources.ClusterInfo) ( return resourceTrackers, nil } +func listDNSRecords(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) { + c := cloud.(scaleway.ScwCloud) + + if strings.HasSuffix(clusterName, ".k8s.local") { + return nil, nil + } + + names := strings.SplitN(clusterName, ".", 2) + clusterNameShort := names[0] + domainName := names[1] + + records, err := c.DomainService().ListDNSZoneRecords(&domain.ListDNSZoneRecordsRequest{ + DNSZone: domainName, + }, scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("failed to list records: %s", err) + } + + resourceTrackers := []*resources.Resource(nil) + for _, record := range records.Records { + if !strings.HasSuffix(record.Name, clusterNameShort) { + continue + } + resourceTracker := &resources.Resource{ + Name: record.Name, + ID: record.ID, + Type: resourceTypeDNSRecord, + Deleter: func(cloud fi.Cloud, tracker *resources.Resource) error { + return deleteDNSRecord(cloud, tracker, domainName) + }, + Obj: record, + } + resourceTrackers = append(resourceTrackers, resourceTracker) + } + + 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 { + 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, + } + for _, gwNetwork := range gw.GatewayNetworks { + resourceTracker.Blocks = append(resourceTracker.Blocks, resourceTypeVPC+":"+gwNetwork.PrivateNetworkID) + } + + 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) @@ -101,6 +179,10 @@ func listServers(cloud fi.Cloud, clusterName string) ([]*resources.Resource, err }, Obj: server, } + for _, privateNic := range server.PrivateNics { + resourceTracker.Blocks = append(resourceTracker.Blocks, resourceTypeVPC+":"+privateNic.PrivateNetworkID) + } + resourceTrackers = append(resourceTrackers, resourceTracker) } @@ -158,6 +240,44 @@ 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 { + return nil, err + } + + resourceTrackers := []*resources.Resource(nil) + for _, vpc := range vpcs { + resourceTracker := &resources.Resource{ + Name: vpc.Name, + ID: vpc.ID, + Type: resourceTypeVPC, + Deleter: func(cloud fi.Cloud, tracker *resources.Resource) error { + return deleteVPC(cloud, tracker) + }, + Obj: vpc, + } + 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) + + 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 deleteLoadBalancer(cloud fi.Cloud, tracker *resources.Resource) error { c := cloud.(scaleway.ScwCloud) loadBalancer := tracker.Obj.(*lb.LB) @@ -185,3 +305,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) + privateNetwork := tracker.Obj.(*vpc.PrivateNetwork) + + return c.DeleteVPC(privateNetwork) +} diff --git a/pkg/zones/wellknown.go b/pkg/zones/wellknown.go index abdb9b1ad7dff..9e69606b813e1 100644 --- a/pkg/zones/wellknown.go +++ b/pkg/zones/wellknown.go @@ -18,12 +18,12 @@ package zones import ( "sort" - "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/scaleway/scaleway-sdk-go/scw" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi/cloudup/awsup" ) @@ -233,6 +233,14 @@ var azureZones = []string{ "westusstage", } +func scwZones() []string { + var scwZones []string + for _, zone := range scw.AllZones { + scwZones = append(scwZones, string(zone)) + } + return scwZones +} + func WellKnownZonesForCloud(matchCloud kops.CloudProviderID, prefix string) []string { var found []string switch matchCloud { @@ -279,6 +287,9 @@ func WellKnownZonesForCloud(matchCloud kops.CloudProviderID, prefix string) []st found = gceZones case kops.CloudProviderHetzner: found = hetznerZones + case kops.CloudProviderScaleway: + found = scwZones() + default: return nil } diff --git a/tests/integration/update_cluster/minimal_scaleway/id_rsa.pub b/tests/integration/update_cluster/minimal_scaleway/id_rsa.pub new file mode 100644 index 0000000000000..7204e43e884bb --- /dev/null +++ b/tests/integration/update_cluster/minimal_scaleway/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDKqbVEozfAqng0gx8HTUu69EppcE5SWet6MpwrGShqMVUC4wkoiuVtJDPhMmWmdt7B7Ttc5pvnAZAZaQ6TKMguyBoAyS7qOTLU9/hM803XtSiwQUftOXiJfmsqAXEc8yDyb7UnrF8X7aA3gQJsnQBGJGdp+C88dPHNZenw4PnQc8BNYTCXG9d8F5vJ3xQ5qbiG4HVNoQ2CZh2ht+GedZJ3hl9lMJ24kE/cbMCLKxabMP4ROetECG6PU251jnm84NA8rm0Av/JMmn/c9CFAe0D0D1dGDlHWPsk4mbhGKJ0yU0YliatmPfmgSasismbYzIFf7VPq91ARzRUbavd1fYMBmkMsce0YR/5FdtrpzRhqDzuvwQgQRsoTcttdvp0puFcrtNefMfk8NCbBedIlkzOFxfGiBbe6jde4wqsqEnSrNHwZ2b+Er8z7vjcDPBqYk3gubmMBCrYxg6o1lOS6tTN0kJDUlyKO2AN1ZDr3mpkbhkvZV/N7gLglcClM0X5X7iM= leila@leila-ThinkPad-T14s-Gen-2i diff --git a/tests/integration/update_cluster/minimal_scaleway/in-v1alpha2.yaml b/tests/integration/update_cluster/minimal_scaleway/in-v1alpha2.yaml new file mode 100644 index 0000000000000..3ea134b5ebe9f --- /dev/null +++ b/tests/integration/update_cluster/minimal_scaleway/in-v1alpha2.yaml @@ -0,0 +1,90 @@ +apiVersion: kops.k8s.io/v1alpha2 +kind: Cluster +metadata: + creationTimestamp: "2023-01-01T00:00:00Z" + name: scw-minimal.k8s.local +spec: + api: + loadBalancer: + type: Public + authorization: + rbac: {} + channel: stable + cloudProvider: scaleway + configBase: memfs://tests/scw-minimal.k8s.local + etcdClusters: + - cpuRequest: 200m + etcdMembers: + - instanceGroup: control-plane-fr-par-1 + name: etcd-1 + memoryRequest: 100Mi + name: main + - cpuRequest: 100m + etcdMembers: + - instanceGroup: control-plane-fr-par-1 + name: etcd-1 + memoryRequest: 100Mi + name: events + iam: + allowContainerRegistry: true + legacy: false + kubeProxy: + enabled: false + kubelet: + anonymousAuth: false + kubernetesApiAccess: + - 0.0.0.0/0 + - ::/0 + kubernetesVersion: 1.25.5 + networking: + cilium: + enableNodePort: true + nonMasqueradeCIDR: 100.64.0.0/10 + sshAccess: + - 0.0.0.0/0 + - ::/0 + subnets: + - name: fr-par-1 + type: Public + zone: fr-par-1 + topology: + dns: + type: Private + masters: public + nodes: public + +--- + +apiVersion: kops.k8s.io/v1alpha2 +kind: InstanceGroup +metadata: + creationTimestamp: "2023-01-01T00:00:00Z" + labels: + kops.k8s.io/cluster: scw-minimal.k8s.local + name: control-plane-fr-par-1 +spec: + image: ubuntu_focal + machineType: DEV1-M + maxSize: 1 + minSize: 1 + role: Master + subnets: + - fr-par-1 + +--- + +apiVersion: kops.k8s.io/v1alpha2 +kind: InstanceGroup +metadata: + creationTimestamp: "2023-01-01T00:00:00Z" + labels: + kops.k8s.io/cluster: scw-minimal.k8s.local + name: nodes-fr-par-1 +spec: + image: ubuntu_focal + machineType: DEV1-M + maxSize: 1 + minSize: 1 + role: Node + subnets: + - fr-par-1 diff --git a/tests/integration/update_cluster/minimal_scaleway/kubernetes.tf b/tests/integration/update_cluster/minimal_scaleway/kubernetes.tf new file mode 100644 index 0000000000000..a0f855dc39837 --- /dev/null +++ b/tests/integration/update_cluster/minimal_scaleway/kubernetes.tf @@ -0,0 +1,259 @@ +locals { + cluster_name = "scw-minimal.k8s.local" + region = "fr-par" +} + +output "cluster_name" { + value = "scw-minimal.k8s.local" +} + +output "region" { + value = "fr-par" +} + +provider "scaleway" { + region = "fr-par" +} + +provider "aws" { + alias = "files" + region = "us-test-1" +} + +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" + provider = aws.files + server_side_encryption = "AES256" +} + +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" + provider = aws.files + server_side_encryption = "AES256" +} + +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" + provider = aws.files + server_side_encryption = "AES256" +} + +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" + provider = aws.files + server_side_encryption = "AES256" +} + +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" + provider = aws.files + server_side_encryption = "AES256" +} + +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" + provider = aws.files + server_side_encryption = "AES256" +} + +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" + provider = aws.files + server_side_encryption = "AES256" +} + +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" + provider = aws.files + server_side_encryption = "AES256" +} + +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" + provider = aws.files + server_side_encryption = "AES256" +} + +resource "aws_s3_object" "scw-minimal-k8s-local-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" + provider = aws.files + server_side_encryption = "AES256" +} + +resource "aws_s3_object" "scw-minimal-k8s-local-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" + provider = aws.files + server_side_encryption = "AES256" +} + +resource "aws_s3_object" "scw-minimal-k8s-local-addons-dns-controller-addons-k8s-io-k8s-1-12" { + 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" + provider = aws.files + server_side_encryption = "AES256" +} + +resource "aws_s3_object" "scw-minimal-k8s-local-addons-kops-controller-addons-k8s-io-k8s-1-16" { + 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" + 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" { + 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" + provider = aws.files + server_side_encryption = "AES256" +} + +resource "aws_s3_object" "scw-minimal-k8s-local-addons-limit-range-addons-k8s-io" { + 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" + provider = aws.files + server_side_encryption = "AES256" +} + +resource "aws_s3_object" "scw-minimal-k8s-local-addons-networking-cilium-io-k8s-1-16" { + 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.12.yaml" + provider = aws.files + server_side_encryption = "AES256" +} + +resource "aws_s3_object" "scw-minimal-k8s-local-addons-rbac-addons-k8s-io-k8s-1-8" { + bucket = "testingBucket" + content = file("${path.module}/data/aws_s3_object_scw-minimal.k8s.local-addons-rbac.addons.k8s.io-k8s-1.8_content") + key = "tests/scw-minimal.k8s.local/addons/rbac.addons.k8s.io/k8s-1.8.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" { + 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" + 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" + 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" { +} + +resource "scaleway_instance_ip" "nodes-fr-par-1" { +} + +resource "scaleway_instance_server" "control-plane-fr-par-1" { + image = "ubuntu_focal" + ip_id = scaleway_instance_ip.control-plane-fr-par-1.id + name = "control-plane-fr-par-1" + tags = ["kops.k8s.io/instance-group=control-plane-fr-par-1", "kops.k8s.io/cluster=scw-minimal.k8s.local", "kops.k8s.io/role=ControlPlane"] + type = "DEV1-M" + user_data = { + "cloud-init" = filebase64("${path.module}/data/scaleway_instance_server_control-plane-fr-par-1_user_data") + } +} + +resource "scaleway_instance_server" "nodes-fr-par-1" { + image = "ubuntu_focal" + ip_id = scaleway_instance_ip.nodes-fr-par-1.id + name = "nodes-fr-par-1" + tags = ["kops.k8s.io/instance-group=nodes-fr-par-1", "kops.k8s.io/cluster=scw-minimal.k8s.local"] + type = "DEV1-M" + user_data = { + "cloud-init" = filebase64("${path.module}/data/scaleway_instance_server_nodes-fr-par-1_user_data") + } +} + +resource "scaleway_instance_volume" "etcd-1-etcd-events-scw-minimal-k8s-local" { + name = "etcd-1.etcd-events.scw-minimal.k8s.local" + size_in_gb = 20 + tags = ["kops.k8s.io/cluster=scw-minimal.k8s.local", "kops.k8s.io/etcd=events", "kops.k8s.io/role=ControlPlane", "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" + size_in_gb = 20 + tags = ["kops.k8s.io/cluster=scw-minimal.k8s.local", "kops.k8s.io/etcd=main", "kops.k8s.io/role=ControlPlane", "kops.k8s.io/instance-group=control-plane-fr-par-1"] + type = "b_ssd" +} + +resource "scaleway_lb" "api-scw-minimal-k8s-local" { + ip_id = scaleway_lb_ip.api-scw-minimal-k8s-local.id + name = "api.scw-minimal.k8s.local" + tags = ["kops.k8s.io/cluster=scw-minimal.k8s.local", "kops.k8s.io/role=ControlPlane"] + type = "LB-S" +} + +resource "scaleway_lb_backend" "api-scw-minimal-k8s-local" { + forward_port = 443 + forward_protocol = "tcp" + lb_id = scaleway_lb.api-scw-minimal-k8s-local.id + name = "lb-backend" +} + +resource "scaleway_lb_frontend" "api-scw-minimal-k8s-local" { + backend_id = scaleway_lb_backend.api-scw-minimal-k8s-local.id + inbound_port = 443 + lb_id = scaleway_lb.api-scw-minimal-k8s-local.id + name = "lb-frontend" +} + +resource "scaleway_lb_ip" "api-scw-minimal-k8s-local" { +} + +terraform { + required_version = ">= 0.15.0" + required_providers { + aws = { + "configuration_aliases" = [aws.files] + "source" = "hashicorp/aws" + "version" = ">= 4.0.0" + } + scaleway = { + "source" = "scaleway/scaleway" + "version" = ">= 2.2.1" + } + } +} diff --git a/upup/models/cloudup/resources/addons/dns-controller.addons.k8s.io/k8s-1.12.yaml.template b/upup/models/cloudup/resources/addons/dns-controller.addons.k8s.io/k8s-1.12.yaml.template index 24c90e85e6b8d..5b918b7e9a285 100644 --- a/upup/models/cloudup/resources/addons/dns-controller.addons.k8s.io/k8s-1.12.yaml.template +++ b/upup/models/cloudup/resources/addons/dns-controller.addons.k8s.io/k8s-1.12.yaml.template @@ -68,6 +68,13 @@ spec: secretKeyRef: name: digitalocean key: access-token +{{- end }} +{{- if eq GetCloudProvider "scaleway" }} + - name: SCW_DNS_ZONE + value: {{ SCW_DNS_ZONE }} + envFrom: + - secretRef: + name: scaleway-secret {{- end }} resources: requests: 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 2d903caca166a..4d57c6f8e356b 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 @@ -11,9 +11,9 @@ stringData: SCW_SECRET_KEY: {{ SCW_SECRET_KEY }} # Project ID could also be an Organization ID SCW_DEFAULT_PROJECT_ID: {{ SCW_DEFAULT_PROJECT_ID }} - # Region is where your loadbalancer will be created, ex: nl-ams, nl-ams + # Region is where your loadbalancer will be created, ex: fr-par, nl-ams SCW_DEFAULT_REGION: {{ SCW_DEFAULT_REGION }} - # Zone is where your servers and volumes will be created, ex: nl-ams-1, nl-ams-2 + # Zone is where your servers and volumes will be created, ex: fr-par-1, nl-ams-2 SCW_DEFAULT_ZONE: {{ SCW_DEFAULT_ZONE }} --- apiVersion: apps/v1 diff --git a/upup/models/cloudup/resources/addons/scaleway-csi-driver.addons.k8s.io/k8s-1.24.yaml.template b/upup/models/cloudup/resources/addons/scaleway-csi-driver.addons.k8s.io/k8s-1.24.yaml.template index 2f1faa5144d2f..f4d55962c8947 100644 --- a/upup/models/cloudup/resources/addons/scaleway-csi-driver.addons.k8s.io/k8s-1.24.yaml.template +++ b/upup/models/cloudup/resources/addons/scaleway-csi-driver.addons.k8s.io/k8s-1.24.yaml.template @@ -12,9 +12,9 @@ stringData: SCW_SECRET_KEY: {{ SCW_SECRET_KEY }} # Project ID could also be an Organization ID SCW_DEFAULT_PROJECT_ID: {{ SCW_DEFAULT_PROJECT_ID }} - # Region is where your load-balancer will be created, ex: nl-ams, nl-ams + # Region is where your load-balancer will be created, ex: fr-par, nl-ams SCW_DEFAULT_REGION: {{ SCW_DEFAULT_REGION }} - # Zone is where your servers and volumes will be created, ex: nl-ams-1, nl-ams-2 + # Zone is where your servers and volumes will be created, ex: fr-par-1, nl-ams-2 SCW_DEFAULT_ZONE: {{ SCW_DEFAULT_ZONE }} --- apiVersion: storage.k8s.io/v1 diff --git a/upup/pkg/fi/cloudup/apply_cluster.go b/upup/pkg/fi/cloudup/apply_cluster.go index 4753d53b424ef..846d7e40fa9f4 100644 --- a/upup/pkg/fi/cloudup/apply_cluster.go +++ b/upup/pkg/fi/cloudup/apply_cluster.go @@ -91,6 +91,7 @@ var TerraformCloudProviders = []kops.CloudProviderID{ kops.CloudProviderAWS, kops.CloudProviderGCE, kops.CloudProviderHetzner, + kops.CloudProviderScaleway, } type ApplyClusterCmd struct { @@ -682,13 +683,13 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) error { &openstackmodel.FirewallModelBuilder{OpenstackModelContext: openstackModelContext, Lifecycle: securityLifecycle}, &openstackmodel.ServerGroupModelBuilder{OpenstackModelContext: openstackModelContext, BootstrapScriptBuilder: bootstrapScriptBuilder, Lifecycle: clusterLifecycle}, ) - case kops.CloudProviderScaleway: scwModelContext := &scalewaymodel.ScwModelContext{ KopsModelContext: modelContext, } l.Builders = append(l.Builders, - &scalewaymodel.APILoadBalancerModelBuilder{ScwModelContext: scwModelContext, Lifecycle: networkLifecycle}, + //&scalewaymodel.NetworkModelBuilder{ScwModelContext: scwModelContext, Lifecycle: networkLifecycle}, + &scalewaymodel.APILoadBalancerModelBuilder{ScwModelContext: scwModelContext, Lifecycle: clusterLifecycle}, &scalewaymodel.InstanceModelBuilder{ScwModelContext: scwModelContext, BootstrapScriptBuilder: bootstrapScriptBuilder, Lifecycle: clusterLifecycle}, &scalewaymodel.SSHKeyModelBuilder{ScwModelContext: scwModelContext, Lifecycle: securityLifecycle}, ) diff --git a/upup/pkg/fi/cloudup/dns.go b/upup/pkg/fi/cloudup/dns.go index 5436460e28ee9..889f9fde766a8 100644 --- a/upup/pkg/fi/cloudup/dns.go +++ b/upup/pkg/fi/cloudup/dns.go @@ -231,6 +231,10 @@ func precreateDNS(ctx context.Context, cluster *kops.Cluster, cloud fi.Cloud) er if len(created) != 0 { klog.Infof("Pre-creating DNS records") + if cloud.ProviderID() == kops.CloudProviderScaleway && os.Getenv("SCW_DNS_ZONE") == "" { + os.Setenv("SCW_DNS_ZONE", cluster.Spec.DNSZone) + } + err := changeset.Apply(ctx) if err != nil { return fmt.Errorf("error pre-creating DNS records: %v", err) diff --git a/upup/pkg/fi/cloudup/scaleway/cloud.go b/upup/pkg/fi/cloudup/scaleway/cloud.go index dd86e0e10c55a..f83f948852632 100644 --- a/upup/pkg/fi/cloudup/scaleway/cloud.go +++ b/upup/pkg/fi/cloudup/scaleway/cloud.go @@ -20,14 +20,18 @@ import ( "fmt" "strings" + 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" "github.com/scaleway/scaleway-sdk-go/api/lb/v1" + "github.com/scaleway/scaleway-sdk-go/api/vpc/v1" + "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" kopsv "k8s.io/kops" "k8s.io/kops/dnsprovider/pkg/dnsprovider" + dns "k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/scaleway" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/cloudinstances" "k8s.io/kops/upup/pkg/fi" @@ -35,12 +39,14 @@ import ( const ( TagClusterName = "kops.k8s.io/cluster" + TagNameEtcdClusterPrefix = "kops.k8s.io/etcd" + TagNeedsUpdate = "kops.k8s.io/needs-update" + TagInstanceGroup = "kops.k8s.io/instance-group" + TagNameRolePrefix = "kops.k8s.io/role" + TagRoleControlPlane = "ControlPlane" // changed from 'control-plane' to match kops.InstanceGroupRoleControlPlane + TagRoleNode = "Node" KopsUserAgentPrefix = "kubernetes-kops/" - TagInstanceGroup = "instance-group" - TagNameRolePrefix = "k8s.io/role" - TagNameEtcdClusterPrefix = "k8s.io/etcd" - TagRoleControlPlane = "control-plane" - TagRoleWorker = "worker" + //TagRoleLoadBalancer = "LoadBalancer" ) // ScwCloud exposes all the interfaces required to operate on Scaleway resources @@ -53,9 +59,12 @@ type ScwCloud interface { Region() string Zone() string + DomainService() *domain.API + GatewayService() *vpcgw.API IamService() *iam.API InstanceService() *instance.API LBService() *lb.ZonedAPI + VPCService() *vpc.API DeleteGroup(group *cloudinstances.CloudInstanceGroup) error DeleteInstance(i *cloudinstances.CloudInstance) error @@ -66,15 +75,21 @@ type ScwCloud interface { GetApiIngressStatus(cluster *kops.Cluster) ([]fi.ApiIngressStatus, error) GetCloudGroups(cluster *kops.Cluster, instancegroups []*kops.InstanceGroup, warnUnmatched bool, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error) + GetClusterGatewayNetworks(clusterName string) ([]*vpcgw.GatewayNetwork, error) + GetClusterGateways(clusterName string) ([]*vpcgw.Gateway, error) GetClusterLoadBalancers(clusterName string) ([]*lb.LB, error) - GetClusterServers(clusterName string, serverName *string) ([]*instance.Server, error) + GetClusterServers(clusterName string, instanceGroupName *string) ([]*instance.Server, error) GetClusterSSHKeys(clusterName string) ([]*iam.SSHKey, error) GetClusterVolumes(clusterName string) ([]*instance.Volume, error) + GetClusterVPCs(clusterName string) ([]*vpc.PrivateNetwork, error) + DeleteDNSRecord(record *domain.Record, domainName string) error + DeleteGateway(gateway *vpcgw.Gateway) error DeleteLoadBalancer(loadBalancer *lb.LB) error DeleteServer(server *instance.Server) error DeleteSSHKey(sshkey *iam.SSHKey) error DeleteVolume(volume *instance.Volume) error + DeleteVPC(vpc *vpc.PrivateNetwork) error } // static compile time check to validate ScwCloud's fi.Cloud Interface. @@ -85,16 +100,22 @@ type scwCloudImplementation struct { client *scw.Client region scw.Region zone scw.Zone + dns dnsprovider.Interface tags map[string]string + domainAPI *domain.API + gatewayAPI *vpcgw.API iamAPI *iam.API instanceAPI *instance.API lbAPI *lb.ZonedAPI + vpcAPI *vpc.API } // NewScwCloud returns a Cloud with a Scaleway Client using the env vars SCW_PROFILE or // SCW_ACCESS_KEY, SCW_SECRET_KEY and SCW_DEFAULT_PROJECT_ID func NewScwCloud(tags map[string]string) (ScwCloud, error) { + displayEnv() + region, err := scw.ParseRegion(tags["region"]) if err != nil { return nil, err @@ -120,10 +141,14 @@ func NewScwCloud(tags map[string]string) (ScwCloud, error) { client: scwClient, region: region, zone: zone, + dns: dns.NewProvider(scwClient), tags: tags, + domainAPI: domain.NewAPI(scwClient), + gatewayAPI: vpcgw.NewAPI(scwClient), iamAPI: iam.NewAPI(scwClient), instanceAPI: instance.NewAPI(scwClient), lbAPI: lb.NewZonedAPI(scwClient), + vpcAPI: vpc.NewAPI(scwClient), }, nil } @@ -137,8 +162,11 @@ func (s *scwCloudImplementation) ClusterName(tags []string) string { } func (s *scwCloudImplementation) DNS() (dnsprovider.Interface, error) { - klog.V(8).Infof("Scaleway DNS is not implemented yet") - return nil, fmt.Errorf("DNS is not implemented yet for Scaleway") + provider, err := dnsprovider.GetDnsProvider(dns.ProviderName, nil) + if err != nil { + return nil, fmt.Errorf("error building DNS provider: %w", err) + } + return provider, nil } func (s *scwCloudImplementation) ProviderID() kops.CloudProviderID { @@ -153,6 +181,14 @@ func (s *scwCloudImplementation) Zone() string { return string(s.zone) } +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 } @@ -165,6 +201,11 @@ func (s *scwCloudImplementation) LBService() *lb.ZonedAPI { return s.lbAPI } +func (s *scwCloudImplementation) VPCService() *vpc.API { + return s.vpcAPI +} + +// DeleteGroup deletes the cloud resources that make up a CloudInstanceGroup, including the instances. func (s *scwCloudImplementation) DeleteGroup(group *cloudinstances.CloudInstanceGroup) error { toDelete := append(group.NeedUpdate, group.Ready...) for _, cloudInstance := range toDelete { @@ -344,6 +385,12 @@ func buildCloudGroup(ig *kops.InstanceGroup, sg []*instance.Server, nodeMap map[ for _, server := range sg { status := cloudinstances.CloudInstanceStatusUpToDate + for _, tag := range server.Tags { + if tag == TagNeedsUpdate { + status = cloudinstances.CloudInstanceStatusNeedsUpdate + } + } + cloudInstance, err := cloudInstanceGroup.NewCloudInstance(server.ID, status, nodeMap[server.ID]) if err != nil { return nil, fmt.Errorf("failed to create cloud instance for server %s(%s): %w", server.Name, server.ID, err) @@ -352,7 +399,7 @@ func buildCloudGroup(ig *kops.InstanceGroup, sg []*instance.Server, nodeMap map[ cloudInstance.MachineType = server.CommercialType for _, tag := range server.Tags { if strings.HasPrefix(tag, TagNameRolePrefix) { - cloudInstance.Roles = append(cloudInstance.Roles, strings.TrimPrefix(tag, TagNameRolePrefix)) + cloudInstance.Roles = append(cloudInstance.Roles, strings.TrimPrefix(tag, TagNameRolePrefix+"=")) } } if server.PrivateIP != nil { @@ -363,6 +410,28 @@ func buildCloudGroup(ig *kops.InstanceGroup, sg []*instance.Server, nodeMap map[ return cloudInstanceGroup, 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 gateway networks: %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{ @@ -375,16 +444,19 @@ func (s *scwCloudImplementation) GetClusterLoadBalancers(clusterName string) ([] return lbs.LBs, nil } -func (s *scwCloudImplementation) GetClusterServers(clusterName string, serverName *string) ([]*instance.Server, error) { +func (s *scwCloudImplementation) GetClusterServers(clusterName string, instanceGroupName *string) ([]*instance.Server, error) { + tags := []string{TagClusterName + "=" + clusterName} + if instanceGroupName != nil { + tags = append(tags, fmt.Sprintf("%s=%s", TagInstanceGroup, *instanceGroupName)) + } request := &instance.ListServersRequest{ Zone: s.zone, - Name: serverName, - Tags: []string{TagClusterName + "=" + clusterName}, + Tags: tags, } servers, err := s.instanceAPI.ListServers(request, scw.WithAllPages()) if err != nil { - if serverName != nil { - return nil, fmt.Errorf("failed to list cluster servers named %q: %w", *serverName, err) + if instanceGroupName != nil { + return nil, fmt.Errorf("failed to list cluster servers named %q: %w", *instanceGroupName, err) } return nil, fmt.Errorf("failed to list cluster servers: %w", err) } @@ -416,6 +488,97 @@ func (s *scwCloudImplementation) GetClusterVolumes(clusterName string) ([]*insta return volumes.Volumes, nil } +func (s *scwCloudImplementation) GetClusterVPCs(clusterName string) ([]*vpc.PrivateNetwork, error) { + vpcs, err := s.vpcAPI.ListPrivateNetworks(&vpc.ListPrivateNetworksRequest{ + Zone: s.zone, + Tags: []string{TagClusterName + "=" + clusterName}, + }, scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("failed to list cluster VPCs: %w", err) + } + return vpcs.PrivateNetworks, nil +} + +func (s *scwCloudImplementation) DeleteGateway(gateway *vpcgw.Gateway) error { + // We look for gateway connexions to private networks and detach them before deleting the gateway + connexions, err := s.GetClusterGatewayNetworks(gateway.ID) + 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("error listing gateway networks: %w", err) + } + for _, connexion := range connexions { + err := s.gatewayAPI.DeleteGatewayNetwork(&vpcgw.DeleteGatewayNetworkRequest{ + Zone: s.zone, + GatewayNetworkID: connexion.ID, + CleanupDHCP: true, + }) + if err != nil { + return fmt.Errorf("failed to detach gateway %s from private network: %w", gateway.ID, err) + } + } + + // 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("error 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("error 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("error 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) DeleteLoadBalancer(loadBalancer *lb.LB) error { ipsToRelease := loadBalancer.IP @@ -425,6 +588,10 @@ func (s *scwCloudImplementation) DeleteLoadBalancer(loadBalancer *lb.LB) error { Zone: s.zone, }) if err != nil { + if is404Error(err) { + klog.V(8).Infof("Load-balancer %q (%s) was already deleted", loadBalancer.Name, loadBalancer.ID) + return nil + } return fmt.Errorf("waiting for load-balancer: %w", err) } err = s.lbAPI.DeleteLB(&lb.ZonedAPIDeleteLBRequest{ @@ -455,6 +622,28 @@ func (s *scwCloudImplementation) DeleteLoadBalancer(loadBalancer *lb.LB) error { return nil } +func (s *scwCloudImplementation) DeleteDNSRecord(record *domain.Record, domainName string) error { + recordDeleteRequest := &domain.UpdateDNSZoneRecordsRequest{ + DNSZone: domainName, + Changes: []*domain.RecordChange{ + { + Delete: &domain.RecordChangeDelete{ + ID: scw.StringPtr(record.ID), + }, + }, + }, + } + _, err := s.domainAPI.UpdateDNSZoneRecords(recordDeleteRequest) + if err != nil { + if is404Error(err) { + klog.V(8).Infof("DNS record %q (%s) was already deleted", record.Name, record.ID) + return nil + } + return fmt.Errorf("failed to delete record %s: %w", record.Name, err) + } + return nil +} + func (s *scwCloudImplementation) DeleteServer(server *instance.Server) error { srv, err := s.instanceAPI.GetServer(&instance.GetServerRequest{ Zone: s.zone, @@ -462,12 +651,25 @@ func (s *scwCloudImplementation) DeleteServer(server *instance.Server) error { }) if err != nil { if is404Error(err) { - klog.V(4).Infof("delete server %s: instance was already deleted", server.ID) + klog.V(8).Infof("Instance server %q (%s) was already deleted", server.Name, server.ID) return nil } 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 instance %s: error detaching private network: %w", server.ID, err) + } + return err + } + // If the server is running, we turn it off and wait before deleting it if srv.Server.State == instance.ServerStateRunning { _, err := s.instanceAPI.ServerAction(&instance.ServerActionRequest{ @@ -554,3 +756,18 @@ func (s *scwCloudImplementation) DeleteVolume(volume *instance.Volume) error { return nil } + +func (s *scwCloudImplementation) DeleteVPC(privateNetwork *vpc.PrivateNetwork) error { + err := s.vpcAPI.DeletePrivateNetwork(&vpc.DeletePrivateNetworkRequest{ + PrivateNetworkID: privateNetwork.ID, + Zone: s.zone, + }) + 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 VPC %s: %w", privateNetwork.ID, err) + } + return nil +} diff --git a/upup/pkg/fi/cloudup/scaleway/utils.go b/upup/pkg/fi/cloudup/scaleway/utils.go index 4a34a2b8463dd..b06b50b19fffc 100644 --- a/upup/pkg/fi/cloudup/scaleway/utils.go +++ b/upup/pkg/fi/cloudup/scaleway/utils.go @@ -25,29 +25,11 @@ import ( "github.com/scaleway/scaleway-sdk-go/scw" k8serrors "k8s.io/apimachinery/pkg/util/errors" + kopsv "k8s.io/kops" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi" ) -// isHTTPCodeError returns true if err is an http error with code statusCode -func isHTTPCodeError(err error, statusCode int) bool { - if err == nil { - return false - } - - responseError := &scw.ResponseError{} - if errors.As(err, &responseError) && responseError.StatusCode == statusCode { - return true - } - return false -} - -// is404Error returns true if err is an HTTP 404 error -func is404Error(err error) bool { - notFoundError := &scw.ResourceNotFoundError{} - return isHTTPCodeError(err, http.StatusNotFound) || errors.As(err, ¬FoundError) -} - func ParseZoneFromClusterSpec(clusterSpec kops.ClusterSpec) (scw.Zone, error) { zone := "" for _, subnet := range clusterSpec.Networking.Subnets { @@ -128,3 +110,76 @@ func CreateValidScalewayProfile() (*scw.Profile, error) { } return profile, nil } + +func CreateScalewayClient(clientOptions ...scw.ClientOption) (*scw.Client, error) { + profile, err := CreateValidScalewayProfile() + if err != nil { + return nil, err + } + clientOptions = append(clientOptions, scw.WithProfile(profile)) + clientOptions = append(clientOptions, scw.WithUserAgent(KopsUserAgentPrefix+kopsv.Version)) + + scwClient, err := scw.NewClient(clientOptions...) + if err != nil { + return nil, err + } + return scwClient, nil +} + +// isHTTPCodeError returns true if err is an http error with code statusCode +func isHTTPCodeError(err error, statusCode int) bool { + if err == nil { + return false + } + + responseError := &scw.ResponseError{} + if errors.As(err, &responseError) && responseError.StatusCode == statusCode { + return true + } + return false +} + +// is404Error returns true if err is an HTTP 404 error +func is404Error(err error) bool { + notFoundError := &scw.ResourceNotFoundError{} + return isHTTPCodeError(err, http.StatusNotFound) || errors.As(err, ¬FoundError) +} + +func displayEnv() { + fmt.Printf("******************* Scaleway credentials *******************\n\n") + + fmt.Printf(fmt.Sprintf("SCW_ACCESS_KEY = %s\n", os.Getenv("SCW_ACCESS_KEY"))) + fmt.Printf(fmt.Sprintf("SCW_SECRET_KEY = %s\n", os.Getenv("SCW_SECRET_KEY"))) + fmt.Printf(fmt.Sprintf("SCW_DEFAULT_PROJECT_ID = %s\n", os.Getenv("SCW_DEFAULT_PROJECT_ID"))) + + fmt.Printf("\n********************* S3 credentials *********************\n\n") + + fmt.Printf(fmt.Sprintf("S3_REGION = %s\n", os.Getenv("S3_REGION"))) + fmt.Printf(fmt.Sprintf("S3_ENDPOINT = %s\n", os.Getenv("S3_ENDPOINT"))) + fmt.Printf(fmt.Sprintf("S3_ACCESS_KEY_ID = %s\n", os.Getenv("S3_ACCESS_KEY_ID"))) + fmt.Printf(fmt.Sprintf("S3_SECRET_ACCESS_KEY = %s\n", os.Getenv("S3_SECRET_ACCESS_KEY"))) + + fmt.Printf("\n\t*********** State-store bucket *************\n\n") + + fmt.Printf(fmt.Sprintf("KOPS_STATE_STORE = %s\n", os.Getenv("KOPS_STATE_STORE"))) + fmt.Printf(fmt.Sprintf("S3_BUCKET_NAME = %s\n", os.Getenv("S3_BUCKET_NAME"))) + + fmt.Printf("\n\t*********** State-store bucket *************\n\n") + + fmt.Printf(fmt.Sprintf("NODEUP_BUCKET = %s\n", os.Getenv("NODEUP_BUCKET"))) + fmt.Printf(fmt.Sprintf("UPLOAD_DEST = %s\n", os.Getenv("UPLOAD_DEST"))) + fmt.Printf(fmt.Sprintf("KOPS_BASE_URL = %s\n", os.Getenv("KOPS_BASE_URL"))) + fmt.Printf(fmt.Sprintf("KOPSCONTROLLER_IMAGE = %s\n", os.Getenv("KOPSCONTROLLER_IMAGE"))) + fmt.Printf(fmt.Sprintf("DNSCONTROLLER_IMAGE = %s\n", os.Getenv("DNSCONTROLLER_IMAGE"))) + + fmt.Printf("\n********************* Registry access *********************\n\n") + + fmt.Printf(fmt.Sprintf("DOCKER_REGISTRY = %s\n", os.Getenv("DOCKER_REGISTRY"))) + fmt.Printf(fmt.Sprintf("DOCKER_IMAGE_PREFIX = %s\n", os.Getenv("DOCKER_IMAGE_PREFIX"))) + + fmt.Printf("\n********************* Other *********************\n\n") + + fmt.Printf(fmt.Sprintf("KOPS_FEATURE_FLAGS = %s\n", os.Getenv("KOPS_FEATURE_FLAGS"))) + fmt.Printf(fmt.Sprintf("KOPS_ARCH = %s\n", os.Getenv("KOPS_ARCH"))) + fmt.Printf(fmt.Sprintf("KOPS_VERSION = %s\n\n", os.Getenv("KOPS_VERSION"))) +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/instance.go b/upup/pkg/fi/cloudup/scalewaytasks/instance.go index 17e2182ff0096..2d8c9aea87e71 100644 --- a/upup/pkg/fi/cloudup/scalewaytasks/instance.go +++ b/upup/pkg/fi/cloudup/scalewaytasks/instance.go @@ -19,12 +19,15 @@ package scalewaytasks import ( "bytes" "fmt" + "strings" "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" "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 @@ -41,6 +44,8 @@ type Instance struct { UserData *fi.Resource LoadBalancer *LoadBalancer + //Network *Network + NeedsUpdate []string } var _ fi.CloudupTask = &Instance{} @@ -60,9 +65,34 @@ func (s *Instance) Find(c *fi.CloudupContext) (*Instance, error) { if len(servers) == 0 { return nil, nil } + + // Check if servers have been added to the instance group, therefore an update is needed + if len(servers) > s.Count { + for _, server := range servers { + alreadyTagged := false + for _, tag := range server.Tags { + if tag == scaleway.TagNeedsUpdate { + alreadyTagged = true + } + } + if alreadyTagged == true { + continue + } + s.NeedsUpdate = append(s.NeedsUpdate, server.ID) + } + } + //TODO(Mia-Cross): handle other changes like image, commercial type, userdata + server := servers[0] - role := scaleway.TagRoleWorker + igName := "" + for _, tag := range server.Tags { + if strings.HasPrefix(tag, scaleway.TagInstanceGroup) { + igName = strings.TrimPrefix(tag, scaleway.TagInstanceGroup+"=") + } + } + + role := scaleway.TagRoleNode for _, tag := range server.Tags { if tag == scaleway.TagNameRolePrefix+"="+scaleway.TagRoleControlPlane { role = scaleway.TagRoleControlPlane @@ -70,7 +100,7 @@ func (s *Instance) Find(c *fi.CloudupContext) (*Instance, error) { } return &Instance{ - Name: fi.PtrTo(server.Name), + Name: fi.PtrTo(igName), Count: len(servers), Zone: fi.PtrTo(server.Zone.String()), Role: fi.PtrTo(role), @@ -79,6 +109,7 @@ func (s *Instance) Find(c *fi.CloudupContext) (*Instance, error) { Tags: server.Tags, UserData: s.UserData, Lifecycle: s.Lifecycle, + //Network: s.Network, }, nil } @@ -117,8 +148,8 @@ func (_ *Instance) CheckChanges(actual, expected, changes *Instance) error { return nil } -func (_ *Instance) RenderScw(c *fi.CloudupContext, actual, expected, changes *Instance) error { - cloud := c.T.Cloud.(scaleway.ScwCloud) +func (_ *Instance) RenderScw(t *scaleway.ScwAPITarget, actual, expected, changes *Instance) error { + cloud := t.Cloud.(scaleway.ScwCloud) instanceService := cloud.InstanceService() zone := scw.Zone(fi.ValueOf(expected.Zone)) controlPlanePrivateIPs := []string(nil) @@ -134,15 +165,51 @@ func (_ *Instance) RenderScw(c *fi.CloudupContext, actual, expected, changes *In return nil } newInstanceCount = expected.Count - actual.Count + + // Add "kops.k8s.io/needs-update" label to servers needing update + for _, serverID := range actual.NeedsUpdate { + server, err := instanceService.GetServer(&instance.GetServerRequest{ + Zone: zone, + ServerID: serverID, + }) + if err != nil { + return fmt.Errorf("error rendering server group: error listing existing servers: %w", err) + } + _, err = instanceService.UpdateServer(&instance.UpdateServerRequest{ + Zone: zone, + ServerID: serverID, + Tags: scw.StringsPtr(append(server.Server.Tags, scaleway.TagNeedsUpdate)), + }) + if err != nil { + return fmt.Errorf("error rendering server group: error adding update tag to server %q (%s): %w", server.Server.Name, serverID, err) + } + } } + // We get the private network to associate it with new instances + //pn, err := cloud.GetClusterVPCs(c.Cluster.Name) + //if err != nil { + // return fmt.Errorf("error listing private networks: %v", err) + //} + //if len(pn) != 1 { + // return fmt.Errorf("more than 1 private network named %s found", c.Cluster.Name) + //} + // If newInstanceCount > 0, we need to create new instances for this group for i := 0; i < newInstanceCount; i++ { + // We create a unique name for each server + actualCount := 0 + if actual != nil { + actualCount = actual.Count + } + // TODO(Mia-Cross): check that this works even when instances were deleted before adding some again + uniqueName := fmt.Sprintf("%s-%d", fi.ValueOf(expected.Name), i+actualCount) + // We create the instance srv, err := instanceService.CreateServer(&instance.CreateServerRequest{ Zone: zone, - Name: fi.ValueOf(expected.Name), + Name: uniqueName, CommercialType: fi.ValueOf(expected.CommercialType), Image: fi.ValueOf(expected.Image), Tags: expected.Tags, @@ -203,6 +270,26 @@ func (_ *Instance) RenderScw(c *fi.CloudupContext, actual, expected, changes *In } controlPlanePrivateIPs = append(controlPlanePrivateIPs, *server.Server.PrivateIP) } + + // We put the instance inside the private network + //pNIC, err := instanceService.CreatePrivateNIC(&instance.CreatePrivateNICRequest{ + // Zone: zone, + // ServerID: srv.Server.ID, + // PrivateNetworkID: pn[0].ID, + //}) + //if err != nil { + // return fmt.Errorf("error linking instance to private network: %v", err) + //} + // + //// We wait for the private nic to be ready before proceeding + //_, err = instanceService.WaitForPrivateNIC(&instance.WaitForPrivateNICRequest{ + // ServerID: srv.Server.ID, + // PrivateNicID: pNIC.PrivateNic.ID, + // Zone: zone, + //}) + //if err != nil { + // return fmt.Errorf("error waiting for private nic: %v", err) + //} } // If newInstanceCount < 0, we need to delete instances of this group @@ -285,5 +372,104 @@ func (_ *Instance) RenderScw(c *fi.CloudupContext, actual, expected, changes *In } } + // We create NAT rules linking the gateway to our instances in order to be able to connect via SSH + // TODO(Mia-Cross): This part is for dev purposes only, remove when done + //gwService := cloud.GatewayService() + //rules := []*vpcgw.SetPATRulesRequestRule(nil) + //port := uint32(2022) + //gwNetwork, err := cloud.GetClusterGatewayNetworks(pn[0].ID) + //if err != nil { + // return err + //} + //if len(gwNetwork) < 1 { + // klog.V(4).Infof("Could not find any gateway connexion, skipping NAT rules creation") + //} else { + // entries, err := gwService.ListDHCPEntries(&vpcgw.ListDHCPEntriesRequest{ + // Zone: zone, + // GatewayNetworkID: scw.StringPtr(gwNetwork[0].ID), + // }, scw.WithAllPages()) + // if err != nil { + // return fmt.Errorf("error listing DHCP entries") + // } + // klog.V(4).Infof("=== DHCP entries are %v", entries.DHCPEntries) + // for _, entry := range entries.DHCPEntries { + // rules = append(rules, &vpcgw.SetPATRulesRequestRule{ + // PublicPort: port, + // PrivateIP: entry.IPAddress, + // PrivatePort: 22, + // Protocol: "both", + // }) + // port += 1 + // } + // + // _, err = gwService.SetPATRules(&vpcgw.SetPATRulesRequest{ + // Zone: zone, + // GatewayID: gwNetwork[0].GatewayID, + // PatRules: rules, + // }) + // if err != nil { + // return fmt.Errorf("error setting PAT rules for gateway") + // } + // klog.V(4).Infof("=== rules set") + //} + return nil } + +type terraformInstanceIP struct{} + +type terraformUserData struct { + CloudInit *terraformWriter.Literal `cty:"cloud-init"` +} + +type terraformInstance struct { + Name *string `cty:"name"` + IPID *terraformWriter.Literal `cty:"ip_id"` + Type *string `cty:"type"` + Tags []string `cty:"tags"` + Image *string `cty:"image"` + UserData map[string]*terraformWriter.Literal `cty:"user_data"` +} + +func (_ *Instance) RenderTerraform(t *terraform.TerraformTarget, actual, expected, changes *Instance) error { + tfName := strings.Replace(fi.ValueOf(expected.Name), ".", "-", -1) + { + tf := terraformInstanceIP{} + err := t.RenderResource("scaleway_instance_ip", tfName, tf) + if err != nil { + return err + } + } + { + tf := terraformInstance{ + Name: expected.Name, + IPID: expected.TerraformLinkIPID(tfName), + Type: expected.CommercialType, + Tags: expected.Tags, + Image: expected.Image, + //UserData: expected.UserData, + } + if expected.UserData != nil { + userDataBytes, err := fi.ResourceAsBytes(fi.ValueOf(expected.UserData)) + if err != nil { + return err + } + if userDataBytes != nil { + tfUserData, err := t.AddFileBytes("scaleway_instance_server", tfName, "user_data", userDataBytes, true) + if err != nil { + return err + } + tf.UserData = map[string]*terraformWriter.Literal{ + "cloud-init": tfUserData, + } + //tf.UserData, err = + } + } + + return t.RenderResource("scaleway_instance_server", tfName, tf) + } +} + +func (i *Instance) TerraformLinkIPID(tfName string) *terraformWriter.Literal { + return terraformWriter.LiteralProperty("scaleway_instance_ip", tfName, "id") +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/lb_backend.go b/upup/pkg/fi/cloudup/scalewaytasks/lb_backend.go index e667c07686379..506817068b3b5 100644 --- a/upup/pkg/fi/cloudup/scalewaytasks/lb_backend.go +++ b/upup/pkg/fi/cloudup/scalewaytasks/lb_backend.go @@ -18,11 +18,14 @@ package scalewaytasks import ( "fmt" + "strings" "github.com/scaleway/scaleway-sdk-go/api/lb/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 @@ -163,3 +166,27 @@ func (l *LBBackend) RenderScw(t *scaleway.ScwAPITarget, actual, expected, change return nil } + +type terraformLBBackend struct { + LBID *terraformWriter.Literal `cty:"lb_id"` + Name *string `cty:"name"` + ForwardProtocol *string `cty:"forward_protocol"` + ForwardPort *int32 `cty:"forward_port"` + //LBName *string +} + +func (l *LBBackend) RenderTerraform(t *terraform.TerraformTarget, actual, expected, changes *LBBackend) error { + //clusterName := t.Cloud.(scaleway.ScwCloud).ClusterName() + tfName := strings.Replace(fi.ValueOf(expected.LoadBalancer.Name), ".", "-", -1) + tf := terraformLBBackend{ + LBID: expected.TerraformLinkLBID(tfName), + Name: expected.Name, + ForwardProtocol: expected.ForwardProtocol, + ForwardPort: expected.ForwardPort, + } + return t.RenderResource("scaleway_lb_backend", tfName, tf) +} + +func (l *LBBackend) TerraformLinkLBID(tfName string) *terraformWriter.Literal { + return terraformWriter.LiteralProperty("scaleway_lb", tfName, "id") +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/lb_frontend.go b/upup/pkg/fi/cloudup/scalewaytasks/lb_frontend.go index 1b17064e778e0..986cf3c55f806 100644 --- a/upup/pkg/fi/cloudup/scalewaytasks/lb_frontend.go +++ b/upup/pkg/fi/cloudup/scalewaytasks/lb_frontend.go @@ -18,11 +18,14 @@ package scalewaytasks import ( "fmt" + "strings" "github.com/scaleway/scaleway-sdk-go/api/lb/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 @@ -147,3 +150,26 @@ func (l *LBFrontend) RenderScw(t *scaleway.ScwAPITarget, actual, expected, chang return nil } + +type terraformLBFrontend struct { + //BackendID *terraformWriter.Literal `cty:"backend_id"` + BackendID *string `cty:"backend_id"` + LBID *terraformWriter.Literal `cty:"lb_id"` + Name *string `cty:"name"` + InboundPort *int32 `cty:"inbound_port"` +} + +func (_ *LBFrontend) RenderTerraform(t *terraform.TerraformTarget, actual, expected, changes *LBFrontend) error { + tfName := strings.Replace(fi.ValueOf(expected.LoadBalancer.Name), ".", "-", -1) + tf := terraformLBFrontend{ + LBID: expected.TerraformLinkLBID(tfName), + BackendID: expected.LBBackend.ID, + Name: expected.Name, + InboundPort: expected.InboundPort, + } + return t.RenderResource("scaleway_lb_frontend", tfName, tf) +} + +func (l *LBFrontend) TerraformLinkLBID(tfName string) *terraformWriter.Literal { + return terraformWriter.LiteralProperty("scaleway_lb", tfName, "id") +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/loadbalancer.go b/upup/pkg/fi/cloudup/scalewaytasks/loadbalancer.go index c6081c2d7925d..8da8aa93dd31a 100644 --- a/upup/pkg/fi/cloudup/scalewaytasks/loadbalancer.go +++ b/upup/pkg/fi/cloudup/scalewaytasks/loadbalancer.go @@ -18,10 +18,13 @@ package scalewaytasks import ( "fmt" + "strings" "k8s.io/klog/v2" "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" "github.com/scaleway/scaleway-sdk-go/api/lb/v1" "github.com/scaleway/scaleway-sdk-go/scw" @@ -39,6 +42,10 @@ type LoadBalancer struct { Description string SslCompatibilityLevel string ForAPIServer bool + + VPCId *string // set if Cluster.Spec.NetworkID is + //VPCName *string // set if Cluster.Spec.NetworkCIDR is + //NetworkCIDR *string // set if Cluster.Spec.NetworkCIDR is } var _ fi.CompareWithID = &LoadBalancer{} @@ -161,7 +168,7 @@ func (l *LoadBalancer) RenderScw(t *scaleway.ScwAPITarget, actual, expected, cha } else { - klog.Infof("Creating new load-balancer with name %q", expected.Name) + klog.Infof("Creating new load-balancer with name %q", fi.ValueOf(expected.Name)) lbCreated, err := lbService.CreateLB(&lb.ZonedAPICreateLBRequest{ Zone: scw.Zone(fi.ValueOf(expected.Zone)), @@ -191,3 +198,44 @@ func (l *LoadBalancer) RenderScw(t *scaleway.ScwAPITarget, actual, expected, cha return nil } + +type terraformLBIP struct { + ID *string +} + +type terraformLoadBalancer struct { + Type string `cty:"type"` + Name *string `cty:"name"` + Tags []string `cty:"tags"` + IPID *terraformWriter.Literal `cty:"ip_id"` + //LBName *string +} + +func (_ *LoadBalancer) RenderTerraform(t *terraform.TerraformTarget, actual, expected, changes *LoadBalancer) error { + tfName := strings.Replace(fi.ValueOf(expected.Name), ".", "-", -1) + { + tf := terraformLBIP{} + err := t.RenderResource("scaleway_lb_ip", tfName, tf) + if err != nil { + return err + } + } + { + tf := terraformLoadBalancer{ + Type: "LB-S", + Name: expected.Name, + Tags: expected.Tags, + IPID: expected.TerraformLinkIPID(tfName), + //LBName: + } + err := t.RenderResource("scaleway_lb", tfName, tf) + if err != nil { + return err + } + } + return nil +} + +func (l *LoadBalancer) TerraformLinkIPID(tfName string) *terraformWriter.Literal { + return terraformWriter.LiteralProperty("scaleway_lb_ip", tfName, "id") +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/network.go b/upup/pkg/fi/cloudup/scalewaytasks/network.go new file mode 100644 index 0000000000000..732e870552f6b --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/network.go @@ -0,0 +1,182 @@ +package scalewaytasks + +import ( + "fmt" + "net" + "os" + + "github.com/scaleway/scaleway-sdk-go/api/vpc/v1" + "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" +) + +// +kops:fitask +type Network struct { + Name *string + ID *string + Lifecycle fi.Lifecycle + IPRange *string + Zone *string + Tags []string + //DHCP vpcgw.DHCP + //Gateway vpcgw.Gateway + //Connexion +} + +var _ fi.CompareWithID = &Network{} + +func (v *Network) CompareWithID() *string { + return v.ID +} + +func (v *Network) Find(c *fi.CloudupContext) (*Network, error) { + cloud := c.T.Cloud.(scaleway.ScwCloud) + vpcService := cloud.VPCService() + + vpcs, err := vpcService.ListPrivateNetworks(&vpc.ListPrivateNetworksRequest{ + Zone: scw.Zone(cloud.Zone()), + }, scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("error listing private networks: %s", err) + } + + for _, vpc := range vpcs.PrivateNetworks { + if vpc.Name == fi.ValueOf(v.Name) { + subnet := "" + if len(vpc.Subnets) > 0 { + subnet = vpc.Subnets[0].String() + } + return &Network{ + Name: fi.PtrTo(vpc.Name), + ID: fi.PtrTo(vpc.ID), + Lifecycle: v.Lifecycle, + IPRange: &subnet, + Zone: fi.PtrTo(string(vpc.Zone)), + Tags: vpc.Tags, + }, nil + } + } + return nil, nil +} + +func (v *Network) Run(c *fi.CloudupContext) error { + return fi.CloudupDefaultDeltaRunMethod(v, c) +} + +func (_ *Network) CheckChanges(a, e, changes *Network) error { + if a != 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 e.Name == nil { + return fi.RequiredField("Name") + } + if e.Zone == nil { + return fi.RequiredField("Zone") + } + } + return nil +} + +func (_ *Network) RenderScw(t *scaleway.ScwAPITarget, a, e, changes *Network) error { + if a != nil { + return nil + } + + vpcService := t.Cloud.VPCService() + gwService := t.Cloud.GatewayService() + + // We create a private network + pn, err := vpcService.CreatePrivateNetwork(&vpc.CreatePrivateNetworkRequest{ + Zone: scw.Zone(fi.ValueOf(e.Zone)), + Name: fi.ValueOf(e.Name), + ProjectID: os.Getenv("SCW_DEFAULT_PROJECT_ID"), + Tags: e.Tags, + }) + if err != nil { + return fmt.Errorf("error rendering network: %s", err) + } + + // We create a public gateway + gw, err := gwService.CreateGateway(&vpcgw.CreateGatewayRequest{ + Zone: scw.Zone(fi.ValueOf(e.Zone)), + ProjectID: os.Getenv("SCW_DEFAULT_PROJECT_ID"), + Name: fi.ValueOf(e.Name), + Tags: e.Tags, + Type: "VPC-GW-S", + UpstreamDNSServers: nil, + IPID: nil, + EnableSMTP: false, + EnableBastion: true, + BastionPort: scw.Uint32Ptr(1042), // TODO(Mia-Cross): drop the bastion if it doesn't work ?? + }) + if err != nil { + return fmt.Errorf("error rendering gateway: %s", err) + } + + _, subnet, err := net.ParseCIDR(fi.ValueOf(e.IPRange)) + if err != nil { + return fmt.Errorf("error parsing CIDR: %s", err) + } + + // We create a DHCP server + dhcp, err := gwService.CreateDHCP(&vpcgw.CreateDHCPRequest{ + Zone: scw.Zone(fi.ValueOf(e.Zone)), + ProjectID: os.Getenv("SCW_DEFAULT_PROJECT_ID"), + Subnet: scw.IPNet{IPNet: *subnet}, + Address: nil, + PoolLow: nil, + PoolHigh: nil, + EnableDynamic: nil, + ValidLifetime: nil, + RenewTimer: nil, + RebindTimer: nil, + PushDefaultRoute: nil, + PushDNSServer: nil, + DNSServersOverride: nil, + DNSSearch: nil, + DNSLocalName: nil, + }) + if err != nil { + return fmt.Errorf("error rendering DHCP: %v", err) + } + + // We link the gateway (with DHCP) to the private network once it's in a stable state + _, err = gwService.WaitForGateway(&vpcgw.WaitForGatewayRequest{ + GatewayID: gw.ID, + Zone: scw.Zone(fi.ValueOf(e.Zone)), + }) + if err != nil { + return fmt.Errorf("error waiting for gateway: %v", err) + } + gwn, err := gwService.CreateGatewayNetwork(&vpcgw.CreateGatewayNetworkRequest{ + Zone: scw.Zone(fi.ValueOf(e.Zone)), + GatewayID: gw.ID, + PrivateNetworkID: pn.ID, + EnableMasquerade: true, + DHCPID: scw.StringPtr(dhcp.ID), + Address: nil, + EnableDHCP: scw.BoolPtr(true), + }) + if err != nil { + return fmt.Errorf("error rendering gateway network with DHCP: %v", err) + } + _, err = gwService.WaitForGatewayNetwork(&vpcgw.WaitForGatewayNetworkRequest{ + GatewayNetworkID: gwn.ID, + Zone: scw.Zone(fi.ValueOf(e.Zone)), + }) + if err != nil { + return fmt.Errorf("error waiting for gateway: %v", err) + } + + return nil +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/network_fitask.go b/upup/pkg/fi/cloudup/scalewaytasks/network_fitask.go new file mode 100644 index 0000000000000..2547799778791 --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/network_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" +) + +// Network + +var _ fi.HasLifecycle = &Network{} + +// GetLifecycle returns the Lifecycle of the object, implementing fi.HasLifecycle +func (o *Network) GetLifecycle() fi.Lifecycle { + return o.Lifecycle +} + +// SetLifecycle sets the Lifecycle of the object, implementing fi.SetLifecycle +func (o *Network) SetLifecycle(lifecycle fi.Lifecycle) { + o.Lifecycle = lifecycle +} + +var _ fi.HasName = &Network{} + +// GetName returns the Name of the object, implementing fi.HasName +func (o *Network) GetName() *string { + return o.Name +} + +// String is the stringer function for the task, producing readable output using fi.TaskAsString +func (o *Network) String() string { + return fi.CloudupTaskAsString(o) +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/sshkey.go b/upup/pkg/fi/cloudup/scalewaytasks/sshkey.go index b660a270625c0..d2622ac563786 100644 --- a/upup/pkg/fi/cloudup/scalewaytasks/sshkey.go +++ b/upup/pkg/fi/cloudup/scalewaytasks/sshkey.go @@ -24,6 +24,7 @@ import ( "k8s.io/kops/pkg/pki" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" + "k8s.io/kops/upup/pkg/fi/cloudup/terraform" iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1" "github.com/scaleway/scaleway-sdk-go/scw" @@ -107,13 +108,13 @@ func (s *SSHKey) CheckChanges(actual, expected, changes *SSHKey) error { return nil } -func (*SSHKey) RenderScw(c *fi.CloudupContext, actual, expected, changes *SSHKey) error { +func (*SSHKey) RenderScw(t *scaleway.ScwAPITarget, actual, expected, changes *SSHKey) error { if actual != nil { klog.Infof("Scaleway does not support changes to ssh keys for the moment") return nil } - cloud := c.T.Cloud.(scaleway.ScwCloud) + cloud := t.Cloud.(scaleway.ScwCloud) name := fi.ValueOf(expected.Name) if name == "" { @@ -141,3 +142,21 @@ func (*SSHKey) RenderScw(c *fi.CloudupContext, actual, expected, changes *SSHKey return nil } + +type terraformSSHKey struct { + Name *string `cty:"name"` + PublicKey *string `cty:"public_key"` +} + +func (_ *SSHKey) RenderTerraform(t *terraform.TerraformTarget, actual, expected, changes *SSHKey) error { + tfName := strings.Replace(fi.ValueOf(expected.Name), ".", "-", -1) + publicKeyStr, err := fi.ResourceAsString(fi.ValueOf(expected.PublicKey)) + if err != nil { + return err + } + tf := terraformSSHKey{ + Name: expected.Name, + PublicKey: fi.PtrTo(publicKeyStr), + } + return t.RenderResource("scaleway_iam_ssh_key", tfName, tf) +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/volume.go b/upup/pkg/fi/cloudup/scalewaytasks/volume.go index a71d49bc554f2..d90782f986735 100644 --- a/upup/pkg/fi/cloudup/scalewaytasks/volume.go +++ b/upup/pkg/fi/cloudup/scalewaytasks/volume.go @@ -17,11 +17,15 @@ 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/klog/v2" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" + "k8s.io/kops/upup/pkg/fi/cloudup/terraform" ) // +kops:fitask @@ -75,8 +79,8 @@ func (v *Volume) Run(c *fi.CloudupContext) error { return fi.CloudupDefaultDeltaRunMethod(v, c) } -func (_ *Volume) CheckChanges(a, e, changes *Volume) error { - if a != nil { +func (_ *Volume) CheckChanges(actual, expected, changes *Volume) error { + if actual != nil { if changes.Name != nil { return fi.CannotChangeField("Name") } @@ -87,33 +91,56 @@ func (_ *Volume) CheckChanges(a, e, changes *Volume) error { return fi.CannotChangeField("Zone") } } else { - if e.Name == nil { + if expected.Name == nil { return fi.RequiredField("Name") } - if e.Size == nil { + if expected.Size == nil { return fi.RequiredField("Size") } - if e.Zone == nil { + if expected.Zone == nil { return fi.RequiredField("Zone") } } return nil } -func (_ *Volume) RenderScw(t *scaleway.ScwAPITarget, a, e, changes *Volume) error { - if a != nil { +func (_ *Volume) RenderScw(t *scaleway.ScwAPITarget, actual, expected, changes *Volume) error { + if actual != nil { + // TODO(Mia-Cross): handle the update of tags at least klog.Infof("Scaleway does not support changes to volumes for the moment") return nil } instanceService := t.Cloud.InstanceService() _, err := instanceService.CreateVolume(&instance.CreateVolumeRequest{ - Zone: scw.Zone(fi.ValueOf(e.Zone)), - Name: fi.ValueOf(e.Name), - VolumeType: instance.VolumeVolumeType(fi.ValueOf(e.Type)), - Size: scw.SizePtr(scw.Size(fi.ValueOf(e.Size))), - Tags: e.Tags, + Zone: scw.Zone(fi.ValueOf(expected.Zone)), + Name: fi.ValueOf(expected.Name), + VolumeType: instance.VolumeVolumeType(fi.ValueOf(expected.Type)), + Size: scw.SizePtr(scw.Size(fi.ValueOf(expected.Size))), + Tags: expected.Tags, }) + if err != nil { + return fmt.Errorf("rendering volume: %w", err) + } return err } + +type terraformVolume struct { + Name *string `cty:"name"` + SizeInGB *int `cty:"size_in_gb"` + Type *string `cty:"type"` + Tags []string `cty:"tags"` +} + +func (_ *Volume) RenderTerraform(t *terraform.TerraformTarget, actual, expected, changes *Volume) error { + tfName := strings.Replace(fi.ValueOf(expected.Name), ".", "-", -1) + tf := &terraformVolume{ + Name: expected.Name, + SizeInGB: fi.PtrTo(int(fi.ValueOf(expected.Size) / 1e9)), + Type: expected.Type, + Tags: expected.Tags, + } + + return t.RenderResource("scaleway_instance_volume", tfName, tf) +} diff --git a/upup/pkg/fi/cloudup/template_functions.go b/upup/pkg/fi/cloudup/template_functions.go index dd49da0040842..3620f9743c9c8 100644 --- a/upup/pkg/fi/cloudup/template_functions.go +++ b/upup/pkg/fi/cloudup/template_functions.go @@ -219,6 +219,9 @@ func (tf *TemplateFunctions) AddTo(dest template.FuncMap, secretStore fi.SecretS scwCloud := tf.cloud.(scaleway.ScwCloud) return scwCloud.Zone() } + dest["SCW_DNS_ZONE"] = func() string { + return cluster.Spec.DNSZone + } if featureflag.Spotinst.Enabled() { if creds, err := spotinst.LoadCredentials(); err == nil { @@ -609,6 +612,8 @@ func (tf *TemplateFunctions) DNSControllerArgv() ([]string, error) { argv = append(argv, "--dns=google-clouddns") case kops.CloudProviderDO: argv = append(argv, "--dns=digitalocean") + case kops.CloudProviderScaleway: + argv = append(argv, "--dns=scaleway") default: return nil, fmt.Errorf("unhandled cloudprovider %q", cluster.Spec.GetCloudProvider()) diff --git a/upup/pkg/fi/cloudup/utils.go b/upup/pkg/fi/cloudup/utils.go index d29cb89db8967..8f0e91c5ff27e 100644 --- a/upup/pkg/fi/cloudup/utils.go +++ b/upup/pkg/fi/cloudup/utils.go @@ -165,11 +165,11 @@ func BuildCloud(cluster *kops.Cluster) (fi.Cloud, error) { { zone, err := scaleway.ParseZoneFromClusterSpec(cluster.Spec) if err != nil { - return nil, fmt.Errorf("error initializing Scaleway cloud: %w", err) + return nil, fmt.Errorf("initializing Scaleway cloud: %w", err) } region, err := scaleway.ParseRegionFromZone(zone) if err != nil { - return nil, fmt.Errorf("error initializing Scaleway cloud: %w", err) + return nil, fmt.Errorf("initializing Scaleway cloud: %w", err) } cloudTags := map[string]string{ diff --git a/vendor/github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1/domain_sdk.go b/vendor/github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1/domain_sdk.go new file mode 100644 index 0000000000000..aec8ded548e08 --- /dev/null +++ b/vendor/github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1/domain_sdk.go @@ -0,0 +1,4153 @@ +// This file was automatically generated. DO NOT EDIT. +// If you have any remark or suggestion do not hesitate to open an issue. + +// Package domain provides methods and message types of the domain v2beta1 API. +package domain + +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 +) + +// API: DNS API +// +// Manage your DNS zones and records. +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, + } +} + +// RegistrarAPI: domains registrar API +// +// Manage your domains and contacts. +type RegistrarAPI struct { + client *scw.Client +} + +// NewRegistrarAPI returns a RegistrarAPI object from a Scaleway client. +func NewRegistrarAPI(client *scw.Client) *RegistrarAPI { + return &RegistrarAPI{ + client: client, + } +} + +type ContactEmailStatus string + +const ( + // ContactEmailStatusEmailStatusUnknown is [insert doc]. + ContactEmailStatusEmailStatusUnknown = ContactEmailStatus("email_status_unknown") + // ContactEmailStatusValidated is [insert doc]. + ContactEmailStatusValidated = ContactEmailStatus("validated") + // ContactEmailStatusNotValidated is [insert doc]. + ContactEmailStatusNotValidated = ContactEmailStatus("not_validated") + // ContactEmailStatusInvalidEmail is [insert doc]. + ContactEmailStatusInvalidEmail = ContactEmailStatus("invalid_email") +) + +func (enum ContactEmailStatus) String() string { + if enum == "" { + // return default value if empty + return "email_status_unknown" + } + return string(enum) +} + +func (enum ContactEmailStatus) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ContactEmailStatus) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ContactEmailStatus(ContactEmailStatus(tmp).String()) + return nil +} + +type ContactExtensionFRMode string + +const ( + // ContactExtensionFRModeModeUnknown is [insert doc]. + ContactExtensionFRModeModeUnknown = ContactExtensionFRMode("mode_unknown") + // ContactExtensionFRModeIndividual is [insert doc]. + ContactExtensionFRModeIndividual = ContactExtensionFRMode("individual") + // ContactExtensionFRModeCompanyIdentificationCode is [insert doc]. + ContactExtensionFRModeCompanyIdentificationCode = ContactExtensionFRMode("company_identification_code") + // ContactExtensionFRModeDuns is [insert doc]. + ContactExtensionFRModeDuns = ContactExtensionFRMode("duns") + // ContactExtensionFRModeLocal is [insert doc]. + ContactExtensionFRModeLocal = ContactExtensionFRMode("local") + // ContactExtensionFRModeAssociation is [insert doc]. + ContactExtensionFRModeAssociation = ContactExtensionFRMode("association") + // ContactExtensionFRModeTrademark is [insert doc]. + ContactExtensionFRModeTrademark = ContactExtensionFRMode("trademark") + // ContactExtensionFRModeCodeAuthAfnic is [insert doc]. + ContactExtensionFRModeCodeAuthAfnic = ContactExtensionFRMode("code_auth_afnic") +) + +func (enum ContactExtensionFRMode) String() string { + if enum == "" { + // return default value if empty + return "mode_unknown" + } + return string(enum) +} + +func (enum ContactExtensionFRMode) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ContactExtensionFRMode) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ContactExtensionFRMode(ContactExtensionFRMode(tmp).String()) + return nil +} + +type ContactExtensionNLLegalForm string + +const ( + // ContactExtensionNLLegalFormLegalFormUnknown is [insert doc]. + ContactExtensionNLLegalFormLegalFormUnknown = ContactExtensionNLLegalForm("legal_form_unknown") + // ContactExtensionNLLegalFormOther is [insert doc]. + ContactExtensionNLLegalFormOther = ContactExtensionNLLegalForm("other") + // ContactExtensionNLLegalFormNonDutchEuCompany is [insert doc]. + ContactExtensionNLLegalFormNonDutchEuCompany = ContactExtensionNLLegalForm("non_dutch_eu_company") + // ContactExtensionNLLegalFormNonDutchLegalFormEnterpriseSubsidiary is [insert doc]. + ContactExtensionNLLegalFormNonDutchLegalFormEnterpriseSubsidiary = ContactExtensionNLLegalForm("non_dutch_legal_form_enterprise_subsidiary") + // ContactExtensionNLLegalFormLimitedCompany is [insert doc]. + ContactExtensionNLLegalFormLimitedCompany = ContactExtensionNLLegalForm("limited_company") + // ContactExtensionNLLegalFormLimitedCompanyInFormation is [insert doc]. + ContactExtensionNLLegalFormLimitedCompanyInFormation = ContactExtensionNLLegalForm("limited_company_in_formation") + // ContactExtensionNLLegalFormCooperative is [insert doc]. + ContactExtensionNLLegalFormCooperative = ContactExtensionNLLegalForm("cooperative") + // ContactExtensionNLLegalFormLimitedPartnership is [insert doc]. + ContactExtensionNLLegalFormLimitedPartnership = ContactExtensionNLLegalForm("limited_partnership") + // ContactExtensionNLLegalFormSoleCompany is [insert doc]. + ContactExtensionNLLegalFormSoleCompany = ContactExtensionNLLegalForm("sole_company") + // ContactExtensionNLLegalFormEuropeanEconomicInterestGroup is [insert doc]. + ContactExtensionNLLegalFormEuropeanEconomicInterestGroup = ContactExtensionNLLegalForm("european_economic_interest_group") + // ContactExtensionNLLegalFormReligiousEntity is [insert doc]. + ContactExtensionNLLegalFormReligiousEntity = ContactExtensionNLLegalForm("religious_entity") + // ContactExtensionNLLegalFormPartnership is [insert doc]. + ContactExtensionNLLegalFormPartnership = ContactExtensionNLLegalForm("partnership") + // ContactExtensionNLLegalFormPublicCompany is [insert doc]. + ContactExtensionNLLegalFormPublicCompany = ContactExtensionNLLegalForm("public_company") + // ContactExtensionNLLegalFormMutualBenefitCompany is [insert doc]. + ContactExtensionNLLegalFormMutualBenefitCompany = ContactExtensionNLLegalForm("mutual_benefit_company") + // ContactExtensionNLLegalFormResidential is [insert doc]. + ContactExtensionNLLegalFormResidential = ContactExtensionNLLegalForm("residential") + // ContactExtensionNLLegalFormShippingCompany is [insert doc]. + ContactExtensionNLLegalFormShippingCompany = ContactExtensionNLLegalForm("shipping_company") + // ContactExtensionNLLegalFormFoundation is [insert doc]. + ContactExtensionNLLegalFormFoundation = ContactExtensionNLLegalForm("foundation") + // ContactExtensionNLLegalFormAssociation is [insert doc]. + ContactExtensionNLLegalFormAssociation = ContactExtensionNLLegalForm("association") + // ContactExtensionNLLegalFormTradingPartnership is [insert doc]. + ContactExtensionNLLegalFormTradingPartnership = ContactExtensionNLLegalForm("trading_partnership") +) + +func (enum ContactExtensionNLLegalForm) String() string { + if enum == "" { + // return default value if empty + return "legal_form_unknown" + } + return string(enum) +} + +func (enum ContactExtensionNLLegalForm) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ContactExtensionNLLegalForm) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ContactExtensionNLLegalForm(ContactExtensionNLLegalForm(tmp).String()) + return nil +} + +type ContactLegalForm string + +const ( + // ContactLegalFormLegalFormUnknown is [insert doc]. + ContactLegalFormLegalFormUnknown = ContactLegalForm("legal_form_unknown") + // ContactLegalFormIndividual is [insert doc]. + ContactLegalFormIndividual = ContactLegalForm("individual") + // ContactLegalFormCorporate is [insert doc]. + ContactLegalFormCorporate = ContactLegalForm("corporate") + // ContactLegalFormAssociation is [insert doc]. + ContactLegalFormAssociation = ContactLegalForm("association") + // ContactLegalFormOther is [insert doc]. + ContactLegalFormOther = ContactLegalForm("other") +) + +func (enum ContactLegalForm) String() string { + if enum == "" { + // return default value if empty + return "legal_form_unknown" + } + return string(enum) +} + +func (enum ContactLegalForm) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ContactLegalForm) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ContactLegalForm(ContactLegalForm(tmp).String()) + return nil +} + +type DNSZoneStatus string + +const ( + // DNSZoneStatusUnknown is [insert doc]. + DNSZoneStatusUnknown = DNSZoneStatus("unknown") + // DNSZoneStatusActive is [insert doc]. + DNSZoneStatusActive = DNSZoneStatus("active") + // DNSZoneStatusPending is [insert doc]. + DNSZoneStatusPending = DNSZoneStatus("pending") + // DNSZoneStatusError is [insert doc]. + DNSZoneStatusError = DNSZoneStatus("error") + // DNSZoneStatusLocked is [insert doc]. + DNSZoneStatusLocked = DNSZoneStatus("locked") +) + +func (enum DNSZoneStatus) String() string { + if enum == "" { + // return default value if empty + return "unknown" + } + return string(enum) +} + +func (enum DNSZoneStatus) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *DNSZoneStatus) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = DNSZoneStatus(DNSZoneStatus(tmp).String()) + return nil +} + +type DSRecordAlgorithm string + +const ( + // DSRecordAlgorithmRsamd5 is [insert doc]. + DSRecordAlgorithmRsamd5 = DSRecordAlgorithm("rsamd5") + // DSRecordAlgorithmDh is [insert doc]. + DSRecordAlgorithmDh = DSRecordAlgorithm("dh") + // DSRecordAlgorithmDsa is [insert doc]. + DSRecordAlgorithmDsa = DSRecordAlgorithm("dsa") + // DSRecordAlgorithmRsasha1 is [insert doc]. + DSRecordAlgorithmRsasha1 = DSRecordAlgorithm("rsasha1") + // DSRecordAlgorithmDsaNsec3Sha1 is [insert doc]. + DSRecordAlgorithmDsaNsec3Sha1 = DSRecordAlgorithm("dsa_nsec3_sha1") + // DSRecordAlgorithmRsasha1Nsec3Sha1 is [insert doc]. + DSRecordAlgorithmRsasha1Nsec3Sha1 = DSRecordAlgorithm("rsasha1_nsec3_sha1") + // DSRecordAlgorithmRsasha256 is [insert doc]. + DSRecordAlgorithmRsasha256 = DSRecordAlgorithm("rsasha256") + // DSRecordAlgorithmRsasha512 is [insert doc]. + DSRecordAlgorithmRsasha512 = DSRecordAlgorithm("rsasha512") + // DSRecordAlgorithmEccGost is [insert doc]. + DSRecordAlgorithmEccGost = DSRecordAlgorithm("ecc_gost") + // DSRecordAlgorithmEcdsap256sha256 is [insert doc]. + DSRecordAlgorithmEcdsap256sha256 = DSRecordAlgorithm("ecdsap256sha256") + // DSRecordAlgorithmEcdsap384sha384 is [insert doc]. + DSRecordAlgorithmEcdsap384sha384 = DSRecordAlgorithm("ecdsap384sha384") + // DSRecordAlgorithmEd25519 is [insert doc]. + DSRecordAlgorithmEd25519 = DSRecordAlgorithm("ed25519") + // DSRecordAlgorithmEd448 is [insert doc]. + DSRecordAlgorithmEd448 = DSRecordAlgorithm("ed448") +) + +func (enum DSRecordAlgorithm) String() string { + if enum == "" { + // return default value if empty + return "rsamd5" + } + return string(enum) +} + +func (enum DSRecordAlgorithm) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *DSRecordAlgorithm) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = DSRecordAlgorithm(DSRecordAlgorithm(tmp).String()) + return nil +} + +type DSRecordDigestType string + +const ( + // DSRecordDigestTypeSha1 is [insert doc]. + DSRecordDigestTypeSha1 = DSRecordDigestType("sha_1") + // DSRecordDigestTypeSha256 is [insert doc]. + DSRecordDigestTypeSha256 = DSRecordDigestType("sha_256") + // DSRecordDigestTypeGostR34_11_94 is [insert doc]. + DSRecordDigestTypeGostR34_11_94 = DSRecordDigestType("gost_r_34_11_94") + // DSRecordDigestTypeSha384 is [insert doc]. + DSRecordDigestTypeSha384 = DSRecordDigestType("sha_384") +) + +func (enum DSRecordDigestType) String() string { + if enum == "" { + // return default value if empty + return "sha_1" + } + return string(enum) +} + +func (enum DSRecordDigestType) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *DSRecordDigestType) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = DSRecordDigestType(DSRecordDigestType(tmp).String()) + return nil +} + +type DomainFeatureStatus string + +const ( + // DomainFeatureStatusFeatureStatusUnknown is [insert doc]. + DomainFeatureStatusFeatureStatusUnknown = DomainFeatureStatus("feature_status_unknown") + // DomainFeatureStatusEnabling is [insert doc]. + DomainFeatureStatusEnabling = DomainFeatureStatus("enabling") + // DomainFeatureStatusEnabled is [insert doc]. + DomainFeatureStatusEnabled = DomainFeatureStatus("enabled") + // DomainFeatureStatusDisabling is [insert doc]. + DomainFeatureStatusDisabling = DomainFeatureStatus("disabling") + // DomainFeatureStatusDisabled is [insert doc]. + DomainFeatureStatusDisabled = DomainFeatureStatus("disabled") +) + +func (enum DomainFeatureStatus) String() string { + if enum == "" { + // return default value if empty + return "feature_status_unknown" + } + return string(enum) +} + +func (enum DomainFeatureStatus) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *DomainFeatureStatus) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = DomainFeatureStatus(DomainFeatureStatus(tmp).String()) + return nil +} + +type DomainRegistrationStatusTransferStatus string + +const ( + // DomainRegistrationStatusTransferStatusStatusUnknown is [insert doc]. + DomainRegistrationStatusTransferStatusStatusUnknown = DomainRegistrationStatusTransferStatus("status_unknown") + // DomainRegistrationStatusTransferStatusPending is [insert doc]. + DomainRegistrationStatusTransferStatusPending = DomainRegistrationStatusTransferStatus("pending") + // DomainRegistrationStatusTransferStatusWaitingVote is [insert doc]. + DomainRegistrationStatusTransferStatusWaitingVote = DomainRegistrationStatusTransferStatus("waiting_vote") + // DomainRegistrationStatusTransferStatusRejected is [insert doc]. + DomainRegistrationStatusTransferStatusRejected = DomainRegistrationStatusTransferStatus("rejected") + // DomainRegistrationStatusTransferStatusProcessing is [insert doc]. + DomainRegistrationStatusTransferStatusProcessing = DomainRegistrationStatusTransferStatus("processing") + // DomainRegistrationStatusTransferStatusDone is [insert doc]. + DomainRegistrationStatusTransferStatusDone = DomainRegistrationStatusTransferStatus("done") +) + +func (enum DomainRegistrationStatusTransferStatus) String() string { + if enum == "" { + // return default value if empty + return "status_unknown" + } + return string(enum) +} + +func (enum DomainRegistrationStatusTransferStatus) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *DomainRegistrationStatusTransferStatus) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = DomainRegistrationStatusTransferStatus(DomainRegistrationStatusTransferStatus(tmp).String()) + return nil +} + +type DomainStatus string + +const ( + // DomainStatusStatusUnknown is [insert doc]. + DomainStatusStatusUnknown = DomainStatus("status_unknown") + // DomainStatusActive is [insert doc]. + DomainStatusActive = DomainStatus("active") + // DomainStatusCreating is [insert doc]. + DomainStatusCreating = DomainStatus("creating") + // DomainStatusCreateError is [insert doc]. + DomainStatusCreateError = DomainStatus("create_error") + // DomainStatusRenewing is [insert doc]. + DomainStatusRenewing = DomainStatus("renewing") + // DomainStatusRenewError is [insert doc]. + DomainStatusRenewError = DomainStatus("renew_error") + // DomainStatusXfering is [insert doc]. + DomainStatusXfering = DomainStatus("xfering") + // DomainStatusXferError is [insert doc]. + DomainStatusXferError = DomainStatus("xfer_error") + // DomainStatusExpired is [insert doc]. + DomainStatusExpired = DomainStatus("expired") + // DomainStatusExpiring is [insert doc]. + DomainStatusExpiring = DomainStatus("expiring") + // DomainStatusUpdating is [insert doc]. + DomainStatusUpdating = DomainStatus("updating") + // DomainStatusChecking is [insert doc]. + DomainStatusChecking = DomainStatus("checking") + // DomainStatusLocked is [insert doc]. + DomainStatusLocked = DomainStatus("locked") + // DomainStatusDeleting is [insert doc]. + DomainStatusDeleting = DomainStatus("deleting") +) + +func (enum DomainStatus) String() string { + if enum == "" { + // return default value if empty + return "status_unknown" + } + return string(enum) +} + +func (enum DomainStatus) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *DomainStatus) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = DomainStatus(DomainStatus(tmp).String()) + return nil +} + +type HostStatus string + +const ( + // HostStatusUnknownStatus is [insert doc]. + HostStatusUnknownStatus = HostStatus("unknown_status") + // HostStatusActive is [insert doc]. + HostStatusActive = HostStatus("active") + // HostStatusUpdating is [insert doc]. + HostStatusUpdating = HostStatus("updating") + // HostStatusDeleting is [insert doc]. + HostStatusDeleting = HostStatus("deleting") +) + +func (enum HostStatus) String() string { + if enum == "" { + // return default value if empty + return "unknown_status" + } + return string(enum) +} + +func (enum HostStatus) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *HostStatus) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = HostStatus(HostStatus(tmp).String()) + return nil +} + +type LanguageCode string + +const ( + // LanguageCodeUnknownLanguageCode is [insert doc]. + LanguageCodeUnknownLanguageCode = LanguageCode("unknown_language_code") + // LanguageCodeEnUS is [insert doc]. + LanguageCodeEnUS = LanguageCode("en_US") + // LanguageCodeFrFR is [insert doc]. + LanguageCodeFrFR = LanguageCode("fr_FR") +) + +func (enum LanguageCode) String() string { + if enum == "" { + // return default value if empty + return "unknown_language_code" + } + return string(enum) +} + +func (enum LanguageCode) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *LanguageCode) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = LanguageCode(LanguageCode(tmp).String()) + return nil +} + +type ListDNSZoneRecordsRequestOrderBy string + +const ( + // ListDNSZoneRecordsRequestOrderByNameAsc is [insert doc]. + ListDNSZoneRecordsRequestOrderByNameAsc = ListDNSZoneRecordsRequestOrderBy("name_asc") + // ListDNSZoneRecordsRequestOrderByNameDesc is [insert doc]. + ListDNSZoneRecordsRequestOrderByNameDesc = ListDNSZoneRecordsRequestOrderBy("name_desc") +) + +func (enum ListDNSZoneRecordsRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "name_asc" + } + return string(enum) +} + +func (enum ListDNSZoneRecordsRequestOrderBy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ListDNSZoneRecordsRequestOrderBy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ListDNSZoneRecordsRequestOrderBy(ListDNSZoneRecordsRequestOrderBy(tmp).String()) + return nil +} + +type ListDNSZonesRequestOrderBy string + +const ( + // ListDNSZonesRequestOrderByDomainAsc is [insert doc]. + ListDNSZonesRequestOrderByDomainAsc = ListDNSZonesRequestOrderBy("domain_asc") + // ListDNSZonesRequestOrderByDomainDesc is [insert doc]. + ListDNSZonesRequestOrderByDomainDesc = ListDNSZonesRequestOrderBy("domain_desc") + // ListDNSZonesRequestOrderBySubdomainAsc is [insert doc]. + ListDNSZonesRequestOrderBySubdomainAsc = ListDNSZonesRequestOrderBy("subdomain_asc") + // ListDNSZonesRequestOrderBySubdomainDesc is [insert doc]. + ListDNSZonesRequestOrderBySubdomainDesc = ListDNSZonesRequestOrderBy("subdomain_desc") +) + +func (enum ListDNSZonesRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "domain_asc" + } + return string(enum) +} + +func (enum ListDNSZonesRequestOrderBy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ListDNSZonesRequestOrderBy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ListDNSZonesRequestOrderBy(ListDNSZonesRequestOrderBy(tmp).String()) + return nil +} + +type ListDomainsRequestOrderBy string + +const ( + // ListDomainsRequestOrderByDomainAsc is [insert doc]. + ListDomainsRequestOrderByDomainAsc = ListDomainsRequestOrderBy("domain_asc") + // ListDomainsRequestOrderByDomainDesc is [insert doc]. + ListDomainsRequestOrderByDomainDesc = ListDomainsRequestOrderBy("domain_desc") +) + +func (enum ListDomainsRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "domain_asc" + } + return string(enum) +} + +func (enum ListDomainsRequestOrderBy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ListDomainsRequestOrderBy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ListDomainsRequestOrderBy(ListDomainsRequestOrderBy(tmp).String()) + return nil +} + +type ListRenewableDomainsRequestOrderBy string + +const ( + // ListRenewableDomainsRequestOrderByDomainAsc is [insert doc]. + ListRenewableDomainsRequestOrderByDomainAsc = ListRenewableDomainsRequestOrderBy("domain_asc") + // ListRenewableDomainsRequestOrderByDomainDesc is [insert doc]. + ListRenewableDomainsRequestOrderByDomainDesc = ListRenewableDomainsRequestOrderBy("domain_desc") +) + +func (enum ListRenewableDomainsRequestOrderBy) String() string { + if enum == "" { + // return default value if empty + return "domain_asc" + } + return string(enum) +} + +func (enum ListRenewableDomainsRequestOrderBy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *ListRenewableDomainsRequestOrderBy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = ListRenewableDomainsRequestOrderBy(ListRenewableDomainsRequestOrderBy(tmp).String()) + return nil +} + +type RawFormat string + +const ( + // RawFormatUnknownRawFormat is [insert doc]. + RawFormatUnknownRawFormat = RawFormat("unknown_raw_format") + // RawFormatBind is [insert doc]. + RawFormatBind = RawFormat("bind") +) + +func (enum RawFormat) String() string { + if enum == "" { + // return default value if empty + return "unknown_raw_format" + } + return string(enum) +} + +func (enum RawFormat) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *RawFormat) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = RawFormat(RawFormat(tmp).String()) + return nil +} + +type RecordHTTPServiceConfigStrategy string + +const ( + // RecordHTTPServiceConfigStrategyRandom is [insert doc]. + RecordHTTPServiceConfigStrategyRandom = RecordHTTPServiceConfigStrategy("random") + // RecordHTTPServiceConfigStrategyHashed is [insert doc]. + RecordHTTPServiceConfigStrategyHashed = RecordHTTPServiceConfigStrategy("hashed") + // RecordHTTPServiceConfigStrategyAll is [insert doc]. + RecordHTTPServiceConfigStrategyAll = RecordHTTPServiceConfigStrategy("all") +) + +func (enum RecordHTTPServiceConfigStrategy) String() string { + if enum == "" { + // return default value if empty + return "random" + } + return string(enum) +} + +func (enum RecordHTTPServiceConfigStrategy) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *RecordHTTPServiceConfigStrategy) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = RecordHTTPServiceConfigStrategy(RecordHTTPServiceConfigStrategy(tmp).String()) + return nil +} + +type RecordType string + +const ( + // RecordTypeUnknown is [insert doc]. + RecordTypeUnknown = RecordType("unknown") + // RecordTypeA is [insert doc]. + RecordTypeA = RecordType("A") + // RecordTypeAAAA is [insert doc]. + RecordTypeAAAA = RecordType("AAAA") + // RecordTypeCNAME is [insert doc]. + RecordTypeCNAME = RecordType("CNAME") + // RecordTypeTXT is [insert doc]. + RecordTypeTXT = RecordType("TXT") + // RecordTypeSRV is [insert doc]. + RecordTypeSRV = RecordType("SRV") + // RecordTypeTLSA is [insert doc]. + RecordTypeTLSA = RecordType("TLSA") + // RecordTypeMX is [insert doc]. + RecordTypeMX = RecordType("MX") + // RecordTypeNS is [insert doc]. + RecordTypeNS = RecordType("NS") + // RecordTypePTR is [insert doc]. + RecordTypePTR = RecordType("PTR") + // RecordTypeCAA is [insert doc]. + RecordTypeCAA = RecordType("CAA") + // RecordTypeALIAS is [insert doc]. + RecordTypeALIAS = RecordType("ALIAS") + // RecordTypeLOC is [insert doc]. + RecordTypeLOC = RecordType("LOC") + // RecordTypeSSHFP is [insert doc]. + RecordTypeSSHFP = RecordType("SSHFP") + // RecordTypeHINFO is [insert doc]. + RecordTypeHINFO = RecordType("HINFO") + // RecordTypeRP is [insert doc]. + RecordTypeRP = RecordType("RP") + // RecordTypeURI is [insert doc]. + RecordTypeURI = RecordType("URI") + // RecordTypeDS is [insert doc]. + RecordTypeDS = RecordType("DS") + // RecordTypeNAPTR is [insert doc]. + RecordTypeNAPTR = RecordType("NAPTR") + // RecordTypeDNAME is [insert doc]. + RecordTypeDNAME = RecordType("DNAME") +) + +func (enum RecordType) String() string { + if enum == "" { + // return default value if empty + return "unknown" + } + return string(enum) +} + +func (enum RecordType) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *RecordType) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = RecordType(RecordType(tmp).String()) + return nil +} + +type RenewableDomainStatus string + +const ( + // RenewableDomainStatusUnknown is [insert doc]. + RenewableDomainStatusUnknown = RenewableDomainStatus("unknown") + // RenewableDomainStatusRenewable is [insert doc]. + RenewableDomainStatusRenewable = RenewableDomainStatus("renewable") + // RenewableDomainStatusLateReneweable is [insert doc]. + RenewableDomainStatusLateReneweable = RenewableDomainStatus("late_reneweable") + // RenewableDomainStatusNotRenewable is [insert doc]. + RenewableDomainStatusNotRenewable = RenewableDomainStatus("not_renewable") +) + +func (enum RenewableDomainStatus) String() string { + if enum == "" { + // return default value if empty + return "unknown" + } + return string(enum) +} + +func (enum RenewableDomainStatus) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *RenewableDomainStatus) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = RenewableDomainStatus(RenewableDomainStatus(tmp).String()) + return nil +} + +type SSLCertificateStatus string + +const ( + // SSLCertificateStatusUnknown is [insert doc]. + SSLCertificateStatusUnknown = SSLCertificateStatus("unknown") + // SSLCertificateStatusNew is [insert doc]. + SSLCertificateStatusNew = SSLCertificateStatus("new") + // SSLCertificateStatusPending is [insert doc]. + SSLCertificateStatusPending = SSLCertificateStatus("pending") + // SSLCertificateStatusSuccess is [insert doc]. + SSLCertificateStatusSuccess = SSLCertificateStatus("success") + // SSLCertificateStatusError is [insert doc]. + SSLCertificateStatusError = SSLCertificateStatus("error") +) + +func (enum SSLCertificateStatus) String() string { + if enum == "" { + // return default value if empty + return "unknown" + } + return string(enum) +} + +func (enum SSLCertificateStatus) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *SSLCertificateStatus) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = SSLCertificateStatus(SSLCertificateStatus(tmp).String()) + return nil +} + +type TaskStatus string + +const ( + // TaskStatusUnavailable is [insert doc]. + TaskStatusUnavailable = TaskStatus("unavailable") + // TaskStatusNew is [insert doc]. + TaskStatusNew = TaskStatus("new") + // TaskStatusWaitingPayment is [insert doc]. + TaskStatusWaitingPayment = TaskStatus("waiting_payment") + // TaskStatusPending is [insert doc]. + TaskStatusPending = TaskStatus("pending") + // TaskStatusSuccess is [insert doc]. + TaskStatusSuccess = TaskStatus("success") + // TaskStatusError is [insert doc]. + TaskStatusError = TaskStatus("error") +) + +func (enum TaskStatus) String() string { + if enum == "" { + // return default value if empty + return "unavailable" + } + return string(enum) +} + +func (enum TaskStatus) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *TaskStatus) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = TaskStatus(TaskStatus(tmp).String()) + return nil +} + +type TaskType string + +const ( + // TaskTypeUnknown is [insert doc]. + TaskTypeUnknown = TaskType("unknown") + // TaskTypeCreateDomain is [insert doc]. + TaskTypeCreateDomain = TaskType("create_domain") + // TaskTypeCreateExternalDomain is [insert doc]. + TaskTypeCreateExternalDomain = TaskType("create_external_domain") + // TaskTypeRenewDomain is [insert doc]. + TaskTypeRenewDomain = TaskType("renew_domain") + // TaskTypeTransferDomain is [insert doc]. + TaskTypeTransferDomain = TaskType("transfer_domain") + // TaskTypeTradeDomain is [insert doc]. + TaskTypeTradeDomain = TaskType("trade_domain") + // TaskTypeLockDomainTransfer is [insert doc]. + TaskTypeLockDomainTransfer = TaskType("lock_domain_transfer") + // TaskTypeUnlockDomainTransfer is [insert doc]. + TaskTypeUnlockDomainTransfer = TaskType("unlock_domain_transfer") + // TaskTypeEnableDnssec is [insert doc]. + TaskTypeEnableDnssec = TaskType("enable_dnssec") + // TaskTypeDisableDnssec is [insert doc]. + TaskTypeDisableDnssec = TaskType("disable_dnssec") + // TaskTypeUpdateDomain is [insert doc]. + TaskTypeUpdateDomain = TaskType("update_domain") + // TaskTypeUpdateContact is [insert doc]. + TaskTypeUpdateContact = TaskType("update_contact") + // TaskTypeDeleteDomain is [insert doc]. + TaskTypeDeleteDomain = TaskType("delete_domain") + // TaskTypeCancelTask is [insert doc]. + TaskTypeCancelTask = TaskType("cancel_task") + // TaskTypeGenerateSslCertificate is [insert doc]. + TaskTypeGenerateSslCertificate = TaskType("generate_ssl_certificate") + // TaskTypeRenewSslCertificate is [insert doc]. + TaskTypeRenewSslCertificate = TaskType("renew_ssl_certificate") + // TaskTypeSendMessage is [insert doc]. + TaskTypeSendMessage = TaskType("send_message") + // TaskTypeDeleteDomainExpired is [insert doc]. + TaskTypeDeleteDomainExpired = TaskType("delete_domain_expired") + // TaskTypeDeleteExternalDomain is [insert doc]. + TaskTypeDeleteExternalDomain = TaskType("delete_external_domain") + // TaskTypeCreateHost is [insert doc]. + TaskTypeCreateHost = TaskType("create_host") + // TaskTypeUpdateHost is [insert doc]. + TaskTypeUpdateHost = TaskType("update_host") + // TaskTypeDeleteHost is [insert doc]. + TaskTypeDeleteHost = TaskType("delete_host") +) + +func (enum TaskType) String() string { + if enum == "" { + // return default value if empty + return "unknown" + } + return string(enum) +} + +func (enum TaskType) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum)), nil +} + +func (enum *TaskType) UnmarshalJSON(data []byte) error { + tmp := "" + + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + *enum = TaskType(TaskType(tmp).String()) + return nil +} + +type AvailableDomain struct { + Domain string `json:"domain"` + + Available bool `json:"available"` + + Tld *Tld `json:"tld"` +} + +// CheckContactsCompatibilityResponse: check contacts compatibility response +type CheckContactsCompatibilityResponse struct { + Compatible bool `json:"compatible"` + + OwnerCheckResult *CheckContactsCompatibilityResponseContactCheckResult `json:"owner_check_result"` + + AdministrativeCheckResult *CheckContactsCompatibilityResponseContactCheckResult `json:"administrative_check_result"` + + TechnicalCheckResult *CheckContactsCompatibilityResponseContactCheckResult `json:"technical_check_result"` +} + +type CheckContactsCompatibilityResponseContactCheckResult struct { + Compatible bool `json:"compatible"` + + ErrorMessage *string `json:"error_message"` +} + +// ClearDNSZoneRecordsResponse: clear dns zone records response +type ClearDNSZoneRecordsResponse struct { +} + +// Contact: contact +type Contact struct { + ID string `json:"id"` + // LegalForm: + // + // Default value: legal_form_unknown + LegalForm ContactLegalForm `json:"legal_form"` + + Firstname string `json:"firstname"` + + Lastname string `json:"lastname"` + + CompanyName string `json:"company_name"` + + Email string `json:"email"` + + EmailAlt string `json:"email_alt"` + + PhoneNumber string `json:"phone_number"` + + FaxNumber string `json:"fax_number"` + + AddressLine1 string `json:"address_line_1"` + + AddressLine2 string `json:"address_line_2"` + + Zip string `json:"zip"` + + City string `json:"city"` + + Country string `json:"country"` + + VatIdentificationCode string `json:"vat_identification_code"` + + CompanyIdentificationCode string `json:"company_identification_code"` + // Lang: + // + // Default value: unknown_language_code + Lang LanguageCode `json:"lang"` + + Resale bool `json:"resale"` + // Deprecated + Questions *[]*ContactQuestion `json:"questions,omitempty"` + + ExtensionFr *ContactExtensionFR `json:"extension_fr"` + + ExtensionEu *ContactExtensionEU `json:"extension_eu"` + + WhoisOptIn bool `json:"whois_opt_in"` + // EmailStatus: + // + // Default value: email_status_unknown + EmailStatus ContactEmailStatus `json:"email_status"` + + State string `json:"state"` + + ExtensionNl *ContactExtensionNL `json:"extension_nl"` +} + +type ContactExtensionEU struct { + EuropeanCitizenship string `json:"european_citizenship"` +} + +type ContactExtensionFR struct { + // Mode: + // + // Default value: mode_unknown + Mode ContactExtensionFRMode `json:"mode"` + + // Precisely one of AssociationInfo, CodeAuthAfnicInfo, DunsInfo, IndividualInfo, TrademarkInfo must be set. + IndividualInfo *ContactExtensionFRIndividualInfo `json:"individual_info,omitempty"` + + // Precisely one of AssociationInfo, CodeAuthAfnicInfo, DunsInfo, IndividualInfo, TrademarkInfo must be set. + DunsInfo *ContactExtensionFRDunsInfo `json:"duns_info,omitempty"` + + // Precisely one of AssociationInfo, CodeAuthAfnicInfo, DunsInfo, IndividualInfo, TrademarkInfo must be set. + AssociationInfo *ContactExtensionFRAssociationInfo `json:"association_info,omitempty"` + + // Precisely one of AssociationInfo, CodeAuthAfnicInfo, DunsInfo, IndividualInfo, TrademarkInfo must be set. + TrademarkInfo *ContactExtensionFRTrademarkInfo `json:"trademark_info,omitempty"` + + // Precisely one of AssociationInfo, CodeAuthAfnicInfo, DunsInfo, IndividualInfo, TrademarkInfo must be set. + CodeAuthAfnicInfo *ContactExtensionFRCodeAuthAfnicInfo `json:"code_auth_afnic_info,omitempty"` +} + +type ContactExtensionFRAssociationInfo struct { + PublicationJo *time.Time `json:"publication_jo"` + + PublicationJoPage uint32 `json:"publication_jo_page"` +} + +type ContactExtensionFRCodeAuthAfnicInfo struct { + CodeAuthAfnic string `json:"code_auth_afnic"` +} + +type ContactExtensionFRDunsInfo struct { + DunsID string `json:"duns_id"` + + LocalID string `json:"local_id"` +} + +type ContactExtensionFRIndividualInfo struct { + WhoisOptIn bool `json:"whois_opt_in"` +} + +type ContactExtensionFRTrademarkInfo struct { + TrademarkInpi string `json:"trademark_inpi"` +} + +type ContactExtensionNL struct { + // LegalForm: + // + // Default value: legal_form_unknown + LegalForm ContactExtensionNLLegalForm `json:"legal_form"` + + LegalFormRegistrationNumber string `json:"legal_form_registration_number"` +} + +type ContactQuestion struct { + Question string `json:"question"` + + Answer string `json:"answer"` +} + +type ContactRoles struct { + Contact *Contact `json:"contact"` + + Roles map[string]*ContactRolesRoles `json:"roles"` +} + +type ContactRolesRoles struct { + IsOwner bool `json:"is_owner"` + + IsAdministrative bool `json:"is_administrative"` + + IsTechnical bool `json:"is_technical"` +} + +type DNSZone struct { + Domain string `json:"domain"` + + Subdomain string `json:"subdomain"` + + Ns []string `json:"ns"` + + NsDefault []string `json:"ns_default"` + + NsMaster []string `json:"ns_master"` + // Status: + // + // Default value: unknown + Status DNSZoneStatus `json:"status"` + + Message *string `json:"message"` + + UpdatedAt *time.Time `json:"updated_at"` + + ProjectID string `json:"project_id"` +} + +type DNSZoneVersion struct { + ID string `json:"id"` + + CreatedAt *time.Time `json:"created_at"` +} + +type DSRecord struct { + KeyID uint32 `json:"key_id"` + // Algorithm: + // + // Default value: rsamd5 + Algorithm DSRecordAlgorithm `json:"algorithm"` + + // Precisely one of Digest, PublicKey must be set. + Digest *DSRecordDigest `json:"digest,omitempty"` + + // Precisely one of Digest, PublicKey must be set. + PublicKey *DSRecordPublicKey `json:"public_key,omitempty"` +} + +type DSRecordDigest struct { + // Type: + // + // Default value: sha_1 + Type DSRecordDigestType `json:"type"` + + Digest string `json:"digest"` + + PublicKey *DSRecordPublicKey `json:"public_key"` +} + +type DSRecordPublicKey struct { + Key string `json:"key"` +} + +// DeleteDNSZoneResponse: delete dns zone response +type DeleteDNSZoneResponse struct { +} + +// DeleteExternalDomainResponse: delete external domain response +type DeleteExternalDomainResponse struct { +} + +// DeleteSSLCertificateResponse: delete ssl certificate response +type DeleteSSLCertificateResponse struct { +} + +// Domain: domain +type Domain struct { + Domain string `json:"domain"` + + OrganizationID string `json:"organization_id"` + + ProjectID string `json:"project_id"` + // AutoRenewStatus: + // + // Default value: feature_status_unknown + AutoRenewStatus DomainFeatureStatus `json:"auto_renew_status"` + + Dnssec *DomainDNSSEC `json:"dnssec"` + + EppCode []string `json:"epp_code"` + + ExpiredAt *time.Time `json:"expired_at"` + + UpdatedAt *time.Time `json:"updated_at"` + + Registrar string `json:"registrar"` + + IsExternal bool `json:"is_external"` + // Status: + // + // Default value: status_unknown + Status DomainStatus `json:"status"` + + DNSZones []*DNSZone `json:"dns_zones"` + + OwnerContact *Contact `json:"owner_contact"` + + TechnicalContact *Contact `json:"technical_contact"` + + AdministrativeContact *Contact `json:"administrative_contact"` + + // Precisely one of ExternalDomainRegistrationStatus, TransferRegistrationStatus must be set. + ExternalDomainRegistrationStatus *DomainRegistrationStatusExternalDomain `json:"external_domain_registration_status,omitempty"` + + // Precisely one of ExternalDomainRegistrationStatus, TransferRegistrationStatus must be set. + TransferRegistrationStatus *DomainRegistrationStatusTransfer `json:"transfer_registration_status,omitempty"` +} + +type DomainDNSSEC struct { + // Status: + // + // Default value: feature_status_unknown + Status DomainFeatureStatus `json:"status"` + + DsRecords []*DSRecord `json:"ds_records"` +} + +type DomainRegistrationStatusExternalDomain struct { + ValidationToken string `json:"validation_token"` +} + +type DomainRegistrationStatusTransfer struct { + // Status: + // + // Default value: status_unknown + Status DomainRegistrationStatusTransferStatus `json:"status"` + + VoteCurrentOwner bool `json:"vote_current_owner"` + + VoteNewOwner bool `json:"vote_new_owner"` +} + +type DomainSummary struct { + Domain string `json:"domain"` + + ProjectID string `json:"project_id"` + // AutoRenewStatus: + // + // Default value: feature_status_unknown + AutoRenewStatus DomainFeatureStatus `json:"auto_renew_status"` + // DnssecStatus: + // + // Default value: feature_status_unknown + DnssecStatus DomainFeatureStatus `json:"dnssec_status"` + + EppCode []string `json:"epp_code"` + + ExpiredAt *time.Time `json:"expired_at"` + + UpdatedAt *time.Time `json:"updated_at"` + + Registrar string `json:"registrar"` + + IsExternal bool `json:"is_external"` + // Status: + // + // Default value: status_unknown + Status DomainStatus `json:"status"` + + // Precisely one of ExternalDomainRegistrationStatus, TransferRegistrationStatus must be set. + ExternalDomainRegistrationStatus *DomainRegistrationStatusExternalDomain `json:"external_domain_registration_status,omitempty"` + + // Precisely one of ExternalDomainRegistrationStatus, TransferRegistrationStatus must be set. + TransferRegistrationStatus *DomainRegistrationStatusTransfer `json:"transfer_registration_status,omitempty"` + + OrganizationID string `json:"organization_id"` +} + +// GetDNSZoneTsigKeyResponse: get dns zone tsig key response +type GetDNSZoneTsigKeyResponse struct { + Name string `json:"name"` + + Key string `json:"key"` + + Algorithm string `json:"algorithm"` +} + +// GetDNSZoneVersionDiffResponse: get dns zone version diff response +type GetDNSZoneVersionDiffResponse struct { + Changes []*RecordChange `json:"changes"` +} + +// GetDomainAuthCodeResponse: get domain auth code response +type GetDomainAuthCodeResponse struct { + AuthCode string `json:"auth_code"` +} + +type Host struct { + Domain string `json:"domain"` + + Name string `json:"name"` + + IPs []net.IP `json:"ips"` + // Status: + // + // Default value: unknown_status + Status HostStatus `json:"status"` +} + +type ImportProviderDNSZoneRequestOnlineV1 struct { + Token string `json:"token"` +} + +// ImportProviderDNSZoneResponse: import provider dns zone response +type ImportProviderDNSZoneResponse struct { + Records []*Record `json:"records"` +} + +type ImportRawDNSZoneRequestAXFRSource struct { + NameServer string `json:"name_server"` + + TsigKey *ImportRawDNSZoneRequestTsigKey `json:"tsig_key"` +} + +type ImportRawDNSZoneRequestBindSource struct { + Content string `json:"content"` +} + +type ImportRawDNSZoneRequestTsigKey struct { + Name string `json:"name"` + + Key string `json:"key"` + + Algorithm string `json:"algorithm"` +} + +// ImportRawDNSZoneResponse: import raw dns zone response +type ImportRawDNSZoneResponse struct { + Records []*Record `json:"records"` +} + +// ListContactsResponse: list contacts response +type ListContactsResponse struct { + TotalCount uint32 `json:"total_count"` + + Contacts []*ContactRoles `json:"contacts"` +} + +// ListDNSZoneNameserversResponse: list dns zone nameservers response +type ListDNSZoneNameserversResponse struct { + // Ns: the returned DNS zone nameservers + Ns []*Nameserver `json:"ns"` +} + +// ListDNSZoneRecordsResponse: list dns zone records response +type ListDNSZoneRecordsResponse struct { + // TotalCount: the total number of DNS zone records + TotalCount uint32 `json:"total_count"` + // Records: the paginated returned DNS zone records + Records []*Record `json:"records"` +} + +// ListDNSZoneVersionRecordsResponse: list dns zone version records response +type ListDNSZoneVersionRecordsResponse struct { + // TotalCount: the total number of DNS zones versions records + TotalCount uint32 `json:"total_count"` + + Records []*Record `json:"records"` +} + +// ListDNSZoneVersionsResponse: list dns zone versions response +type ListDNSZoneVersionsResponse struct { + // TotalCount: the total number of DNS zones versions + TotalCount uint32 `json:"total_count"` + + Versions []*DNSZoneVersion `json:"versions"` +} + +// ListDNSZonesResponse: list dns zones response +type ListDNSZonesResponse struct { + // TotalCount: the total number of DNS zones + TotalCount uint32 `json:"total_count"` + // DNSZones: the paginated returned DNS zones + DNSZones []*DNSZone `json:"dns_zones"` +} + +// ListDomainHostsResponse: list domain hosts response +type ListDomainHostsResponse struct { + TotalCount uint32 `json:"total_count"` + + Hosts []*Host `json:"hosts"` +} + +// ListDomainsResponse: list domains response +type ListDomainsResponse struct { + TotalCount uint32 `json:"total_count"` + + Domains []*DomainSummary `json:"domains"` +} + +// ListRenewableDomainsResponse: list renewable domains response +type ListRenewableDomainsResponse struct { + TotalCount uint32 `json:"total_count"` + + Domains []*RenewableDomain `json:"domains"` +} + +// ListSSLCertificatesResponse: list ssl certificates response +type ListSSLCertificatesResponse struct { + TotalCount uint32 `json:"total_count"` + + Certificates []*SSLCertificate `json:"certificates"` +} + +// ListTasksResponse: list tasks response +type ListTasksResponse struct { + TotalCount uint32 `json:"total_count"` + + Tasks []*Task `json:"tasks"` +} + +type Nameserver struct { + Name string `json:"name"` + + IP []string `json:"ip"` +} + +type NewContact struct { + // LegalForm: + // + // Default value: legal_form_unknown + LegalForm ContactLegalForm `json:"legal_form"` + + Firstname string `json:"firstname"` + + Lastname string `json:"lastname"` + + CompanyName *string `json:"company_name"` + + Email string `json:"email"` + + EmailAlt *string `json:"email_alt"` + + PhoneNumber string `json:"phone_number"` + + FaxNumber *string `json:"fax_number"` + + AddressLine1 string `json:"address_line_1"` + + AddressLine2 *string `json:"address_line_2"` + + Zip string `json:"zip"` + + City string `json:"city"` + + Country string `json:"country"` + + VatIdentificationCode *string `json:"vat_identification_code"` + + CompanyIdentificationCode *string `json:"company_identification_code"` + // Lang: + // + // Default value: unknown_language_code + Lang LanguageCode `json:"lang"` + + Resale bool `json:"resale"` + // Deprecated + Questions *[]*ContactQuestion `json:"questions,omitempty"` + + ExtensionFr *ContactExtensionFR `json:"extension_fr"` + + ExtensionEu *ContactExtensionEU `json:"extension_eu"` + + WhoisOptIn bool `json:"whois_opt_in"` + + State *string `json:"state"` + + ExtensionNl *ContactExtensionNL `json:"extension_nl"` +} + +type OrderResponse struct { + Domains []string `json:"domains"` + + OrganizationID string `json:"organization_id"` + + ProjectID string `json:"project_id"` + + TaskID string `json:"task_id"` + + CreatedAt *time.Time `json:"created_at"` +} + +type Record struct { + Data string `json:"data"` + + Name string `json:"name"` + + Priority uint32 `json:"priority"` + + TTL uint32 `json:"ttl"` + // Type: + // + // Default value: unknown + Type RecordType `json:"type"` + + Comment *string `json:"comment"` + + // Precisely one of GeoIPConfig, HTTPServiceConfig, ViewConfig, WeightedConfig must be set. + GeoIPConfig *RecordGeoIPConfig `json:"geo_ip_config,omitempty"` + + // Precisely one of GeoIPConfig, HTTPServiceConfig, ViewConfig, WeightedConfig must be set. + HTTPServiceConfig *RecordHTTPServiceConfig `json:"http_service_config,omitempty"` + + // Precisely one of GeoIPConfig, HTTPServiceConfig, ViewConfig, WeightedConfig must be set. + WeightedConfig *RecordWeightedConfig `json:"weighted_config,omitempty"` + + // Precisely one of GeoIPConfig, HTTPServiceConfig, ViewConfig, WeightedConfig must be set. + ViewConfig *RecordViewConfig `json:"view_config,omitempty"` + + ID string `json:"id"` +} + +type RecordChange struct { + + // Precisely one of Add, Clear, Delete, Set must be set. + Add *RecordChangeAdd `json:"add,omitempty"` + + // Precisely one of Add, Clear, Delete, Set must be set. + Set *RecordChangeSet `json:"set,omitempty"` + + // Precisely one of Add, Clear, Delete, Set must be set. + Delete *RecordChangeDelete `json:"delete,omitempty"` + + // Precisely one of Add, Clear, Delete, Set must be set. + Clear *RecordChangeClear `json:"clear,omitempty"` +} + +type RecordChangeAdd struct { + Records []*Record `json:"records"` +} + +type RecordChangeClear struct { +} + +type RecordChangeDelete struct { + + // Precisely one of ID, IDFields must be set. + ID *string `json:"id,omitempty"` + + // Precisely one of ID, IDFields must be set. + IDFields *RecordIdentifier `json:"id_fields,omitempty"` +} + +type RecordChangeSet struct { + + // Precisely one of ID, IDFields must be set. + ID *string `json:"id,omitempty"` + + // Precisely one of ID, IDFields must be set. + IDFields *RecordIdentifier `json:"id_fields,omitempty"` + + Records []*Record `json:"records"` +} + +type RecordGeoIPConfig struct { + Matches []*RecordGeoIPConfigMatch `json:"matches"` + + Default string `json:"default"` +} + +type RecordGeoIPConfigMatch struct { + Countries []string `json:"countries"` + + Continents []string `json:"continents"` + + Data string `json:"data"` +} + +type RecordHTTPServiceConfig struct { + IPs []net.IP `json:"ips"` + + MustContain *string `json:"must_contain"` + + URL string `json:"url"` + + UserAgent *string `json:"user_agent"` + // Strategy: + // + // Default value: random + Strategy RecordHTTPServiceConfigStrategy `json:"strategy"` +} + +type RecordIdentifier struct { + Name string `json:"name"` + // Type: + // + // Default value: unknown + Type RecordType `json:"type"` + + Data *string `json:"data"` + + TTL *uint32 `json:"ttl"` +} + +type RecordViewConfig struct { + Views []*RecordViewConfigView `json:"views"` +} + +type RecordViewConfigView struct { + Subnet string `json:"subnet"` + + Data string `json:"data"` +} + +type RecordWeightedConfig struct { + WeightedIPs []*RecordWeightedConfigWeightedIP `json:"weighted_ips"` +} + +type RecordWeightedConfigWeightedIP struct { + IP net.IP `json:"ip"` + + Weight uint32 `json:"weight"` +} + +// RefreshDNSZoneResponse: refresh dns zone response +type RefreshDNSZoneResponse struct { + // DNSZones: the returned DNS zones + DNSZones []*DNSZone `json:"dns_zones"` +} + +type RegisterExternalDomainResponse struct { + Domain string `json:"domain"` + + OrganizationID string `json:"organization_id"` + + ValidationToken string `json:"validation_token"` + + CreatedAt *time.Time `json:"created_at"` + + ProjectID string `json:"project_id"` +} + +type RenewableDomain struct { + Domain string `json:"domain"` + + ProjectID string `json:"project_id"` + + OrganizationID string `json:"organization_id"` + // Status: + // + // Default value: unknown + Status RenewableDomainStatus `json:"status"` + + RenewableDurationInYears *int32 `json:"renewable_duration_in_years"` + + ExpiredAt *time.Time `json:"expired_at"` +} + +// RestoreDNSZoneVersionResponse: restore dns zone version response +type RestoreDNSZoneVersionResponse struct { +} + +type SSLCertificate struct { + DNSZone string `json:"dns_zone"` + + AlternativeDNSZones []string `json:"alternative_dns_zones"` + // Status: + // + // Default value: unknown + Status SSLCertificateStatus `json:"status"` + + PrivateKey string `json:"private_key"` + + CertificateChain string `json:"certificate_chain"` + + CreatedAt *time.Time `json:"created_at"` + + ExpiredAt *time.Time `json:"expired_at"` +} + +// SearchAvailableDomainsResponse: search available domains response +type SearchAvailableDomainsResponse struct { + // AvailableDomains: array of available domains + AvailableDomains []*AvailableDomain `json:"available_domains"` +} + +type Task struct { + ID string `json:"id"` + + ProjectID string `json:"project_id"` + + OrganizationID string `json:"organization_id"` + + Domain *string `json:"domain"` + // Type: + // + // Default value: unknown + Type TaskType `json:"type"` + // Status: + // + // Default value: unavailable + Status TaskStatus `json:"status"` + + StartedAt *time.Time `json:"started_at"` + + UpdatedAt *time.Time `json:"updated_at"` + + Message *string `json:"message"` +} + +type Tld struct { + Name string `json:"name"` + + DnssecSupport bool `json:"dnssec_support"` + + DurationInYearsMin uint32 `json:"duration_in_years_min"` + + DurationInYearsMax uint32 `json:"duration_in_years_max"` + + IdnSupport bool `json:"idn_support"` + + Offers map[string]*TldOffer `json:"offers"` + + Specifications map[string]string `json:"specifications"` +} + +type TldOffer struct { + Action string `json:"action"` + + OperationPath string `json:"operation_path"` + + Price *scw.Money `json:"price"` +} + +type TransferInDomainRequestTransferRequest struct { + Domain string `json:"domain"` + + AuthCode string `json:"auth_code"` +} + +type UpdateContactRequestQuestion struct { + Question *string `json:"question"` + + Answer *string `json:"answer"` +} + +// UpdateDNSZoneNameserversResponse: update dns zone nameservers response +type UpdateDNSZoneNameserversResponse struct { + // Ns: the returned DNS zone nameservers + Ns []*Nameserver `json:"ns"` +} + +// UpdateDNSZoneRecordsResponse: update dns zone records response +type UpdateDNSZoneRecordsResponse struct { + // Records: the returned DNS zone records + Records []*Record `json:"records"` +} + +// Service API + +type ListDNSZonesRequest struct { + // OrganizationID: the organization ID on which to filter the returned DNS zones + OrganizationID *string `json:"-"` + // ProjectID: the project ID on which to filter the returned DNS zones + ProjectID *string `json:"-"` + // OrderBy: the sort order of the returned DNS zones + // + // Default value: domain_asc + OrderBy ListDNSZonesRequestOrderBy `json:"-"` + // Page: the page number for the returned DNS zones + Page *int32 `json:"-"` + // PageSize: the maximum number of DNS zones per page + PageSize *uint32 `json:"-"` + // Domain: the domain on which to filter the returned DNS zones + Domain string `json:"-"` + // DNSZone: the DNS zone on which to filter the returned DNS zones + DNSZone string `json:"-"` +} + +// ListDNSZones: list DNS zones +// +// Returns a list of manageable DNS zones. +// You can filter the DNS zones by domain name. +// +func (s *API) ListDNSZones(req *ListDNSZonesRequest, opts ...scw.RequestOption) (*ListDNSZonesResponse, error) { + var err error + + defaultPageSize, exist := s.client.GetDefaultPageSize() + if (req.PageSize == nil || *req.PageSize == 0) && exist { + req.PageSize = &defaultPageSize + } + + query := url.Values{} + parameter.AddToQuery(query, "organization_id", req.OrganizationID) + parameter.AddToQuery(query, "project_id", req.ProjectID) + parameter.AddToQuery(query, "order_by", req.OrderBy) + parameter.AddToQuery(query, "page", req.Page) + parameter.AddToQuery(query, "page_size", req.PageSize) + parameter.AddToQuery(query, "domain", req.Domain) + parameter.AddToQuery(query, "dns_zone", req.DNSZone) + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/dns-zones", + Query: query, + Headers: http.Header{}, + } + + var resp ListDNSZonesResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type CreateDNSZoneRequest struct { + // Domain: the domain of the DNS zone to create + Domain string `json:"domain"` + // Subdomain: the subdomain of the DNS zone to create + Subdomain string `json:"subdomain"` + // ProjectID: the project ID where the DNS zone will be created + ProjectID string `json:"project_id"` +} + +// CreateDNSZone: create a DNS zone +// +// Create a new DNS zone. +func (s *API) CreateDNSZone(req *CreateDNSZoneRequest, opts ...scw.RequestOption) (*DNSZone, error) { + var err error + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/dns-zones", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp DNSZone + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type UpdateDNSZoneRequest struct { + // DNSZone: the DNS zone to update + DNSZone string `json:"-"` + // NewDNSZone: the new DNS zone + NewDNSZone *string `json:"new_dns_zone"` + // ProjectID: the project ID of the new DNS zone + ProjectID string `json:"project_id"` +} + +// UpdateDNSZone: update a DNS zone +// +// Update the name and/or the organizations for a DNS zone. +func (s *API) UpdateDNSZone(req *UpdateDNSZoneRequest, opts ...scw.RequestOption) (*DNSZone, error) { + var err error + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp DNSZone + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type CloneDNSZoneRequest struct { + // DNSZone: the DNS zone to clone + DNSZone string `json:"-"` + // DestDNSZone: the destinaton DNS zone + DestDNSZone string `json:"dest_dns_zone"` + // Overwrite: whether or not the destination DNS zone will be overwritten + Overwrite bool `json:"overwrite"` + // ProjectID: the project ID of the destination DNS zone + ProjectID *string `json:"project_id"` +} + +// CloneDNSZone: clone a DNS zone +// +// Clone an existed DNS zone with all its records into a new one. +func (s *API) CloneDNSZone(req *CloneDNSZoneRequest, opts ...scw.RequestOption) (*DNSZone, error) { + var err error + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "/clone", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp DNSZone + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type DeleteDNSZoneRequest struct { + // DNSZone: the DNS zone to delete + DNSZone string `json:"-"` + // ProjectID: the project ID of the DNS zone to delete + ProjectID string `json:"-"` +} + +// DeleteDNSZone: delete DNS zone +// +// Delete a DNS zone and all it's records. +func (s *API) DeleteDNSZone(req *DeleteDNSZoneRequest, opts ...scw.RequestOption) (*DeleteDNSZoneResponse, error) { + var err error + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + query := url.Values{} + parameter.AddToQuery(query, "project_id", req.ProjectID) + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "", + Query: query, + Headers: http.Header{}, + } + + var resp DeleteDNSZoneResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type ListDNSZoneRecordsRequest struct { + // DNSZone: the DNS zone on which to filter the returned DNS zone records + DNSZone string `json:"-"` + // ProjectID: the project ID on which to filter the returned DNS zone records + ProjectID *string `json:"-"` + // OrderBy: the sort order of the returned DNS zone records + // + // Default value: name_asc + OrderBy ListDNSZoneRecordsRequestOrderBy `json:"-"` + // Page: the page number for the returned DNS zone records + Page *int32 `json:"-"` + // PageSize: the maximum number of DNS zone records per page + PageSize *uint32 `json:"-"` + // Name: the name on which to filter the returned DNS zone records + Name string `json:"-"` + // Type: the record type on which to filter the returned DNS zone records + // + // Default value: unknown + Type RecordType `json:"-"` + // ID: the record ID on which to filter the returned DNS zone records + ID *string `json:"-"` +} + +// ListDNSZoneRecords: list DNS zone records +// +// Returns a list of DNS records of a DNS zone with default NS. +// You can filter the records by type and name. +// +func (s *API) ListDNSZoneRecords(req *ListDNSZoneRecordsRequest, opts ...scw.RequestOption) (*ListDNSZoneRecordsResponse, error) { + var err error + + defaultPageSize, exist := s.client.GetDefaultPageSize() + if (req.PageSize == nil || *req.PageSize == 0) && exist { + req.PageSize = &defaultPageSize + } + + query := url.Values{} + parameter.AddToQuery(query, "project_id", req.ProjectID) + 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, "type", req.Type) + parameter.AddToQuery(query, "id", req.ID) + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "/records", + Query: query, + Headers: http.Header{}, + } + + var resp ListDNSZoneRecordsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type UpdateDNSZoneRecordsRequest struct { + // DNSZone: the DNS zone where the DNS zone records will be updated + DNSZone string `json:"-"` + // Changes: the changes made to the records + Changes []*RecordChange `json:"changes"` + // ReturnAllRecords: whether or not to return all the records + ReturnAllRecords *bool `json:"return_all_records"` + // DisallowNewZoneCreation: forbid the creation of the target zone if not existing (default action is yes) + DisallowNewZoneCreation bool `json:"disallow_new_zone_creation"` + // Serial: don't use the autoincremenent serial but the provided one (0 to keep the same) + Serial *uint64 `json:"serial"` +} + +// UpdateDNSZoneRecords: update DNS zone records +// +// Only available with default NS.
+// Send a list of actions and records. +// +// Action can be: +// - add: +// - Add new record +// - Can be more specific and add a new IP to an existing A record for example +// - set: +// - Edit a record +// - Can be more specific and edit an IP from an existing A record for example +// - delete: +// - Delete a record +// - Can be more specific and delete an IP from an existing A record for example +// - clear: +// - Delete all records from a DNS zone +// +// All edits will be versioned. +// +func (s *API) UpdateDNSZoneRecords(req *UpdateDNSZoneRecordsRequest, opts ...scw.RequestOption) (*UpdateDNSZoneRecordsResponse, error) { + var err error + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "/records", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp UpdateDNSZoneRecordsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type ListDNSZoneNameserversRequest struct { + // DNSZone: the DNS zone on which to filter the returned DNS zone nameservers + DNSZone string `json:"-"` + // ProjectID: the project ID on which to filter the returned DNS zone nameservers + ProjectID *string `json:"-"` +} + +// ListDNSZoneNameservers: list DNS zone nameservers +// +// Returns a list of Nameservers and their optional glue records for a DNS zone. +func (s *API) ListDNSZoneNameservers(req *ListDNSZoneNameserversRequest, opts ...scw.RequestOption) (*ListDNSZoneNameserversResponse, error) { + var err error + + query := url.Values{} + parameter.AddToQuery(query, "project_id", req.ProjectID) + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "/nameservers", + Query: query, + Headers: http.Header{}, + } + + var resp ListDNSZoneNameserversResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type UpdateDNSZoneNameserversRequest struct { + // DNSZone: the DNS zone where the DNS zone nameservers will be updated + DNSZone string `json:"-"` + // Ns: the new DNS zone nameservers + Ns []*Nameserver `json:"ns"` +} + +// UpdateDNSZoneNameservers: update DNS zone nameservers +// +// Update DNS zone nameservers and set optional glue records. +func (s *API) UpdateDNSZoneNameservers(req *UpdateDNSZoneNameserversRequest, opts ...scw.RequestOption) (*UpdateDNSZoneNameserversResponse, error) { + var err error + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PUT", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "/nameservers", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp UpdateDNSZoneNameserversResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type ClearDNSZoneRecordsRequest struct { + // DNSZone: the DNS zone to clear + DNSZone string `json:"-"` +} + +// ClearDNSZoneRecords: clear DNS zone records +// +// Only available with default NS.
+// Delete all the records from a DNS zone. +// All edits will be versioned. +// +func (s *API) ClearDNSZoneRecords(req *ClearDNSZoneRecordsRequest, opts ...scw.RequestOption) (*ClearDNSZoneRecordsResponse, error) { + var err error + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "/records", + Headers: http.Header{}, + } + + var resp ClearDNSZoneRecordsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type ExportRawDNSZoneRequest struct { + // DNSZone: the DNS zone to export + DNSZone string `json:"-"` + // Format: format for DNS zone + // + // Default value: bind + Format RawFormat `json:"-"` +} + +// ExportRawDNSZone: export raw DNS zone +// +// Get a DNS zone in a given format with default NS. +func (s *API) ExportRawDNSZone(req *ExportRawDNSZoneRequest, opts ...scw.RequestOption) (*scw.File, error) { + var err error + + query := url.Values{} + parameter.AddToQuery(query, "format", req.Format) + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "/raw", + Query: query, + Headers: http.Header{}, + } + + var resp scw.File + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type ImportRawDNSZoneRequest struct { + // DNSZone: the DNS zone to import + DNSZone string `json:"-"` + // Deprecated + Content *string `json:"content,omitempty"` + + ProjectID string `json:"project_id"` + // Deprecated: Format: + // + // Default value: unknown_raw_format + Format *RawFormat `json:"format,omitempty"` + // BindSource: import a bind file format + // Precisely one of AxfrSource, BindSource must be set. + BindSource *ImportRawDNSZoneRequestBindSource `json:"bind_source,omitempty"` + // AxfrSource: import from the nameserver given with tsig use or not + // Precisely one of AxfrSource, BindSource must be set. + AxfrSource *ImportRawDNSZoneRequestAXFRSource `json:"axfr_source,omitempty"` +} + +// ImportRawDNSZone: import raw DNS zone +// +// Import and replace records from a given provider format with default NS. +func (s *API) ImportRawDNSZone(req *ImportRawDNSZoneRequest, opts ...scw.RequestOption) (*ImportRawDNSZoneResponse, error) { + var err error + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "/raw", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp ImportRawDNSZoneResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type ImportProviderDNSZoneRequest struct { + DNSZone string `json:"-"` + + // Precisely one of OnlineV1 must be set. + OnlineV1 *ImportProviderDNSZoneRequestOnlineV1 `json:"online_v1,omitempty"` +} + +// ImportProviderDNSZone: import provider DNS zone +// +// Import and replace records from a given provider format with default NS. +func (s *API) ImportProviderDNSZone(req *ImportProviderDNSZoneRequest, opts ...scw.RequestOption) (*ImportProviderDNSZoneResponse, error) { + var err error + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "/import-provider", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp ImportProviderDNSZoneResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RefreshDNSZoneRequest struct { + // DNSZone: the DNS zone to refresh + DNSZone string `json:"-"` + // RecreateDNSZone: whether or not to recreate the DNS zone + RecreateDNSZone bool `json:"recreate_dns_zone"` + // RecreateSubDNSZone: whether or not to recreate the sub DNS zone + RecreateSubDNSZone bool `json:"recreate_sub_dns_zone"` +} + +// RefreshDNSZone: refresh DNS zone +// +// Refresh SOA DNS zone. +// You can recreate the given DNS zone and its sub DNS zone if needed. +// +func (s *API) RefreshDNSZone(req *RefreshDNSZoneRequest, opts ...scw.RequestOption) (*RefreshDNSZoneResponse, error) { + var err error + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "/refresh", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp RefreshDNSZoneResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type ListDNSZoneVersionsRequest struct { + DNSZone string `json:"-"` + // Page: the page number for the returned DNS zones versions + Page *int32 `json:"-"` + // PageSize: the maximum number of DNS zones versions per page + PageSize *uint32 `json:"-"` +} + +// ListDNSZoneVersions: list DNS zone versions +// +// Get a list of DNS zone versions.
+// The maximum version count is 100.
+// If the count reaches this limit, the oldest version will be deleted after each new modification. +// +func (s *API) ListDNSZoneVersions(req *ListDNSZoneVersionsRequest, opts ...scw.RequestOption) (*ListDNSZoneVersionsResponse, error) { + var err error + + 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) + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "/versions", + Query: query, + Headers: http.Header{}, + } + + var resp ListDNSZoneVersionsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type ListDNSZoneVersionRecordsRequest struct { + DNSZoneVersionID string `json:"-"` + // Page: the page number for the returned DNS zones versions records + Page *int32 `json:"-"` + // PageSize: the maximum number of DNS zones versions records per page + PageSize *uint32 `json:"-"` +} + +// ListDNSZoneVersionRecords: list DNS zone version records +// +// Get a list of records from a previous DNS zone version. +func (s *API) ListDNSZoneVersionRecords(req *ListDNSZoneVersionRecordsRequest, opts ...scw.RequestOption) (*ListDNSZoneVersionRecordsResponse, error) { + var err error + + 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) + + if fmt.Sprint(req.DNSZoneVersionID) == "" { + return nil, errors.New("field DNSZoneVersionID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/dns-zones/version/" + fmt.Sprint(req.DNSZoneVersionID) + "", + Query: query, + Headers: http.Header{}, + } + + var resp ListDNSZoneVersionRecordsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type GetDNSZoneVersionDiffRequest struct { + DNSZoneVersionID string `json:"-"` +} + +// GetDNSZoneVersionDiff: get DNS zone version diff +// +// Get all differences from a previous DNS zone version. +func (s *API) GetDNSZoneVersionDiff(req *GetDNSZoneVersionDiffRequest, opts ...scw.RequestOption) (*GetDNSZoneVersionDiffResponse, error) { + var err error + + if fmt.Sprint(req.DNSZoneVersionID) == "" { + return nil, errors.New("field DNSZoneVersionID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/dns-zones/version/" + fmt.Sprint(req.DNSZoneVersionID) + "/diff", + Headers: http.Header{}, + } + + var resp GetDNSZoneVersionDiffResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RestoreDNSZoneVersionRequest struct { + DNSZoneVersionID string `json:"-"` +} + +// RestoreDNSZoneVersion: restore DNS zone version +// +// Restore and activate a previous DNS zone version. +func (s *API) RestoreDNSZoneVersion(req *RestoreDNSZoneVersionRequest, opts ...scw.RequestOption) (*RestoreDNSZoneVersionResponse, error) { + var err error + + if fmt.Sprint(req.DNSZoneVersionID) == "" { + return nil, errors.New("field DNSZoneVersionID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/dns-zones/version/" + fmt.Sprint(req.DNSZoneVersionID) + "/restore", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp RestoreDNSZoneVersionResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type GetSSLCertificateRequest struct { + DNSZone string `json:"-"` +} + +// GetSSLCertificate: get the zone TLS certificate if it exists +func (s *API) GetSSLCertificate(req *GetSSLCertificateRequest, opts ...scw.RequestOption) (*SSLCertificate, error) { + var err error + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/ssl-certificates/" + fmt.Sprint(req.DNSZone) + "", + Headers: http.Header{}, + } + + var resp SSLCertificate + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type CreateSSLCertificateRequest struct { + DNSZone string `json:"dns_zone"` + + AlternativeDNSZones []string `json:"alternative_dns_zones"` +} + +// CreateSSLCertificate: create or return the zone TLS certificate +func (s *API) CreateSSLCertificate(req *CreateSSLCertificateRequest, opts ...scw.RequestOption) (*SSLCertificate, error) { + var err error + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/ssl-certificates", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp SSLCertificate + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type ListSSLCertificatesRequest struct { + DNSZone string `json:"-"` + + Page *int32 `json:"-"` + + PageSize *uint32 `json:"-"` + + ProjectID *string `json:"-"` +} + +// ListSSLCertificates: list all user TLS certificates +func (s *API) ListSSLCertificates(req *ListSSLCertificatesRequest, opts ...scw.RequestOption) (*ListSSLCertificatesResponse, error) { + var err error + + defaultPageSize, exist := s.client.GetDefaultPageSize() + if (req.PageSize == nil || *req.PageSize == 0) && exist { + req.PageSize = &defaultPageSize + } + + query := url.Values{} + parameter.AddToQuery(query, "dns_zone", req.DNSZone) + parameter.AddToQuery(query, "page", req.Page) + parameter.AddToQuery(query, "page_size", req.PageSize) + parameter.AddToQuery(query, "project_id", req.ProjectID) + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/ssl-certificates", + Query: query, + Headers: http.Header{}, + } + + var resp ListSSLCertificatesResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type DeleteSSLCertificateRequest struct { + DNSZone string `json:"-"` +} + +// DeleteSSLCertificate: delete an TLS certificate +func (s *API) DeleteSSLCertificate(req *DeleteSSLCertificateRequest, opts ...scw.RequestOption) (*DeleteSSLCertificateResponse, error) { + var err error + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/domain/v2beta1/ssl-certificates/" + fmt.Sprint(req.DNSZone) + "", + Headers: http.Header{}, + } + + var resp DeleteSSLCertificateResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type GetDNSZoneTsigKeyRequest struct { + DNSZone string `json:"-"` +} + +// GetDNSZoneTsigKey: get the DNS zone TSIG Key +// +// Get the DNS zone TSIG Key to allow AXFR request. +func (s *API) GetDNSZoneTsigKey(req *GetDNSZoneTsigKeyRequest, opts ...scw.RequestOption) (*GetDNSZoneTsigKeyResponse, error) { + var err error + + if fmt.Sprint(req.DNSZone) == "" { + return nil, errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "/tsig-key", + Headers: http.Header{}, + } + + var resp GetDNSZoneTsigKeyResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type DeleteDNSZoneTsigKeyRequest struct { + DNSZone string `json:"-"` +} + +// DeleteDNSZoneTsigKey: delete the DNS zone TSIG Key +func (s *API) DeleteDNSZoneTsigKey(req *DeleteDNSZoneTsigKeyRequest, opts ...scw.RequestOption) error { + var err error + + if fmt.Sprint(req.DNSZone) == "" { + return errors.New("field DNSZone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/domain/v2beta1/dns-zones/" + fmt.Sprint(req.DNSZone) + "/tsig-key", + Headers: http.Header{}, + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +// Service RegistrarAPI + +type RegistrarAPIListTasksRequest struct { + Page *int32 `json:"-"` + + PageSize *uint32 `json:"-"` + + Domain string `json:"-"` + + ProjectID *string `json:"-"` + + OrganizationID *string `json:"-"` +} + +// ListTasks: list tasks +// +// List all account tasks. +// You can filter the list by domain name. +// +func (s *RegistrarAPI) ListTasks(req *RegistrarAPIListTasksRequest, opts ...scw.RequestOption) (*ListTasksResponse, error) { + var err error + + 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, "domain", req.Domain) + parameter.AddToQuery(query, "project_id", req.ProjectID) + parameter.AddToQuery(query, "organization_id", req.OrganizationID) + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/tasks", + Query: query, + Headers: http.Header{}, + } + + var resp ListTasksResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIBuyDomainsRequest struct { + Domains []string `json:"domains"` + + DurationInYears uint32 `json:"duration_in_years"` + + ProjectID string `json:"project_id"` + + // Precisely one of OwnerContact, OwnerContactID must be set. + OwnerContactID *string `json:"owner_contact_id,omitempty"` + + // Precisely one of OwnerContact, OwnerContactID must be set. + OwnerContact *NewContact `json:"owner_contact,omitempty"` + + // Precisely one of AdministrativeContact, AdministrativeContactID must be set. + AdministrativeContactID *string `json:"administrative_contact_id,omitempty"` + + // Precisely one of AdministrativeContact, AdministrativeContactID must be set. + AdministrativeContact *NewContact `json:"administrative_contact,omitempty"` + + // Precisely one of TechnicalContact, TechnicalContactID must be set. + TechnicalContactID *string `json:"technical_contact_id,omitempty"` + + // Precisely one of TechnicalContact, TechnicalContactID must be set. + TechnicalContact *NewContact `json:"technical_contact,omitempty"` +} + +// BuyDomains: buy one or more domains +// +// Request the registration of domain names. +// You can provide an already existing domain's contact or a new contact. +// +func (s *RegistrarAPI) BuyDomains(req *RegistrarAPIBuyDomainsRequest, opts ...scw.RequestOption) (*OrderResponse, error) { + var err error + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/buy-domains", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp OrderResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIRenewDomainsRequest struct { + Domains []string `json:"domains"` + + DurationInYears uint32 `json:"duration_in_years"` + + ForceLateRenewal *bool `json:"force_late_renewal"` +} + +// RenewDomains: renew one or more domains +// +// Request the renewal of domain names. +// +func (s *RegistrarAPI) RenewDomains(req *RegistrarAPIRenewDomainsRequest, opts ...scw.RequestOption) (*OrderResponse, error) { + var err error + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/renew-domains", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp OrderResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPITransferInDomainRequest struct { + Domains []*TransferInDomainRequestTransferRequest `json:"domains"` + + ProjectID string `json:"project_id"` + + // Precisely one of OwnerContact, OwnerContactID must be set. + OwnerContactID *string `json:"owner_contact_id,omitempty"` + + // Precisely one of OwnerContact, OwnerContactID must be set. + OwnerContact *NewContact `json:"owner_contact,omitempty"` + + // Precisely one of AdministrativeContact, AdministrativeContactID must be set. + AdministrativeContactID *string `json:"administrative_contact_id,omitempty"` + + // Precisely one of AdministrativeContact, AdministrativeContactID must be set. + AdministrativeContact *NewContact `json:"administrative_contact,omitempty"` + + // Precisely one of TechnicalContact, TechnicalContactID must be set. + TechnicalContactID *string `json:"technical_contact_id,omitempty"` + + // Precisely one of TechnicalContact, TechnicalContactID must be set. + TechnicalContact *NewContact `json:"technical_contact,omitempty"` +} + +// TransferInDomain: transfer a domain +// +// Request the transfer from another registrar domain to Scaleway. +// +func (s *RegistrarAPI) TransferInDomain(req *RegistrarAPITransferInDomainRequest, opts ...scw.RequestOption) (*OrderResponse, error) { + var err error + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/domains/transfer-domains", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp OrderResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPITradeDomainRequest struct { + Domain string `json:"-"` + + ProjectID *string `json:"project_id"` + + // Precisely one of NewOwnerContact, NewOwnerContactID must be set. + NewOwnerContactID *string `json:"new_owner_contact_id,omitempty"` + + // Precisely one of NewOwnerContact, NewOwnerContactID must be set. + NewOwnerContact *NewContact `json:"new_owner_contact,omitempty"` +} + +// TradeDomain: trade a domain contact +// +// Request a trade for the contact owner.
+// If an `organization_id` is given, the change is from the current Scaleway account to another Scaleway account.
+// If no contact is given, the first contact of the other Scaleway account is taken.
+// If the other Scaleway account has no contact. An error occurs. +// +func (s *RegistrarAPI) TradeDomain(req *RegistrarAPITradeDomainRequest, opts ...scw.RequestOption) (*OrderResponse, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "/trade", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp OrderResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIRegisterExternalDomainRequest struct { + Domain string `json:"domain"` + + ProjectID string `json:"project_id"` +} + +// RegisterExternalDomain: register an external domain +// +// Request the registration of an external domain name. +// +func (s *RegistrarAPI) RegisterExternalDomain(req *RegistrarAPIRegisterExternalDomainRequest, opts ...scw.RequestOption) (*RegisterExternalDomainResponse, error) { + var err error + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/external-domains", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp RegisterExternalDomainResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIDeleteExternalDomainRequest struct { + Domain string `json:"-"` +} + +// DeleteExternalDomain: delete an external domain +// +// Delete an external domain name. +// +func (s *RegistrarAPI) DeleteExternalDomain(req *RegistrarAPIDeleteExternalDomainRequest, opts ...scw.RequestOption) (*DeleteExternalDomainResponse, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/domain/v2beta1/external-domains/" + fmt.Sprint(req.Domain) + "", + Headers: http.Header{}, + } + + var resp DeleteExternalDomainResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPICheckContactsCompatibilityRequest struct { + + // Precisely one of Domain, Tld must be set. + Domain *string `json:"domain,omitempty"` + + // Precisely one of Domain, Tld must be set. + Tld *string `json:"tld,omitempty"` + + // Precisely one of OwnerContact, OwnerContactID must be set. + OwnerContactID *string `json:"owner_contact_id,omitempty"` + + // Precisely one of OwnerContact, OwnerContactID must be set. + OwnerContact *NewContact `json:"owner_contact,omitempty"` + + // Precisely one of AdministrativeContact, AdministrativeContactID must be set. + AdministrativeContactID *string `json:"administrative_contact_id,omitempty"` + + // Precisely one of AdministrativeContact, AdministrativeContactID must be set. + AdministrativeContact *NewContact `json:"administrative_contact,omitempty"` + + // Precisely one of TechnicalContact, TechnicalContactID must be set. + TechnicalContactID *string `json:"technical_contact_id,omitempty"` + + // Precisely one of TechnicalContact, TechnicalContactID must be set. + TechnicalContact *NewContact `json:"technical_contact,omitempty"` +} + +// CheckContactsCompatibility: check if contacts are compatible against a domain or a tld +// +// Check if contacts are compatible against a domain or a tld. +// If not, it will return the information requiring a correction. +// +func (s *RegistrarAPI) CheckContactsCompatibility(req *RegistrarAPICheckContactsCompatibilityRequest, opts ...scw.RequestOption) (*CheckContactsCompatibilityResponse, error) { + var err error + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/check-contacts-compatibility", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp CheckContactsCompatibilityResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIListContactsRequest struct { + Page *int32 `json:"-"` + + PageSize *uint32 `json:"-"` + + Domain *string `json:"-"` + + ProjectID *string `json:"-"` + + OrganizationID *string `json:"-"` +} + +// ListContacts: list contacts +// +// Return a list of contacts with their domains and roles. +// You can filter the list by domain name. +// +func (s *RegistrarAPI) ListContacts(req *RegistrarAPIListContactsRequest, opts ...scw.RequestOption) (*ListContactsResponse, error) { + var err error + + 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, "domain", req.Domain) + parameter.AddToQuery(query, "project_id", req.ProjectID) + parameter.AddToQuery(query, "organization_id", req.OrganizationID) + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/contacts", + Query: query, + Headers: http.Header{}, + } + + var resp ListContactsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIGetContactRequest struct { + ContactID string `json:"-"` +} + +// GetContact: get a contact +// +// Return a contact details retrieved from the registrar using a given contact ID. +func (s *RegistrarAPI) GetContact(req *RegistrarAPIGetContactRequest, opts ...scw.RequestOption) (*Contact, error) { + var err error + + if fmt.Sprint(req.ContactID) == "" { + return nil, errors.New("field ContactID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/contacts/" + fmt.Sprint(req.ContactID) + "", + Headers: http.Header{}, + } + + var resp Contact + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIUpdateContactRequest struct { + ContactID string `json:"-"` + + Email *string `json:"email"` + + EmailAlt *string `json:"email_alt"` + + PhoneNumber *string `json:"phone_number"` + + FaxNumber *string `json:"fax_number"` + + AddressLine1 *string `json:"address_line_1"` + + AddressLine2 *string `json:"address_line_2"` + + Zip *string `json:"zip"` + + City *string `json:"city"` + + Country *string `json:"country"` + + VatIdentificationCode *string `json:"vat_identification_code"` + + CompanyIdentificationCode *string `json:"company_identification_code"` + // Lang: + // + // Default value: unknown_language_code + Lang LanguageCode `json:"lang"` + + Resale *bool `json:"resale"` + // Deprecated + Questions *[]*UpdateContactRequestQuestion `json:"questions,omitempty"` + + ExtensionFr *ContactExtensionFR `json:"extension_fr"` + + ExtensionEu *ContactExtensionEU `json:"extension_eu"` + + WhoisOptIn *bool `json:"whois_opt_in"` + + State *string `json:"state"` + + ExtensionNl *ContactExtensionNL `json:"extension_nl"` +} + +// UpdateContact: update contact +// +// You can edit the contact coordinates. +func (s *RegistrarAPI) UpdateContact(req *RegistrarAPIUpdateContactRequest, opts ...scw.RequestOption) (*Contact, error) { + var err error + + if fmt.Sprint(req.ContactID) == "" { + return nil, errors.New("field ContactID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/domain/v2beta1/contacts/" + fmt.Sprint(req.ContactID) + "", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Contact + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIListDomainsRequest struct { + Page *int32 `json:"-"` + + PageSize *uint32 `json:"-"` + // OrderBy: + // + // Default value: domain_asc + OrderBy ListDomainsRequestOrderBy `json:"-"` + + Registrar *string `json:"-"` + // Status: + // + // Default value: status_unknown + Status DomainStatus `json:"-"` + + ProjectID *string `json:"-"` + + OrganizationID *string `json:"-"` + + IsExternal *bool `json:"-"` + + Domain *string `json:"-"` +} + +// ListDomains: list domains +// +// Returns a list of domains owned by the user. +func (s *RegistrarAPI) ListDomains(req *RegistrarAPIListDomainsRequest, opts ...scw.RequestOption) (*ListDomainsResponse, error) { + var err error + + 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, "registrar", req.Registrar) + parameter.AddToQuery(query, "status", req.Status) + parameter.AddToQuery(query, "project_id", req.ProjectID) + parameter.AddToQuery(query, "organization_id", req.OrganizationID) + parameter.AddToQuery(query, "is_external", req.IsExternal) + parameter.AddToQuery(query, "domain", req.Domain) + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/domains", + Query: query, + Headers: http.Header{}, + } + + var resp ListDomainsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIListRenewableDomainsRequest struct { + Page *int32 `json:"-"` + + PageSize *uint32 `json:"-"` + // OrderBy: + // + // Default value: domain_asc + OrderBy ListRenewableDomainsRequestOrderBy `json:"-"` + + ProjectID *string `json:"-"` + + OrganizationID *string `json:"-"` +} + +// ListRenewableDomains: list scaleway domains that can or not be renewed +// +// Returns a list of domains owned by the user with a renew status and if renewable, the maximum renew duration in years. +func (s *RegistrarAPI) ListRenewableDomains(req *RegistrarAPIListRenewableDomainsRequest, opts ...scw.RequestOption) (*ListRenewableDomainsResponse, error) { + var err error + + 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) + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/renewable-domains", + Query: query, + Headers: http.Header{}, + } + + var resp ListRenewableDomainsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIGetDomainRequest struct { + Domain string `json:"-"` +} + +// GetDomain: get domain +// +// Returns a the domain with more informations. +func (s *RegistrarAPI) GetDomain(req *RegistrarAPIGetDomainRequest, opts ...scw.RequestOption) (*Domain, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "", + Headers: http.Header{}, + } + + var resp Domain + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIUpdateDomainRequest struct { + Domain string `json:"-"` + + // Precisely one of TechnicalContact, TechnicalContactID must be set. + TechnicalContactID *string `json:"technical_contact_id,omitempty"` + + // Precisely one of TechnicalContact, TechnicalContactID must be set. + TechnicalContact *NewContact `json:"technical_contact,omitempty"` + + // Precisely one of OwnerContact, OwnerContactID must be set. + OwnerContactID *string `json:"owner_contact_id,omitempty"` + + // Precisely one of OwnerContact, OwnerContactID must be set. + OwnerContact *NewContact `json:"owner_contact,omitempty"` + + // Precisely one of AdministrativeContact, AdministrativeContactID must be set. + AdministrativeContactID *string `json:"administrative_contact_id,omitempty"` + + // Precisely one of AdministrativeContact, AdministrativeContactID must be set. + AdministrativeContact *NewContact `json:"administrative_contact,omitempty"` +} + +// UpdateDomain: update a domain +// +// Update the domain contacts or create a new one.
+// If you add the same contact for multiple roles. Only one ID will be created and used for all of them. +// +func (s *RegistrarAPI) UpdateDomain(req *RegistrarAPIUpdateDomainRequest, opts ...scw.RequestOption) (*Domain, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Domain + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPILockDomainTransferRequest struct { + Domain string `json:"-"` +} + +// LockDomainTransfer: lock domain transfer +// +// Lock domain transfer. A locked domain transfer can't be transferred and the auth code can't be requested. +// +func (s *RegistrarAPI) LockDomainTransfer(req *RegistrarAPILockDomainTransferRequest, opts ...scw.RequestOption) (*Domain, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "/lock-transfer", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Domain + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIUnlockDomainTransferRequest struct { + Domain string `json:"-"` +} + +// UnlockDomainTransfer: unlock domain transfer +// +// Unlock domain transfer. An unlocked domain can be transferred and the auth code can be requested for this. +// +func (s *RegistrarAPI) UnlockDomainTransfer(req *RegistrarAPIUnlockDomainTransferRequest, opts ...scw.RequestOption) (*Domain, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "/unlock-transfer", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Domain + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIEnableDomainAutoRenewRequest struct { + Domain string `json:"-"` +} + +// EnableDomainAutoRenew: enable domain auto renew +func (s *RegistrarAPI) EnableDomainAutoRenew(req *RegistrarAPIEnableDomainAutoRenewRequest, opts ...scw.RequestOption) (*Domain, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "/enable-auto-renew", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Domain + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIDisableDomainAutoRenewRequest struct { + Domain string `json:"-"` +} + +// DisableDomainAutoRenew: disable domain auto renew +func (s *RegistrarAPI) DisableDomainAutoRenew(req *RegistrarAPIDisableDomainAutoRenewRequest, opts ...scw.RequestOption) (*Domain, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "/disable-auto-renew", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Domain + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIGetDomainAuthCodeRequest struct { + Domain string `json:"-"` +} + +// GetDomainAuthCode: return domain auth code +// +// If possible, return the auth code for an unlocked domain transfer, or an error if the domain is locked. +// Some TLD may have a different procedure to retrieve the auth code, in that case, the information is given in the message field. +// +func (s *RegistrarAPI) GetDomainAuthCode(req *RegistrarAPIGetDomainAuthCodeRequest, opts ...scw.RequestOption) (*GetDomainAuthCodeResponse, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "/auth-code", + Headers: http.Header{}, + } + + var resp GetDomainAuthCodeResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIEnableDomainDNSSECRequest struct { + Domain string `json:"-"` + + DsRecord *DSRecord `json:"ds_record"` +} + +// EnableDomainDNSSEC: update domain DNSSEC +// +// If your domain has the default Scaleway NS and uses another registrar, you have to update the DS record manually. +// For the algorithm, here are the code numbers for each type: +// - 1: RSAMD5 +// - 2: DIFFIE_HELLMAN +// - 3: DSA_SHA1 +// - 5: RSA_SHA1 +// - 6: DSA_NSEC3_SHA1 +// - 7: RSASHA1_NSEC3_SHA1 +// - 8: RSASHA256 +// - 10: RSASHA512 +// - 12: ECC_GOST +// - 13: ECDSAP256SHA256 +// - 14: ECDSAP384SHA384 +// +// And for the digest type: +// - 1: SHA_1 +// - 2: SHA_256 +// - 3: GOST_R_34_11_94 +// - 4: SHA_384 +// +func (s *RegistrarAPI) EnableDomainDNSSEC(req *RegistrarAPIEnableDomainDNSSECRequest, opts ...scw.RequestOption) (*Domain, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "/enable-dnssec", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Domain + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIDisableDomainDNSSECRequest struct { + Domain string `json:"-"` +} + +// DisableDomainDNSSEC: disable domain DNSSEC +func (s *RegistrarAPI) DisableDomainDNSSEC(req *RegistrarAPIDisableDomainDNSSECRequest, opts ...scw.RequestOption) (*Domain, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "/disable-dnssec", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Domain + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPISearchAvailableDomainsRequest struct { + // Domains: a list of domain to search, TLD is optional + Domains []string `json:"-"` + // Tlds: array of tlds to search on + Tlds []string `json:"-"` +} + +// SearchAvailableDomains: search available domains +// +// Search a domain (or at maximum, 10 domains). +// +// If the TLD list is empty or not set the search returns the results from the most popular TLDs. +// +func (s *RegistrarAPI) SearchAvailableDomains(req *RegistrarAPISearchAvailableDomainsRequest, opts ...scw.RequestOption) (*SearchAvailableDomainsResponse, error) { + var err error + + query := url.Values{} + parameter.AddToQuery(query, "domains", req.Domains) + parameter.AddToQuery(query, "tlds", req.Tlds) + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/search-domains", + Query: query, + Headers: http.Header{}, + } + + var resp SearchAvailableDomainsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPICreateDomainHostRequest struct { + Domain string `json:"-"` + + Name string `json:"name"` + + IPs []net.IP `json:"ips"` +} + +// CreateDomainHost: create domain hostname with glue IPs +func (s *RegistrarAPI) CreateDomainHost(req *RegistrarAPICreateDomainHostRequest, opts ...scw.RequestOption) (*Host, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "/hosts", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Host + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIListDomainHostsRequest struct { + Domain string `json:"-"` + + Page *int32 `json:"-"` + + PageSize *uint32 `json:"-"` +} + +// ListDomainHosts: list domain hostnames with they glue IPs +func (s *RegistrarAPI) ListDomainHosts(req *RegistrarAPIListDomainHostsRequest, opts ...scw.RequestOption) (*ListDomainHostsResponse, error) { + var err error + + 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) + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "/hosts", + Query: query, + Headers: http.Header{}, + } + + var resp ListDomainHostsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIUpdateDomainHostRequest struct { + Domain string `json:"-"` + + Name string `json:"-"` + + IPs *[]string `json:"ips"` +} + +// UpdateDomainHost: update domain hostname with glue IPs +func (s *RegistrarAPI) UpdateDomainHost(req *RegistrarAPIUpdateDomainHostRequest, opts ...scw.RequestOption) (*Host, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + if fmt.Sprint(req.Name) == "" { + return nil, errors.New("field Name cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "/hosts/" + fmt.Sprint(req.Name) + "", + Headers: http.Header{}, + } + + err = scwReq.SetBody(req) + if err != nil { + return nil, err + } + + var resp Host + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type RegistrarAPIDeleteDomainHostRequest struct { + Domain string `json:"-"` + + Name string `json:"-"` +} + +// DeleteDomainHost: delete domain hostname +func (s *RegistrarAPI) DeleteDomainHost(req *RegistrarAPIDeleteDomainHostRequest, opts ...scw.RequestOption) (*Host, error) { + var err error + + if fmt.Sprint(req.Domain) == "" { + return nil, errors.New("field Domain cannot be empty in request") + } + + if fmt.Sprint(req.Name) == "" { + return nil, errors.New("field Name cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/domain/v2beta1/domains/" + fmt.Sprint(req.Domain) + "/hosts/" + fmt.Sprint(req.Name) + "", + Headers: http.Header{}, + } + + var resp Host + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListDNSZonesResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListDNSZonesResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListDNSZonesResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.DNSZones = append(r.DNSZones, results.DNSZones...) + r.TotalCount += uint32(len(results.DNSZones)) + return uint32(len(results.DNSZones)), nil +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListDNSZoneRecordsResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListDNSZoneRecordsResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListDNSZoneRecordsResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.Records = append(r.Records, results.Records...) + r.TotalCount += uint32(len(results.Records)) + return uint32(len(results.Records)), nil +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListDNSZoneVersionsResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListDNSZoneVersionsResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListDNSZoneVersionsResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.Versions = append(r.Versions, results.Versions...) + r.TotalCount += uint32(len(results.Versions)) + return uint32(len(results.Versions)), nil +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListDNSZoneVersionRecordsResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListDNSZoneVersionRecordsResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListDNSZoneVersionRecordsResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.Records = append(r.Records, results.Records...) + r.TotalCount += uint32(len(results.Records)) + return uint32(len(results.Records)), nil +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListSSLCertificatesResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListSSLCertificatesResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListSSLCertificatesResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.Certificates = append(r.Certificates, results.Certificates...) + r.TotalCount += uint32(len(results.Certificates)) + return uint32(len(results.Certificates)), nil +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListTasksResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListTasksResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListTasksResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.Tasks = append(r.Tasks, results.Tasks...) + r.TotalCount += uint32(len(results.Tasks)) + return uint32(len(results.Tasks)), nil +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListContactsResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListContactsResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListContactsResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.Contacts = append(r.Contacts, results.Contacts...) + r.TotalCount += uint32(len(results.Contacts)) + return uint32(len(results.Contacts)), nil +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListDomainsResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListDomainsResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListDomainsResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.Domains = append(r.Domains, results.Domains...) + r.TotalCount += uint32(len(results.Domains)) + return uint32(len(results.Domains)), nil +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListRenewableDomainsResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListRenewableDomainsResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListRenewableDomainsResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.Domains = append(r.Domains, results.Domains...) + r.TotalCount += uint32(len(results.Domains)) + return uint32(len(results.Domains)), nil +} + +// UnsafeGetTotalCount should not be used +// Internal usage only +func (r *ListDomainHostsResponse) UnsafeGetTotalCount() uint32 { + return r.TotalCount +} + +// UnsafeAppend should not be used +// Internal usage only +func (r *ListDomainHostsResponse) UnsafeAppend(res interface{}) (uint32, error) { + results, ok := res.(*ListDomainHostsResponse) + if !ok { + return 0, errors.New("%T type cannot be appended to type %T", res, r) + } + + r.Hosts = append(r.Hosts, results.Hosts...) + r.TotalCount += uint32(len(results.Hosts)) + return uint32(len(results.Hosts)), nil +} diff --git a/vendor/github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1/domain_utils.go b/vendor/github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1/domain_utils.go new file mode 100644 index 0000000000000..62089da8de49e --- /dev/null +++ b/vendor/github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1/domain_utils.go @@ -0,0 +1,81 @@ +package domain + +import ( + "fmt" + "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 ( + defaultRetryInterval = 15 * time.Second + defaultTimeout = 5 * time.Minute +) + +const ( + // ErrCodeNoSuchDNSZone for service response error code + // + // The specified dns zone does not exist. + ErrCodeNoSuchDNSZone = "NoSuchDNSZone" +) + +// WaitForDNSZoneRequest is used by WaitForDNSZone method. +type WaitForDNSZoneRequest struct { + DNSZone string + Timeout *time.Duration + RetryInterval *time.Duration +} + +func (s *API) WaitForDNSZone( + req *WaitForDNSZoneRequest, + opts ...scw.RequestOption, +) (*DNSZone, error) { + + timeout := defaultTimeout + if req.Timeout != nil { + timeout = *req.Timeout + } + retryInterval := defaultRetryInterval + if req.RetryInterval != nil { + retryInterval = *req.RetryInterval + } + + terminalStatus := map[DNSZoneStatus]struct{}{ + DNSZoneStatusActive: {}, + DNSZoneStatusLocked: {}, + DNSZoneStatusError: {}, + } + + dns, err := async.WaitSync(&async.WaitSyncConfig{ + Get: func() (interface{}, bool, error) { + // listing dns zones and take the first one + DNSZones, err := s.ListDNSZones(&ListDNSZonesRequest{ + DNSZone: req.DNSZone, + }, opts...) + + if err != nil { + return nil, false, err + } + + if len(DNSZones.DNSZones) == 0 { + return nil, true, fmt.Errorf(ErrCodeNoSuchDNSZone) + } + + Dns := DNSZones.DNSZones[0] + + _, isTerminal := terminalStatus[Dns.Status] + + return Dns, isTerminal, nil + }, + Timeout: timeout, + IntervalStrategy: async.LinearIntervalStrategy(retryInterval), + }) + + if err != nil { + return nil, errors.Wrap(err, "waiting for DNS failed") + } + + return dns.(*DNSZone), nil +} diff --git a/vendor/github.com/scaleway/scaleway-sdk-go/api/vpc/v1/vpc_sdk.go b/vendor/github.com/scaleway/scaleway-sdk-go/api/vpc/v1/vpc_sdk.go new file mode 100644 index 0000000000000..903d65d1b51ba --- /dev/null +++ b/vendor/github.com/scaleway/scaleway-sdk-go/api/vpc/v1/vpc_sdk.go @@ -0,0 +1,392 @@ +// 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 v1 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 +) + +// API: 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, + } +} + +type ListPrivateNetworksRequestOrderBy string + +const ( + // ListPrivateNetworksRequestOrderByCreatedAtAsc is [insert doc]. + ListPrivateNetworksRequestOrderByCreatedAtAsc = ListPrivateNetworksRequestOrderBy("created_at_asc") + // ListPrivateNetworksRequestOrderByCreatedAtDesc is [insert doc]. + ListPrivateNetworksRequestOrderByCreatedAtDesc = ListPrivateNetworksRequestOrderBy("created_at_desc") + // ListPrivateNetworksRequestOrderByNameAsc is [insert doc]. + ListPrivateNetworksRequestOrderByNameAsc = ListPrivateNetworksRequestOrderBy("name_asc") + // ListPrivateNetworksRequestOrderByNameDesc is [insert doc]. + 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 ListPrivateNetworksResponse struct { + PrivateNetworks []*PrivateNetwork `json:"private_networks"` + + TotalCount uint32 `json:"total_count"` +} + +// PrivateNetwork: private network +type PrivateNetwork struct { + // ID: the private network ID + ID string `json:"id"` + // Name: the private network name + Name string `json:"name"` + // OrganizationID: the private network organization + OrganizationID string `json:"organization_id"` + // ProjectID: the private network project ID + ProjectID string `json:"project_id"` + // Zone: the zone in which the private network is available + Zone scw.Zone `json:"zone"` + // Tags: the private network tags + Tags []string `json:"tags"` + // CreatedAt: the private network creation date + CreatedAt *time.Time `json:"created_at"` + // UpdatedAt: the last private network modification date + UpdatedAt *time.Time `json:"updated_at"` + // Subnets: private network subnets CIDR + Subnets []scw.IPNet `json:"subnets"` +} + +// Service API + +type ListPrivateNetworksRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // OrderBy: the sort order of the returned private networks + // + // Default value: created_at_asc + OrderBy ListPrivateNetworksRequestOrderBy `json:"-"` + // Page: the page number for the returned private networks + Page *int32 `json:"-"` + // PageSize: the maximum number of private networks per page + PageSize *uint32 `json:"-"` + // Name: filter private networks with names containing this string + Name *string `json:"-"` + // Tags: filter private networks with one or more matching tags + Tags []string `json:"-"` + // OrganizationID: the organization ID on which to filter the returned private networks + OrganizationID *string `json:"-"` + // ProjectID: the project ID on which to filter the returned private networks + ProjectID *string `json:"-"` +} + +// ListPrivateNetworks: list private networks +func (s *API) ListPrivateNetworks(req *ListPrivateNetworksRequest, opts ...scw.RequestOption) (*ListPrivateNetworksResponse, 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, "name", req.Name) + parameter.AddToQuery(query, "tags", req.Tags) + parameter.AddToQuery(query, "organization_id", req.OrganizationID) + parameter.AddToQuery(query, "project_id", req.ProjectID) + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc/v1/zones/" + fmt.Sprint(req.Zone) + "/private-networks", + Query: query, + Headers: http.Header{}, + } + + var resp ListPrivateNetworksResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type CreatePrivateNetworkRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // Name: the name of the private network + Name string `json:"name"` + // ProjectID: the project ID of the private network + ProjectID string `json:"project_id"` + // Tags: the private networks tags + Tags []string `json:"tags"` + // Subnets: private network subnets CIDR + Subnets []scw.IPNet `json:"subnets"` +} + +// CreatePrivateNetwork: create a private network +func (s *API) CreatePrivateNetwork(req *CreatePrivateNetworkRequest, opts ...scw.RequestOption) (*PrivateNetwork, error) { + var err error + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + if req.Name == "" { + req.Name = namegenerator.GetRandomName("pn") + } + + if fmt.Sprint(req.Zone) == "" { + return nil, errors.New("field Zone cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "POST", + Path: "/vpc/v1/zones/" + fmt.Sprint(req.Zone) + "/private-networks", + Headers: http.Header{}, + } + + 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 +} + +type GetPrivateNetworkRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // PrivateNetworkID: the private network id + PrivateNetworkID string `json:"-"` +} + +// GetPrivateNetwork: get a private network +func (s *API) GetPrivateNetwork(req *GetPrivateNetworkRequest, opts ...scw.RequestOption) (*PrivateNetwork, 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.PrivateNetworkID) == "" { + return nil, errors.New("field PrivateNetworkID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "GET", + Path: "/vpc/v1/zones/" + fmt.Sprint(req.Zone) + "/private-networks/" + fmt.Sprint(req.PrivateNetworkID) + "", + Headers: http.Header{}, + } + + var resp PrivateNetwork + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type UpdatePrivateNetworkRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // PrivateNetworkID: the private network ID + PrivateNetworkID string `json:"-"` + // Name: the name of the private network + Name *string `json:"name"` + // Tags: the private networks tags + Tags *[]string `json:"tags"` + // Subnets: private network subnets CIDR + Subnets *[]string `json:"subnets"` +} + +// UpdatePrivateNetwork: update private network +func (s *API) UpdatePrivateNetwork(req *UpdatePrivateNetworkRequest, opts ...scw.RequestOption) (*PrivateNetwork, 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.PrivateNetworkID) == "" { + return nil, errors.New("field PrivateNetworkID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "PATCH", + Path: "/vpc/v1/zones/" + fmt.Sprint(req.Zone) + "/private-networks/" + fmt.Sprint(req.PrivateNetworkID) + "", + Headers: http.Header{}, + } + + 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 +} + +type DeletePrivateNetworkRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // PrivateNetworkID: the private network ID + PrivateNetworkID string `json:"-"` +} + +// DeletePrivateNetwork: delete a private network +func (s *API) DeletePrivateNetwork(req *DeletePrivateNetworkRequest, 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.PrivateNetworkID) == "" { + return errors.New("field PrivateNetworkID cannot be empty in request") + } + + scwReq := &scw.ScalewayRequest{ + Method: "DELETE", + Path: "/vpc/v1/zones/" + fmt.Sprint(req.Zone) + "/private-networks/" + fmt.Sprint(req.PrivateNetworkID) + "", + Headers: http.Header{}, + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +// 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 +} 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..0973645981d9a --- /dev/null +++ b/vendor/github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1/vpcgw_sdk.go @@ -0,0 +1,2728 @@ +// 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 +) + +// API: vPC Public Gateway 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, + } +} + +type DHCPEntryType string + +const ( + // DHCPEntryTypeUnknown is [insert doc]. + DHCPEntryTypeUnknown = DHCPEntryType("unknown") + // DHCPEntryTypeReservation is [insert doc]. + DHCPEntryTypeReservation = DHCPEntryType("reservation") + // DHCPEntryTypeLease is [insert doc]. + 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 is [insert doc]. + GatewayNetworkStatusUnknown = GatewayNetworkStatus("unknown") + // GatewayNetworkStatusCreated is [insert doc]. + GatewayNetworkStatusCreated = GatewayNetworkStatus("created") + // GatewayNetworkStatusAttaching is [insert doc]. + GatewayNetworkStatusAttaching = GatewayNetworkStatus("attaching") + // GatewayNetworkStatusConfiguring is [insert doc]. + GatewayNetworkStatusConfiguring = GatewayNetworkStatus("configuring") + // GatewayNetworkStatusReady is [insert doc]. + GatewayNetworkStatusReady = GatewayNetworkStatus("ready") + // GatewayNetworkStatusDetaching is [insert doc]. + GatewayNetworkStatusDetaching = GatewayNetworkStatus("detaching") + // GatewayNetworkStatusDeleted is [insert doc]. + 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 is [insert doc]. + GatewayStatusUnknown = GatewayStatus("unknown") + // GatewayStatusStopped is [insert doc]. + GatewayStatusStopped = GatewayStatus("stopped") + // GatewayStatusAllocating is [insert doc]. + GatewayStatusAllocating = GatewayStatus("allocating") + // GatewayStatusConfiguring is [insert doc]. + GatewayStatusConfiguring = GatewayStatus("configuring") + // GatewayStatusRunning is [insert doc]. + GatewayStatusRunning = GatewayStatus("running") + // GatewayStatusStopping is [insert doc]. + GatewayStatusStopping = GatewayStatus("stopping") + // GatewayStatusFailed is [insert doc]. + GatewayStatusFailed = GatewayStatus("failed") + // GatewayStatusDeleting is [insert doc]. + GatewayStatusDeleting = GatewayStatus("deleting") + // GatewayStatusDeleted is [insert doc]. + GatewayStatusDeleted = GatewayStatus("deleted") + // GatewayStatusLocked is [insert doc]. + 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 is [insert doc]. + ListDHCPEntriesRequestOrderByCreatedAtAsc = ListDHCPEntriesRequestOrderBy("created_at_asc") + // ListDHCPEntriesRequestOrderByCreatedAtDesc is [insert doc]. + ListDHCPEntriesRequestOrderByCreatedAtDesc = ListDHCPEntriesRequestOrderBy("created_at_desc") + // ListDHCPEntriesRequestOrderByIPAddressAsc is [insert doc]. + ListDHCPEntriesRequestOrderByIPAddressAsc = ListDHCPEntriesRequestOrderBy("ip_address_asc") + // ListDHCPEntriesRequestOrderByIPAddressDesc is [insert doc]. + ListDHCPEntriesRequestOrderByIPAddressDesc = ListDHCPEntriesRequestOrderBy("ip_address_desc") + // ListDHCPEntriesRequestOrderByHostnameAsc is [insert doc]. + ListDHCPEntriesRequestOrderByHostnameAsc = ListDHCPEntriesRequestOrderBy("hostname_asc") + // ListDHCPEntriesRequestOrderByHostnameDesc is [insert doc]. + 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 is [insert doc]. + ListDHCPsRequestOrderByCreatedAtAsc = ListDHCPsRequestOrderBy("created_at_asc") + // ListDHCPsRequestOrderByCreatedAtDesc is [insert doc]. + ListDHCPsRequestOrderByCreatedAtDesc = ListDHCPsRequestOrderBy("created_at_desc") + // ListDHCPsRequestOrderBySubnetAsc is [insert doc]. + ListDHCPsRequestOrderBySubnetAsc = ListDHCPsRequestOrderBy("subnet_asc") + // ListDHCPsRequestOrderBySubnetDesc is [insert doc]. + 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 is [insert doc]. + ListGatewayNetworksRequestOrderByCreatedAtAsc = ListGatewayNetworksRequestOrderBy("created_at_asc") + // ListGatewayNetworksRequestOrderByCreatedAtDesc is [insert doc]. + ListGatewayNetworksRequestOrderByCreatedAtDesc = ListGatewayNetworksRequestOrderBy("created_at_desc") + // ListGatewayNetworksRequestOrderByStatusAsc is [insert doc]. + ListGatewayNetworksRequestOrderByStatusAsc = ListGatewayNetworksRequestOrderBy("status_asc") + // ListGatewayNetworksRequestOrderByStatusDesc is [insert doc]. + 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 is [insert doc]. + ListGatewaysRequestOrderByCreatedAtAsc = ListGatewaysRequestOrderBy("created_at_asc") + // ListGatewaysRequestOrderByCreatedAtDesc is [insert doc]. + ListGatewaysRequestOrderByCreatedAtDesc = ListGatewaysRequestOrderBy("created_at_desc") + // ListGatewaysRequestOrderByNameAsc is [insert doc]. + ListGatewaysRequestOrderByNameAsc = ListGatewaysRequestOrderBy("name_asc") + // ListGatewaysRequestOrderByNameDesc is [insert doc]. + ListGatewaysRequestOrderByNameDesc = ListGatewaysRequestOrderBy("name_desc") + // ListGatewaysRequestOrderByTypeAsc is [insert doc]. + ListGatewaysRequestOrderByTypeAsc = ListGatewaysRequestOrderBy("type_asc") + // ListGatewaysRequestOrderByTypeDesc is [insert doc]. + ListGatewaysRequestOrderByTypeDesc = ListGatewaysRequestOrderBy("type_desc") + // ListGatewaysRequestOrderByStatusAsc is [insert doc]. + ListGatewaysRequestOrderByStatusAsc = ListGatewaysRequestOrderBy("status_asc") + // ListGatewaysRequestOrderByStatusDesc is [insert doc]. + 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 is [insert doc]. + ListIPsRequestOrderByCreatedAtAsc = ListIPsRequestOrderBy("created_at_asc") + // ListIPsRequestOrderByCreatedAtDesc is [insert doc]. + ListIPsRequestOrderByCreatedAtDesc = ListIPsRequestOrderBy("created_at_desc") + // ListIPsRequestOrderByIPAsc is [insert doc]. + ListIPsRequestOrderByIPAsc = ListIPsRequestOrderBy("ip_asc") + // ListIPsRequestOrderByIPDesc is [insert doc]. + ListIPsRequestOrderByIPDesc = ListIPsRequestOrderBy("ip_desc") + // ListIPsRequestOrderByReverseAsc is [insert doc]. + ListIPsRequestOrderByReverseAsc = ListIPsRequestOrderBy("reverse_asc") + // ListIPsRequestOrderByReverseDesc is [insert doc]. + 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 is [insert doc]. + ListPATRulesRequestOrderByCreatedAtAsc = ListPATRulesRequestOrderBy("created_at_asc") + // ListPATRulesRequestOrderByCreatedAtDesc is [insert doc]. + ListPATRulesRequestOrderByCreatedAtDesc = ListPATRulesRequestOrderBy("created_at_desc") + // ListPATRulesRequestOrderByPublicPortAsc is [insert doc]. + ListPATRulesRequestOrderByPublicPortAsc = ListPATRulesRequestOrderBy("public_port_asc") + // ListPATRulesRequestOrderByPublicPortDesc is [insert doc]. + 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 is [insert doc]. + PATRuleProtocolUnknown = PATRuleProtocol("unknown") + // PATRuleProtocolBoth is [insert doc]. + PATRuleProtocolBoth = PATRuleProtocol("both") + // PATRuleProtocolTCP is [insert doc]. + PATRuleProtocolTCP = PATRuleProtocol("tcp") + // PATRuleProtocolUDP is [insert doc]. + 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: configuration creation date + 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: address of the DHCP server + // + // Address of the DHCP server. This will be the gateway's address in the private network. It must be part of config's subnet. + // + Address net.IP `json:"address"` + // PoolLow: low IP (included) of the dynamic address pool. Must be in the config's subnet + PoolLow net.IP `json:"pool_low"` + // PoolHigh: high IP (included) of the dynamic address pool. Must be in the config's subnet + PoolHigh net.IP `json:"pool_high"` + // EnableDynamic: whether to enable dynamic pooling of IPs + // + // Whether to enable dynamic pooling of IPs. By turning the dynamic pool off, only pre-existing DHCP reservations will be handed out. + // + EnableDynamic bool `json:"enable_dynamic"` + // ValidLifetime: how long, in seconds, DHCP entries will be valid for + ValidLifetime *scw.Duration `json:"valid_lifetime"` + // RenewTimer: after how long a renew will be attempted + // + // After how long, in seconds, 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 + // + // After how long, in seconds, 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: whether the gateway should push a default route to DHCP clients or only hand out IPs + PushDefaultRoute bool `json:"push_default_route"` + // PushDNSServer: whether the gateway should push custom DNS servers to clients + // + // Whether the gateway should push custom DNS servers to clients. This allows for instance hostname -> IP resolution. + // + PushDNSServer bool `json:"push_dns_server"` + // DNSServersOverride: override the DNS server list pushed to DHCP clients, instead of the gateway itself + DNSServersOverride []string `json:"dns_servers_override"` + // DNSSearch: add search paths to the pushed DNS configuration + DNSSearch []string `json:"dns_search"` + // DNSLocalName: tLD given to hostnames in the Private Networks + // + // TLD given to hostnames in the Private Network. 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 this configuration is available in + Zone scw.Zone `json:"zone"` +} + +// DHCPEntry: dhcp entry +type DHCPEntry struct { + // ID: entry ID + ID string `json:"id"` + // CreatedAt: configuration creation date + CreatedAt *time.Time `json:"created_at"` + // UpdatedAt: configuration last modification date + UpdatedAt *time.Time `json:"updated_at"` + // GatewayNetworkID: owning GatewayNetwork + GatewayNetworkID string `json:"gateway_network_id"` + // MacAddress: mAC address of the client machine + MacAddress string `json:"mac_address"` + // IPAddress: assigned IP address + IPAddress net.IP `json:"ip_address"` + // Hostname: hostname of the client machine + Hostname string `json:"hostname"` + // Type: entry type, either static (DHCP reservation) or dynamic (DHCP lease) + // + // Default value: unknown + Type DHCPEntryType `json:"type"` + // Zone: zone this entry is available in + 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 + Type *GatewayType `json:"type"` + // Status: gateway's current status + // + // Default value: unknown + Status GatewayStatus `json:"status"` + // Name: name of the gateway + Name string `json:"name"` + // Tags: tags of the gateway + Tags []string `json:"tags"` + // IP: public IP of the gateway + IP *IP `json:"ip"` + // GatewayNetworks: gatewayNetworks attached to the gateway + GatewayNetworks []*GatewayNetwork `json:"gateway_networks"` + // UpstreamDNSServers: 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: 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: whether SMTP traffic is allowed to pass through the gateway + SMTPEnabled bool `json:"smtp_enabled"` + // Zone: zone the gateway is available in + Zone scw.Zone `json:"zone"` +} + +// GatewayNetwork: gateway network +type GatewayNetwork struct { + // ID: ID of the 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 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 network (if the gateway is up and running) + MacAddress *string `json:"mac_address"` + // EnableMasquerade: whether the gateway masquerades traffic for this network + EnableMasquerade bool `json:"enable_masquerade"` + // Status: current status of the gateway network connection + // + // Default value: unknown + Status GatewayNetworkStatus `json:"status"` + // DHCP: DHCP configuration for the connected private network + DHCP *DHCP `json:"dhcp"` + // EnableDHCP: 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"` + // Zone: zone the connection lives in + Zone scw.Zone `json:"zone"` +} + +// GatewayType: gateway type +type GatewayType struct { + // Name: type name + Name string `json:"name"` + // Bandwidth: bandwidth, in bps, the gateway has + // + // Bandwidth, in bps, the gateway has. 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 type is available in + Zone scw.Zone `json:"zone"` +} + +// IP: ip +type IP struct { + // ID: IP ID + ID string `json:"id"` + // OrganizationID: owning organization + OrganizationID string `json:"organization_id"` + // ProjectID: owning project + ProjectID string `json:"project_id"` + // CreatedAt: configuration creation date + CreatedAt *time.Time `json:"created_at"` + // UpdatedAt: configuration last modification date + UpdatedAt *time.Time `json:"updated_at"` + // Tags: tags associated with the IP + Tags []string `json:"tags"` + // Address: the IP itself + Address net.IP `json:"address"` + // Reverse: reverse domain name for the IP address + Reverse *string `json:"reverse"` + // GatewayID: gateway associated to the IP + GatewayID *string `json:"gateway_id"` + // Zone: zone this IP is available in + Zone scw.Zone `json:"zone"` +} + +// ListDHCPEntriesResponse: list dhcp entries response +type ListDHCPEntriesResponse struct { + // DHCPEntries: DHCP entries in this page + DHCPEntries []*DHCPEntry `json:"dhcp_entries"` + // TotalCount: total DHCP entries matching the filter + TotalCount uint32 `json:"total_count"` +} + +// ListDHCPsResponse: list dhc ps response +type ListDHCPsResponse struct { + // Dhcps: first page of DHCP configs + Dhcps []*DHCP `json:"dhcps"` + // TotalCount: total DHCP configs matching the filter + TotalCount uint32 `json:"total_count"` +} + +// ListGatewayNetworksResponse: list gateway networks response +type ListGatewayNetworksResponse struct { + // GatewayNetworks: gatewayNetworks in this page + GatewayNetworks []*GatewayNetwork `json:"gateway_networks"` + // TotalCount: total GatewayNetworks count matching the filter + TotalCount uint32 `json:"total_count"` +} + +// ListGatewayTypesResponse: list gateway types response +type ListGatewayTypesResponse struct { + // Types: available types of gateway + Types []*GatewayType `json:"types"` +} + +// ListGatewaysResponse: list gateways response +type ListGatewaysResponse struct { + // Gateways: gateways in this page + Gateways []*Gateway `json:"gateways"` + // TotalCount: total count of gateways matching the filter + TotalCount uint32 `json:"total_count"` +} + +// ListIPsResponse: list i ps response +type ListIPsResponse struct { + // IPs: iPs in this page + IPs []*IP `json:"ips"` + // TotalCount: total IP count matching the filter + TotalCount uint32 `json:"total_count"` +} + +// ListPATRulesResponse: list pat rules response +type ListPATRulesResponse struct { + // PatRules: this page of PAT rules matching the filter + PatRules []*PATRule `json:"pat_rules"` + // TotalCount: total PAT rules matching the filter + TotalCount uint32 `json:"total_count"` +} + +// PATRule: pat rule +type PATRule struct { + // ID: rule ID + ID string `json:"id"` + // GatewayID: gateway the PAT rule applies to + GatewayID string `json:"gateway_id"` + // CreatedAt: rule creation date + CreatedAt *time.Time `json:"created_at"` + // UpdatedAt: rule last modification date + UpdatedAt *time.Time `json:"updated_at"` + // 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 applies to + // + // Default value: unknown + Protocol PATRuleProtocol `json:"protocol"` + // Zone: zone this rule is available in + Zone scw.Zone `json:"zone"` +} + +// SetDHCPEntriesRequestEntry: set dhcp entries request. entry +type SetDHCPEntriesRequestEntry struct { + // MacAddress: mAC address to give a static entry to + // + // 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 machine + IPAddress net.IP `json:"ip_address"` +} + +// SetDHCPEntriesResponse: set dhcp entries response +type SetDHCPEntriesResponse struct { + // DHCPEntries: list of DHCP entries + DHCPEntries []*DHCPEntry `json:"dhcp_entries"` +} + +// SetPATRulesRequestRule: set pat rules request. rule +type SetPATRulesRequestRule struct { + // PublicPort: public port to listen on + // + // 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"` +} + +// SetPATRulesResponse: set pat rules response +type SetPATRulesResponse struct { + // PatRules: list of PAT rules + PatRules []*PATRule `json:"pat_rules"` +} + +// Service API + +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 + 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 gateways including this name + Name *string `json:"-"` + // Tags: filter gateways with these tags + Tags []string `json:"-"` + // Type: filter gateways of this type + Type *string `json:"-"` + // Status: filter gateways in this status (unknown for any) + // + // Default value: unknown + Status GatewayStatus `json:"-"` + // PrivateNetworkID: filter gateways attached to this private network + PrivateNetworkID *string `json:"-"` +} + +// ListGateways: list VPC Public Gateways +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, + Headers: http.Header{}, + } + + var resp ListGatewaysResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +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:"-"` +} + +// GetGateway: get a VPC Public Gateway +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) + "", + Headers: http.Header{}, + } + + var resp Gateway + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type CreateGatewayRequest 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 gateway into + ProjectID string `json:"project_id"` + // Name: name of the gateway + Name string `json:"name"` + // Tags: tags for the gateway + Tags []string `json:"tags"` + // Type: gateway type + Type string `json:"type"` + // UpstreamDNSServers: override the gateway's default recursive DNS servers, if DNS features are enabled + UpstreamDNSServers []string `json:"upstream_dns_servers"` + // IPID: attach an existing IP to the gateway + IPID *string `json:"ip_id"` + // EnableSMTP: allow SMTP traffic to pass through the gateway + EnableSMTP bool `json:"enable_smtp"` + // EnableBastion: enable SSH bastion on the gateway + EnableBastion bool `json:"enable_bastion"` + // BastionPort: port of the SSH bastion + BastionPort *uint32 `json:"bastion_port"` +} + +// CreateGateway: create a VPC Public Gateway +func (s *API) CreateGateway(req *CreateGatewayRequest, opts ...scw.RequestOption) (*Gateway, error) { + var err error + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + if req.Zone == "" { + defaultZone, _ := s.client.GetDefaultZone() + req.Zone = defaultZone + } + + 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", + Headers: http.Header{}, + } + + 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 +} + +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 fo the gateway + Name *string `json:"name"` + // Tags: tags for the gateway + Tags *[]string `json:"tags"` + // UpstreamDNSServers: override the gateway's default recursive DNS servers, if DNS features are enabled + UpstreamDNSServers *[]string `json:"upstream_dns_servers"` + // EnableBastion: enable SSH bastion on the gateway + EnableBastion *bool `json:"enable_bastion"` + // BastionPort: port of the SSH bastion + BastionPort *uint32 `json:"bastion_port"` + // EnableSMTP: allow SMTP traffic to pass through the gateway + EnableSMTP *bool `json:"enable_smtp"` +} + +// UpdateGateway: update a VPC Public Gateway +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) + "", + Headers: http.Header{}, + } + + 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 +} + +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: whether to cleanup attached DHCP configurations + // + // Whether to cleanup attached DHCP configurations (if any, and if not attached to another Gateway Network). + // + CleanupDHCP bool `json:"-"` +} + +// DeleteGateway: delete a VPC Public Gateway +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, + Headers: http.Header{}, + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +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:"-"` +} + +// UpgradeGateway: upgrade a VPC Public Gateway to the latest version +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", + Headers: http.Header{}, + } + + 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 +} + +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 by gateway + GatewayID *string `json:"-"` + // PrivateNetworkID: filter by private network + PrivateNetworkID *string `json:"-"` + // EnableMasquerade: filter by masquerade enablement + EnableMasquerade *bool `json:"-"` + // DHCPID: filter by DHCP configuration + DHCPID *string `json:"-"` + // Status: filter GatewayNetworks by this status (unknown for any) + // + // Default value: unknown + Status GatewayNetworkStatus `json:"-"` +} + +// ListGatewayNetworks: list gateway connections to Private Networks +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, + Headers: http.Header{}, + } + + var resp ListGatewayNetworksResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +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:"-"` +} + +// GetGatewayNetwork: get a gateway connection to a Private Network +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) + "", + Headers: http.Header{}, + } + + var resp GatewayNetwork + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type CreateGatewayNetworkRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // GatewayID: gateway to connect + GatewayID string `json:"gateway_id"` + // PrivateNetworkID: private Network to connect + PrivateNetworkID string `json:"private_network_id"` + // EnableMasquerade: whether to enable masquerade on this network + EnableMasquerade bool `json:"enable_masquerade"` + // DHCPID: existing configuration + // Precisely one of Address, DHCPID must be set. + DHCPID *string `json:"dhcp_id,omitempty"` + // Address: static IP address in CIDR format to to use without DHCP + // Precisely one of Address, DHCPID must be set. + Address *scw.IPNet `json:"address,omitempty"` + // EnableDHCP: whether to enable DHCP on this Private Network + // + // Whether to enable DHCP on this Private Network. Defaults to `true` if either `dhcp_id` or `dhcp` short: are present. If set to `true`, requires that either `dhcp_id` or `dhcp` to be present. + // + EnableDHCP *bool `json:"enable_dhcp"` +} + +// CreateGatewayNetwork: attach a gateway to a Private Network +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", + Headers: http.Header{}, + } + + 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 +} + +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: new masquerade enablement + EnableMasquerade *bool `json:"enable_masquerade"` + // DHCPID: new DHCP configuration + // Precisely one of Address, DHCPID must be set. + DHCPID *string `json:"dhcp_id,omitempty"` + // EnableDHCP: whether to enable DHCP on the connected Private Network + EnableDHCP *bool `json:"enable_dhcp"` + // Address: new static IP address + // Precisely one of Address, DHCPID must be set. + Address *scw.IPNet `json:"address,omitempty"` +} + +// UpdateGatewayNetwork: update a gateway connection to a Private Network +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) + "", + Headers: http.Header{}, + } + + 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 +} + +type DeleteGatewayNetworkRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // GatewayNetworkID: gatewayNetwork to delete + GatewayNetworkID string `json:"-"` + // CleanupDHCP: whether to cleanup the attached DHCP configuration + // + // Whether to cleanup the attached DHCP configuration (if any, and if not attached to another gateway_network). + // + CleanupDHCP bool `json:"-"` +} + +// DeleteGatewayNetwork: detach a gateway from a Private Network +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, + Headers: http.Header{}, + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +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 DHCPs in this organization + OrganizationID *string `json:"-"` + // ProjectID: include only DHCPs in this project + ProjectID *string `json:"-"` + // Address: filter on gateway address + Address *net.IP `json:"-"` + // HasAddress: filter on subnets containing address + HasAddress *net.IP `json:"-"` +} + +// ListDHCPs: list DHCP configurations +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, + Headers: http.Header{}, + } + + var resp ListDHCPsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +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 config to fetch + DHCPID string `json:"-"` +} + +// GetDHCP: get a DHCP configuration +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) + "", + Headers: http.Header{}, + } + + var resp DHCP + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +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: 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"` + // PoolLow: low IP (included) of the dynamic address pool + // + // Low IP (included) of the dynamic address pool. Defaults to the second address of the subnet. + PoolLow *net.IP `json:"pool_low"` + // PoolHigh: high IP (included) of the dynamic address pool + // + // High IP (included) of the dynamic address pool. Defaults to the last address of the subnet. + PoolHigh *net.IP `json:"pool_high"` + // EnableDynamic: whether to enable dynamic pooling of IPs + // + // Whether to enable dynamic pooling of IPs. By turning the dynamic pool off, only pre-existing DHCP reservations will be handed out. Defaults to true. + // + EnableDynamic *bool `json:"enable_dynamic"` + // ValidLifetime: for how long will DHCP entries will be valid + // + // For how long, in seconds, will DHCP entries will be valid. Defaults to 1h (3600s). + ValidLifetime *scw.Duration `json:"valid_lifetime"` + // RenewTimer: after how long a renew will be attempted + // + // After how long, in seconds, a renew will be attempted. Must be 30s lower than `rebind_timer`. Defaults to 50m (3000s). + // + RenewTimer *scw.Duration `json:"renew_timer"` + // RebindTimer: after how long a DHCP client will query for a new lease if previous renews fail + // + // After how long, in seconds, 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"` + // PushDefaultRoute: 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"` + // PushDNSServer: whether the gateway should push custom DNS servers to clients + // + // 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"` + // DNSServersOverride: override the DNS server list pushed to DHCP clients, instead of the gateway itself + DNSServersOverride *[]string `json:"dns_servers_override"` + // DNSSearch: additional DNS search paths + DNSSearch *[]string `json:"dns_search"` + // DNSLocalName: tLD given to hosts in the Private Network + // + // 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"` +} + +// CreateDHCP: create a DHCP configuration +func (s *API) CreateDHCP(req *CreateDHCPRequest, opts ...scw.RequestOption) (*DHCP, error) { + var err error + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + 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) + "/dhcps", + Headers: http.Header{}, + } + + 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 +} + +type UpdateDHCPRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // DHCPID: DHCP config to update + DHCPID string `json:"-"` + // Subnet: subnet for the DHCP server + Subnet *scw.IPNet `json:"subnet"` + // Address: address of the DHCP server. This will be the gateway's address in the private network + Address *net.IP `json:"address"` + // PoolLow: low IP (included) of the dynamic address pool + PoolLow *net.IP `json:"pool_low"` + // PoolHigh: high IP (included) of the dynamic address pool + PoolHigh *net.IP `json:"pool_high"` + // EnableDynamic: whether to enable dynamic pooling of IPs + // + // Whether to enable dynamic pooling of IPs. By turning the dynamic pool off, only pre-existing DHCP reservations will be handed out. Defaults to true. + // + EnableDynamic *bool `json:"enable_dynamic"` + // ValidLifetime: how long, in seconds, DHCP entries will be valid for + ValidLifetime *scw.Duration `json:"valid_lifetime"` + // RenewTimer: after how long a renew will be attempted + // + // After how long, in seconds, 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 + // + // After how long, in seconds, 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: whether the gateway should push a default route to DHCP clients or only hand out IPs + PushDefaultRoute *bool `json:"push_default_route"` + // PushDNSServer: whether the gateway should push custom DNS servers to clients + // + // Whether the gateway should push custom DNS servers to clients. This allows for instance hostname -> IP resolution. + // + PushDNSServer *bool `json:"push_dns_server"` + // DNSServersOverride: override the DNS server list pushed to DHCP clients, instead of the gateway itself + DNSServersOverride *[]string `json:"dns_servers_override"` + // DNSSearch: additional DNS search paths + DNSSearch *[]string `json:"dns_search"` + // DNSLocalName: tLD given to hosts in the Private Network + // + // TLD given to hostnames in the Private Network. Allowed characters are `a-z0-9-.`. + DNSLocalName *string `json:"dns_local_name"` +} + +// UpdateDHCP: update a DHCP configuration +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) + "", + Headers: http.Header{}, + } + + 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 +} + +type DeleteDHCPRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // DHCPID: DHCP config id to delete + DHCPID string `json:"-"` +} + +// DeleteDHCP: delete a DHCP configuration +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) + "", + Headers: http.Header{}, + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +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 entries based on the gateway network they are on + GatewayNetworkID *string `json:"-"` + // MacAddress: filter entries on their MAC address + MacAddress *string `json:"-"` + // IPAddress: filter entries on their IP address + IPAddress *net.IP `json:"-"` + // Hostname: filter entries on their hostname substring + Hostname *string `json:"-"` + // Type: filter entries on their type + // + // Default value: unknown + Type DHCPEntryType `json:"-"` +} + +// ListDHCPEntries: list DHCP entries +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, + Headers: http.Header{}, + } + + var resp ListDHCPEntriesResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +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:"-"` +} + +// GetDHCPEntry: get DHCP entries +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) + "", + Headers: http.Header{}, + } + + var resp DHCPEntry + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +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 machine + IPAddress net.IP `json:"ip_address"` +} + +// CreateDHCPEntry: create a 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", + Headers: http.Header{}, + } + + 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 +} + +type UpdateDHCPEntryRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // DHCPEntryID: DHCP entry ID to update + DHCPEntryID string `json:"-"` + // IPAddress: new IP address to give to the machine + IPAddress *net.IP `json:"ip_address"` +} + +// UpdateDHCPEntry: update a DHCP entry +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) + "", + Headers: http.Header{}, + } + + 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 +} + +type SetDHCPEntriesRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // GatewayNetworkID: 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"` +} + +// SetDHCPEntries: set all DHCP reservations on a Gateway Network +// +// 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. +// +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", + Headers: http.Header{}, + } + + 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 +} + +type DeleteDHCPEntryRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // DHCPEntryID: DHCP entry ID to delete + DHCPEntryID string `json:"-"` +} + +// DeleteDHCPEntry: delete a DHCP reservation +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) + "", + Headers: http.Header{}, + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +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: fetch rules for this gateway + GatewayID *string `json:"-"` + // PrivateIP: fetch rules targeting this private ip + PrivateIP *net.IP `json:"-"` + // Protocol: fetch rules for this protocol + // + // Default value: unknown + Protocol PATRuleProtocol `json:"-"` +} + +// ListPATRules: list PAT rules +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, + Headers: http.Header{}, + } + + var resp ListPATRulesResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type GetPATRuleRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // PatRuleID: pAT rule to get + PatRuleID string `json:"-"` +} + +// GetPATRule: get a PAT rule +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) + "", + Headers: http.Header{}, + } + + var resp PATRule + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +type CreatePATRuleRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // GatewayID: gateway on which to attach the rule to + 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"` +} + +// CreatePATRule: create a PAT rule +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", + Headers: http.Header{}, + } + + 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 +} + +type UpdatePATRuleRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // PatRuleID: pAT rule to update + PatRuleID string `json:"-"` + // 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"` +} + +// UpdatePATRule: update a PAT rule +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) + "", + Headers: http.Header{}, + } + + 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 +} + +type SetPATRulesRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // GatewayID: gateway on which to set the PAT rules + GatewayID string `json:"gateway_id"` + // PatRules: new list of PAT rules + PatRules []*SetPATRulesRequestRule `json:"pat_rules"` +} + +// SetPATRules: set all PAT rules on a Gateway +// +// Set the list of PAT rules attached to a Gateway. Rules are identified by their public port and protocol. This will sync the current PAT rule list with the givent list, creating, updating or deleting PAT rules. +// +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", + Headers: http.Header{}, + } + + 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 +} + +type DeletePATRuleRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + // PatRuleID: pAT rule to delete + PatRuleID string `json:"-"` +} + +// DeletePATRule: delete a PAT rule +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) + "", + Headers: http.Header{}, + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +type ListGatewayTypesRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` +} + +// ListGatewayTypes: list VPC Public Gateway types +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", + Headers: http.Header{}, + } + + var resp ListGatewayTypesResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +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: iPs per page + PageSize *uint32 `json:"-"` + // OrganizationID: include only IPs in this organization + OrganizationID *string `json:"-"` + // ProjectID: include only IPs in this project + ProjectID *string `json:"-"` + // Tags: filter IPs with these tags + Tags []string `json:"-"` + // Reverse: filter by reverse containing this string + Reverse *string `json:"-"` + // IsFree: filter whether the IP is attached to a gateway or not + IsFree *bool `json:"-"` +} + +// ListIPs: list IPs +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, + Headers: http.Header{}, + } + + var resp ListIPsResponse + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +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 to get + IPID string `json:"-"` +} + +// GetIP: get an IP +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) + "", + Headers: http.Header{}, + } + + var resp IP + + err = s.client.Do(scwReq, &resp, opts...) + if err != nil { + return nil, err + } + return &resp, nil +} + +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 into + ProjectID string `json:"project_id"` + // Tags: tags to give to the IP + Tags []string `json:"tags"` +} + +// CreateIP: reserve an IP +func (s *API) CreateIP(req *CreateIPRequest, opts ...scw.RequestOption) (*IP, error) { + var err error + + if req.ProjectID == "" { + defaultProjectID, _ := s.client.GetDefaultProjectID() + req.ProjectID = defaultProjectID + } + + 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) + "/ips", + Headers: http.Header{}, + } + + 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 +} + +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 to update + IPID string `json:"-"` + // Tags: tags to give to the IP + Tags *[]string `json:"tags"` + // Reverse: reverse to set on the IP. Empty string to unset + Reverse *string `json:"reverse"` + // GatewayID: gateway to attach the IP to. Empty string to detach + GatewayID *string `json:"gateway_id"` +} + +// UpdateIP: update an IP +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) + "", + Headers: http.Header{}, + } + + 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 +} + +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 to delete + IPID string `json:"-"` +} + +// DeleteIP: delete an IP +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) + "", + Headers: http.Header{}, + } + + err = s.client.Do(scwReq, nil, opts...) + if err != nil { + return err + } + return nil +} + +type RefreshSSHKeysRequest struct { + // Zone: + // + // Zone to target. If none is passed will use default zone from the config + Zone scw.Zone `json:"-"` + + GatewayID string `json:"-"` +} + +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", + Headers: http.Header{}, + } + + 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} 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..ade69d7aeeedf --- /dev/null +++ b/vendor/github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1/vpcgw_utils.go @@ -0,0 +1,169 @@ +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{}{ + GatewayStatusRunning: {}, + GatewayStatusDeleted: {}, + GatewayStatusUnknown: {}, + GatewayStatusFailed: {}, + } + + 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: {}, + GatewayNetworkStatusCreated: {}, + } + + 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/zzz-dev-scripts/add_masters.sh b/zzz-dev-scripts/add_masters.sh new file mode 100755 index 0000000000000..3315100673450 --- /dev/null +++ b/zzz-dev-scripts/add_masters.sh @@ -0,0 +1,6 @@ +CLUSTER_NAME=$1 + +go run -v ./cmd/kops replace -f "$CLUSTER_NAME"_extra_masters.yaml +go run -v ./cmd/kops/ create instancegroup -v10 --name=$CLUSTER_NAME master2 --role master --subnet fr-par-1 +go run -v ./cmd/kops/ create instancegroup -v10 --name=$CLUSTER_NAME master3 --role master --subnet fr-par-1 +go run -v ./cmd/kops/ update cluster -v10 --name=$CLUSTER_NAME --yes \ No newline at end of file diff --git a/zzz-dev-scripts/cluster.k8s.local-extra_masters.yaml b/zzz-dev-scripts/cluster.k8s.local-extra_masters.yaml new file mode 100644 index 0000000000000..29a4b832d49cb --- /dev/null +++ b/zzz-dev-scripts/cluster.k8s.local-extra_masters.yaml @@ -0,0 +1,64 @@ +apiVersion: kops.k8s.io/v1alpha2 +kind: Cluster +metadata: + name: cluster.k8s.local +spec: + api: + loadBalancer: + type: Public + authorization: + rbac: {} + channel: stable + cloudProvider: scaleway + configBase: scw://kops-state-store/cluster.k8s.local + etcdClusters: + - cpuRequest: 200m + etcdMembers: + - instanceGroup: control-plane-fr-par-1 + name: etcd-1 + - instanceGroup: master2 + name: etcd-2 + - instanceGroup: master3 + name: etcd-3 + memoryRequest: 100Mi + name: main + - cpuRequest: 100m + etcdMembers: + - instanceGroup: control-plane-fr-par-1 + name: etcd-1 + - instanceGroup: master2 + name: etcd-2 + - instanceGroup: master3 + name: etcd-3 + memoryRequest: 100Mi + name: events + iam: + allowContainerRegistry: true + legacy: false + kubeProxy: + enabled: false + kubelet: + anonymousAuth: false + kubernetesApiAccess: + - 0.0.0.0/0 + - ::/0 + kubernetesVersion: 1.25.3 + masterPublicName: api.cluster.k8s.local + networking: + cilium: + enableNodePort: true + nonMasqueradeCIDR: 100.64.0.0/10 + sshAccess: + - 0.0.0.0/0 + - ::/0 + subnets: + - name: fr-par-1 + type: Public + zone: fr-par-1 + topology: + dns: + type: Public + masters: public + nodes: public + +--- \ No newline at end of file diff --git a/zzz-dev-scripts/cluster.k8s.local-simple.yaml b/zzz-dev-scripts/cluster.k8s.local-simple.yaml new file mode 100644 index 0000000000000..9d2c6a31033c9 --- /dev/null +++ b/zzz-dev-scripts/cluster.k8s.local-simple.yaml @@ -0,0 +1,88 @@ +apiVersion: kops.k8s.io/v1alpha2 +kind: Cluster +metadata: + creationTimestamp: "2022-09-23T09:25:26Z" + name: cluster.k8s.local +spec: + api: + loadBalancer: + type: Public + authorization: + rbac: {} + channel: stable + cloudProvider: scaleway + configBase: scw://kops-state-store/cluster.k8s.local + etcdClusters: + - cpuRequest: 200m + etcdMembers: + - instanceGroup: control-plane-fr-par-1 + name: etcd-1 + memoryRequest: 100Mi + name: main + - cpuRequest: 100m + etcdMembers: + - instanceGroup: control-plane-fr-par-1 + name: etcd-1 + memoryRequest: 100Mi + name: events + iam: + allowContainerRegistry: true + legacy: false + kubelet: + anonymousAuth: false + kubernetesApiAccess: + - 0.0.0.0/0 + - ::/0 + kubernetesVersion: 1.25.2 + masterPublicName: api.cluster.k8s.local + networking: + calico: {} + nonMasqueradeCIDR: 100.64.0.0/10 + sshAccess: + - 0.0.0.0/0 + - ::/0 + subnets: + - name: fr-par-1 + type: Public + zone: fr-par-1 + topology: + dns: + type: Public + masters: public + nodes: public + +--- + +apiVersion: kops.k8s.io/v1alpha2 +kind: InstanceGroup +metadata: + name: master-{{$zone}} + labels: + kops.k8s.io/cluster: {{.clusterName}} +spec: + associatePublicIp: true + image: ubuntu_focal + machineType: DEV1-M + maxSize: 1 + minSize: 1 + role: Master + subnets: + - {{$zone}} + +--- + +apiVersion: kops.k8s.io/v1alpha2 +kind: InstanceGroup +metadata: + name: nodes-{{$zone}} + labels: + kops.k8s.io/cluster: {{.clusterName}} +spec: + associatePublicIp: true + image: ubuntu_focal + machineType: PLAY2-NANO + maxSize: 3 + minSize: 3 + role: Node + subnets: + - {{$zone}} \ No newline at end of file diff --git a/zzz-dev-scripts/cluster.k8s.local_simple.yaml b/zzz-dev-scripts/cluster.k8s.local_simple.yaml new file mode 100644 index 0000000000000..db84026bf95c1 --- /dev/null +++ b/zzz-dev-scripts/cluster.k8s.local_simple.yaml @@ -0,0 +1,52 @@ +apiVersion: kops.k8s.io/v1alpha2 +kind: Cluster +metadata: + creationTimestamp: "2022-09-23T09:25:26Z" + name: cluster.k8s.local +spec: + api: + loadBalancer: + type: Public + authorization: + rbac: {} + channel: stable + cloudProvider: scaleway + configBase: scw://kops-state-store/cluster.k8s.local + etcdClusters: + - cpuRequest: 200m + etcdMembers: + - instanceGroup: master-fr-par-1 + name: etcd-1 + memoryRequest: 100Mi + name: main + - cpuRequest: 100m + etcdMembers: + - instanceGroup: master-fr-par-1 + name: etcd-1 + memoryRequest: 100Mi + name: events + iam: + allowContainerRegistry: true + legacy: false + kubelet: + anonymousAuth: false + kubernetesApiAccess: + - 0.0.0.0/0 + - ::/0 + kubernetesVersion: 1.25.2 + masterPublicName: api.cluster.k8s.local + networking: + calico: {} + nonMasqueradeCIDR: 100.64.0.0/10 + sshAccess: + - 0.0.0.0/0 + - ::/0 + subnets: + - name: fr-par-1 + type: Public + zone: fr-par-1 + topology: + dns: + type: Public + masters: public + nodes: public diff --git a/zzz-dev-scripts/cluster.leila.sieben.fr-extra_masters.yaml b/zzz-dev-scripts/cluster.leila.sieben.fr-extra_masters.yaml new file mode 100644 index 0000000000000..e0916cef28320 --- /dev/null +++ b/zzz-dev-scripts/cluster.leila.sieben.fr-extra_masters.yaml @@ -0,0 +1,60 @@ +apiVersion: kops.k8s.io/v1alpha2 +kind: Cluster +metadata: + creationTimestamp: "2022-09-23T09:25:26Z" + name: cluster.leila.sieben.fr +spec: + api: + loadBalancer: + type: Public + authorization: + rbac: {} + channel: stable + cloudProvider: scaleway + configBase: scw://kops-state-store/cluster.leila.sieben.fr + etcdClusters: + - cpuRequest: 200m + etcdMembers: + - instanceGroup: control-plane-fr-par-1 + name: etcd-1 + - instanceGroup: master2 + name: etcd-2 + - instanceGroup: master3 + name: etcd-3 + memoryRequest: 100Mi + name: main + - cpuRequest: 100m + etcdMembers: + - instanceGroup: control-plane-fr-par-1 + name: etcd-1 + - instanceGroup: master2 + name: etcd-2 + - instanceGroup: master3 + name: etcd-3 + memoryRequest: 100Mi + name: events + iam: + allowContainerRegistry: true + legacy: false + kubelet: + anonymousAuth: false + kubernetesApiAccess: + - 0.0.0.0/0 + - ::/0 + kubernetesVersion: 1.25.2 + masterPublicName: api.cluster.leila.sieben.fr + networking: + calico: {} + nonMasqueradeCIDR: 100.64.0.0/10 + sshAccess: + - 0.0.0.0/0 + - ::/0 + subnets: + - name: fr-par-1 + type: Public + zone: fr-par-1 + topology: + dns: + type: Public + masters: public + nodes: public diff --git a/zzz-dev-scripts/cluster.leila.sieben.fr-simple.yaml b/zzz-dev-scripts/cluster.leila.sieben.fr-simple.yaml new file mode 100644 index 0000000000000..34c44f97f6dab --- /dev/null +++ b/zzz-dev-scripts/cluster.leila.sieben.fr-simple.yaml @@ -0,0 +1,52 @@ +apiVersion: kops.k8s.io/v1alpha2 +kind: Cluster +metadata: + creationTimestamp: "2022-09-23T09:25:26Z" + name: cluster.leila.sieben.fr +spec: + api: + loadBalancer: + type: Public + authorization: + rbac: {} + channel: stable + cloudProvider: scaleway + configBase: scw://kops-state-store/cluster.leila.sieben.fr + etcdClusters: + - cpuRequest: 200m + etcdMembers: + - instanceGroup: control-plane-fr-par-1 + name: etcd-1 + memoryRequest: 100Mi + name: main + - cpuRequest: 100m + etcdMembers: + - instanceGroup: control-plane-fr-par-1 + name: etcd-1 + memoryRequest: 100Mi + name: events + iam: + allowContainerRegistry: true + legacy: false + kubelet: + anonymousAuth: false + kubernetesApiAccess: + - 0.0.0.0/0 + - ::/0 + kubernetesVersion: 1.25.2 + masterPublicName: api.cluster.leila.sieben.fr + networking: + calico: {} + nonMasqueradeCIDR: 100.64.0.0/10 + sshAccess: + - 0.0.0.0/0 + - ::/0 + subnets: + - name: fr-par-1 + type: Public + zone: fr-par-1 + topology: + dns: + type: Public + masters: public + nodes: public diff --git a/zzz-dev-scripts/dev-controllers.sh b/zzz-dev-scripts/dev-controllers.sh new file mode 100755 index 0000000000000..d0d72966cab07 --- /dev/null +++ b/zzz-dev-scripts/dev-controllers.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +KOPS_VERSION=`.build/dist/$(go env GOOS)/$(go env GOARCH)/kops version -- --short` +export DOCKER_IMAGE_PREFIX=kops/ +export DOCKER_REGISTRY=rg.fr-par.scw.cloud + +if [[ $1 == "dns" ]] || [[ $2 == "dns" ]] +then + make dns-controller-push + export DNSCONTROLLER_IMAGE=${DOCKER_IMAGE_PREFIX}dns-controller:${KOPS_VERSION} +fi + +if [[ $1 == "kops" ]] || [[ $2 == "kops" ]] +then + make kops-controller-push + export KOPSCONTROLLER_IMAGE=${DOCKER_IMAGE_PREFIX}kops-controller:${KOPS_VERSION} +fi \ No newline at end of file diff --git a/zzz-dev-scripts/ig_test_loop.sh b/zzz-dev-scripts/ig_test_loop.sh new file mode 100755 index 0000000000000..44a2ec883dd38 --- /dev/null +++ b/zzz-dev-scripts/ig_test_loop.sh @@ -0,0 +1,61 @@ +#!/usr/bin/bash + +CLUSTER_NAME=$1 + +if [ "$2" == "-d" ]; then + go run -v ./cmd/kops -v10 delete cluster --name="$CLUSTER_NAME" --yes + if [ $? != 0 ]; then + echo "ERROR DELETING PREVIOUS CLUSTER" + exit + fi +fi +go run -v ./cmd/kops -v10 create cluster --cloud=scaleway --zones=fr-par-1 --name="$CLUSTER_NAME" --networking=calico --yes +if [ $? != 0 ]; then + echo "ERROR CREATING CLUSTER" + exit +fi +go run -v ./cmd/kops go run -v ./cmd/kops edit ig control-plane-fr-par-1 +if [ $? != 0 ]; then + echo "ERROR GROWING INSTANCE GROUP" + exit +fi +go run -v ./cmd/kops/ update cluster -v10 --name="$CLUSTER_NAME" --yes +if [ $? != 0 ]; then + echo "ERROR UPDATING CLUSTER" + exit +fi +go run -v ./cmd/kops go run -v ./cmd/kops edit ig control-plane-fr-par-1 +if [ $? != 0 ]; then + echo "ERROR SHRINKING INSTANCE GROUP" + exit +fi +go run -v ./cmd/kops/ update cluster -v10 --name="$CLUSTER_NAME" --yes +if [ $? != 0 ]; then + echo "ERROR UPDATING CLUSTER" + exit +fi +go run -v ./cmd/kops/ create instancegroup -v10 --name="$CLUSTER_NAME" master2 --role=master --subnet=fr-par-1 --edit=false +if [ $? != 0 ]; then + echo "ERROR CREATING INSTANCE GROUP MASTER 2" + exit +fi +go run -v ./cmd/kops/ create instancegroup -v10 --name="$CLUSTER_NAME" master3 --role=master --subnet=fr-par-1 --edit=false +if [ $? != 0 ]; then + echo "ERROR CREATING INSTANCE GROUP MASTER 3" + exit +fi +go run -v ./cmd/kops/ update cluster -v10 --name="$CLUSTER_NAME" --yes +if [ $? != 0 ]; then + echo "ERROR UPDATING CLUSTER" + exit +fi +printf '\a' +read -r -p "Are you ready to delete $CLUSTER_NAME ? y or n" input +if [[ $input == "y" ]] +then + go run -v ./cmd/kops -v10 delete cluster --name="$CLUSTER_NAME" --yes + if [ $? != 0 ]; then + echo "ERROR DELETING CLUSTER" + exit + fi +fi \ No newline at end of file diff --git a/zzz-dev-scripts/instance_groups.sh b/zzz-dev-scripts/instance_groups.sh new file mode 100644 index 0000000000000..c9aa92a64de75 --- /dev/null +++ b/zzz-dev-scripts/instance_groups.sh @@ -0,0 +1,34 @@ +########################################### +# cluster.leila.sieben.fr # +########################################### + +# NODE +go run -v ./cmd/kops/ create instancegroup -v10 --name=cluster.leila.sieben.fr extra-node --role node +go run -v ./cmd/kops/ delete instancegroup -v10 --name=cluster.leila.sieben.fr extra-node + +# MASTER +go run -v ./cmd/kops get cluster -o yaml > mycluster.yaml +go run -v ./cmd/kops replace -f zzz-dev-scripts/cluster.leila.sieben.fr_extra_masters.yaml +#go run -v ./cmd/kops/ edit cluster -v10 --name=cluster.leila.sieben.fr +go run -v ./cmd/kops/ create instancegroup -v10 --name=cluster.leila.sieben.fr master2 --role master --subnet fr-par-1 +go run -v ./cmd/kops/ create instancegroup -v10 --name=cluster.leila.sieben.fr master3 --role master --subnet fr-par-1 +go run -v ./cmd/kops/ delete instancegroup -v10 --name=cluster.leila.sieben.fr master2 +go run -v ./cmd/kops/ delete instancegroup -v10 --name=cluster.leila.sieben.fr master3 + +########################################### +# cluster.k8s.local # +########################################### + +# NODE +go run -v ./cmd/kops/ create instancegroup -v10 --name=cluster.k8s.local extra-node --role node +go run -v ./cmd/kops/ delete instancegroup -v10 --name=cluster.k8s.local extra-node + +# MASTER +go run -v ./cmd/kops get cluster -o yaml > cluster.k8s.local_simple.yaml +go run -v ./cmd/kops replace -f zzz-dev-scripts/cluster.k8s.local_extra_masters.yaml +#go run -v ./cmd/kops/ edit cluster -v10 --name=cluster.k8s.local +go run -v ./cmd/kops/ create instancegroup -v10 --name=cluster.k8s.local master2 --role master --subnet fr-par-1 +go run -v ./cmd/kops/ create instancegroup -v10 --name=cluster.k8s.local master3 --role master --subnet fr-par-1 +go run -v ./cmd/kops/ delete instancegroup -v10 --name=cluster.k8s.local master2 +go run -v ./cmd/kops/ delete instancegroup -v10 --name=cluster.k8s.local master3 + diff --git a/zzz-dev-scripts/rebuild_registry.sh b/zzz-dev-scripts/rebuild_registry.sh new file mode 100755 index 0000000000000..97286f85f5562 --- /dev/null +++ b/zzz-dev-scripts/rebuild_registry.sh @@ -0,0 +1,32 @@ +#!/usr/bin/zsh + +KOPS_PATH=$HOME/Desktop/kops +ETCD_MANAGER_PATH=$HOME/Desktop/etcdadm/etcd-manager +PROFILE=normal + +export REGISTRY_NAME=kops +export DOCKER_REGISTRY=rg.fr-par.scw.cloud +export DOCKER_IMAGE_PREFIX=$REGISTRY_NAME/ +export DOCKER_TAG=1.25.0-beta.1 + +if [[ $1 == "-r" ]] +then + echo "Recreating registry" + scw registry namespace create name=$REGISTRY_NAME is-public=true description="Stores images needed by kops (things like etcd-manager, dns-controller, kops-controller, etc)" -p $PROFILE +fi + +docker login rg.fr-par.scw.cloud/$REGISTRY_NAME -u nologin --password $SCW_SECRET_KEY + +cd "$KOPS_PATH" || exit +printf "\nKOPS-CONTROLLER\n" +make kops-controller-push +printf "\nDNS-CONTROLLER\n" +make dns-controller-push +printf "\nKUBE-API-SERVER-HEALTHCHECK\n" +make kube-apiserver-healthcheck-push + +cd "$ETCD_MANAGER_PATH" || exit +printf "\nETCD-MANAGER\n" +make push-etcd-manager +#printf "\nETCD-MANAGER MANIFESTS\n" +#make push-etcd-manager-manifest diff --git a/zzz-dev-scripts/remove_masters.sh b/zzz-dev-scripts/remove_masters.sh new file mode 100755 index 0000000000000..45e9b957020fc --- /dev/null +++ b/zzz-dev-scripts/remove_masters.sh @@ -0,0 +1,7 @@ +CLUSTER_NAME=$1 +SPEC_FILES_DIR=zzz-dev-scripts + +go run -v ./cmd/kops/ delete instancegroup -v10 --name=$CLUSTER_NAME master2 --yes +go run -v ./cmd/kops/ delete instancegroup -v10 --name=$CLUSTER_NAME master3 --yes +go run -v ./cmd/kops replace -f "$SPEC_FILES_DIR/$CLUSTER_NAME"_simple.yaml +go run -v ./cmd/kops/ update cluster -v10 --name=$CLUSTER_NAME --yes diff --git a/zzz-dev-scripts/see_my_resources.sh b/zzz-dev-scripts/see_my_resources.sh new file mode 100755 index 0000000000000..87468a0f34b30 --- /dev/null +++ b/zzz-dev-scripts/see_my_resources.sh @@ -0,0 +1,9 @@ +echo "--> DNS RECORDS :" +scw dns record list leila.sieben.fr +echo "\n--> LOAD - BALANCERS :" +scw lb lb list zone=fr-par-1 +echo "\n--> SERVERS :" +scw instance server list zone=fr-par-1 +echo "\n--> VOLUMES :" +scw instance volume list zone=fr-par-1 + diff --git a/zzz-dev-scripts/total_test_loop.sh b/zzz-dev-scripts/total_test_loop.sh new file mode 100755 index 0000000000000..2e7a0e6eb5f95 --- /dev/null +++ b/zzz-dev-scripts/total_test_loop.sh @@ -0,0 +1,116 @@ +#!/usr/bin/bash + +CLUSTER_NAME=$1 +SPEC_FILES_DIR=zzz-dev-scripts + +delete_cluster() { + go run -v ./cmd/kops -v10 delete cluster --name="$CLUSTER_NAME" --yes + if [ $? != 0 ]; then + echo "ERROR DELETING" "$1" "CLUSTER" + exit 1 + fi +} + +create_cluster() { + go run -v ./cmd/kops -v10 create cluster --cloud=scaleway --zones=fr-par-1 --name="$CLUSTER_NAME" --networking=cilium --yes + if [ $? != 0 ]; then + echo "ERROR CREATING CLUSTER" + exit 1 + fi +} + +validate_cluster() { + go run -v ./cmd/kops validate cluster --wait=10m + if [ $? != 0 ]; then + echo "COULD NOT VALIDATE CLUSTER WITHIN 10MIN" + exit 1 + fi +} + +update_cluster() { + go run -v ./cmd/kops/ update cluster -v10 --name="$CLUSTER_NAME" --yes + if [ $? != 0 ]; then + echo "ERROR UPDATING CLUSTER" + exit 1 + fi +} + +replace_conf_file() { + go run -v ./cmd/kops replace -f $SPEC_FILES_DIR/$CLUSTER_NAME-$1.yaml + if [ $? != 0 ]; then + echo "ERROR REPLACING CLUSTER SPEC FILE $SPEC_FILES_DIR/$CLUSTER_NAME-$1.yaml" + exit 1 + fi +} + +add_instance_group() { + go run -v ./cmd/kops/ create instancegroup -v10 --name="$CLUSTER_NAME" "$1" --role="$2" --subnet=fr-par-1 --edit=false + if [ $? != 0 ]; then + echo "ERROR CREATING INSTANCE GROUP $1" + exit 1 + fi +} + +delete_instance_group() { + go run -v ./cmd/kops/ delete instancegroup -v10 --name="$CLUSTER_NAME" "$1" --yes + if [ $? != 0 ]; then + echo "ERROR DELETING INSTANCE GROUP $1" + exit 1 + fi +} + +######################################################################################################################## + +if [ "$1" == "-d" ] || [ "$1" == "-c" ] || [ "$1" == "-am" ] || [ "$1" == "-rm" ] ; then + echo "You forgot to give me a cluster name !" + exit +fi + +# DELETE PREVIOUS CLUSTER ? +if [ "$2" == "-d" ] ; then + delete_cluster "previous" + if [ "$3" == "" ]; then + exit 0 + fi +fi + +# CREATE CLUSTER ? +if [ "$2" == "-c" ] || [ "$3" == "-c" ] ; then + create_cluster +fi + +# ADD MASTERS ? +if [ "$2" == "-am" ] || [ "$3" == "-am" ] || [ "$4" == "-am" ] ; then + validate_cluster + replace_conf_file "extra_masters" + add_instance_group "master2" "master" + add_instance_group "master3" "master" + update_cluster + # REMOVE EXTRA MASTERS ? + read -r -p "Are you ready to remove extra masters ? y or n" input + if [[ $input == "y" ]] ; then + replace_conf_file "simple" + update_cluster + delete_instance_group "master2" + delete_instance_group "master3" + fi +fi + +# REMOVE EXTRA MASTERS ? +if [ "$2" == "-rm" ] || [ "$3" == "-rm" ] || [ "$4" == "-rm" ] || [ "$5" == "-rm" ] ; then + if [[ $input == "y" ]] ; then + replace_conf_file "simple" + delete_instance_group "master2" + delete_instance_group "master3" + update_cluster + fi +fi + +printf '\a' + +# DELETE CLUSTER ? +read -r -p "Are you ready to delete $CLUSTER_NAME ? y or n" input +if [[ $input == "y" ]] +then + delete_cluster +fi \ No newline at end of file diff --git a/zzz-dev-scripts/update_binaries.sh b/zzz-dev-scripts/update_binaries.sh new file mode 100755 index 0000000000000..ac05033131f53 --- /dev/null +++ b/zzz-dev-scripts/update_binaries.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +#make nodeup-arm64 +make nodeup-amd64 +#make protokube-arm64 +make protokube-amd64 +#sha256sum .build/dist/linux/amd64/nodeup > ./build/dist/linux/amd64/hash-nodeup +#sha256sum .build/dist/linux/arm64/nodeup > ./build/dist/linux/arm64/hash-nodeup +rclone sync .build/dist/ scaleway:kops-state-store-test/dist/ -P +#mc cp -r .build/dist/ s3://kops-state-store-test/dist/ \ No newline at end of file