mirror of https://github.com/kubernetes/kops.git
Merge pull request #15393 from scaleway/scaleway_dns
scaleway: dns support
This commit is contained in:
commit
e299cc2c4b
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package mockdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1"
|
||||||
|
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FakeDomainAPI struct {
|
||||||
|
DNSZones []*domain.DNSZone
|
||||||
|
Records map[string]*domain.Record
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeDomainAPI) ListDNSZones(req *domain.ListDNSZonesRequest, opts ...scw.RequestOption) (*domain.ListDNSZonesResponse, error) {
|
||||||
|
if f.DNSZones == nil {
|
||||||
|
return nil, fmt.Errorf("error response")
|
||||||
|
}
|
||||||
|
return &domain.ListDNSZonesResponse{
|
||||||
|
TotalCount: uint32(len(f.DNSZones)),
|
||||||
|
DNSZones: f.DNSZones,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeDomainAPI) CreateDNSZone(req *domain.CreateDNSZoneRequest, opts ...scw.RequestOption) (*domain.DNSZone, error) {
|
||||||
|
if f.DNSZones == nil {
|
||||||
|
return nil, fmt.Errorf("error response")
|
||||||
|
}
|
||||||
|
newZone := &domain.DNSZone{
|
||||||
|
Domain: req.Domain,
|
||||||
|
Subdomain: req.Subdomain,
|
||||||
|
}
|
||||||
|
f.DNSZones = append(f.DNSZones, newZone)
|
||||||
|
return newZone, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeDomainAPI) DeleteDNSZone(req *domain.DeleteDNSZoneRequest, opts ...scw.RequestOption) (*domain.DeleteDNSZoneResponse, error) {
|
||||||
|
if f.DNSZones == nil {
|
||||||
|
return nil, fmt.Errorf("error response")
|
||||||
|
}
|
||||||
|
var newZoneList []*domain.DNSZone
|
||||||
|
for _, zone := range f.DNSZones {
|
||||||
|
if req.DNSZone == fmt.Sprintf("%s.%s", zone.Subdomain, zone.Domain) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newZoneList = append(newZoneList, zone)
|
||||||
|
}
|
||||||
|
f.DNSZones = newZoneList
|
||||||
|
return &domain.DeleteDNSZoneResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeDomainAPI) ListDNSZoneRecords(req *domain.ListDNSZoneRecordsRequest, opts ...scw.RequestOption) (*domain.ListDNSZoneRecordsResponse, error) {
|
||||||
|
var recordsList []*domain.Record
|
||||||
|
for _, record := range f.Records {
|
||||||
|
recordsList = append(recordsList, record)
|
||||||
|
}
|
||||||
|
records := &domain.ListDNSZoneRecordsResponse{
|
||||||
|
TotalCount: uint32(len(f.Records)),
|
||||||
|
Records: recordsList,
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeDomainAPI) UpdateDNSZoneRecords(req *domain.UpdateDNSZoneRecordsRequest, opts ...scw.RequestOption) (*domain.UpdateDNSZoneRecordsResponse, error) {
|
||||||
|
for _, change := range req.Changes {
|
||||||
|
|
||||||
|
if change.Add != nil {
|
||||||
|
for _, toAdd := range change.Add.Records {
|
||||||
|
toAdd.ID = uuid.New().String()
|
||||||
|
f.Records[toAdd.Name] = toAdd
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if change.Set != nil {
|
||||||
|
if len(change.Set.Records) != 1 {
|
||||||
|
fmt.Printf("only 1 record change will be applied from %d changes requested", len(change.Set.Records))
|
||||||
|
}
|
||||||
|
for _, toUpsert := range change.Set.Records {
|
||||||
|
if _, ok := f.Records[toUpsert.Name]; !ok {
|
||||||
|
return nil, fmt.Errorf("could not find record %s to upsert", toUpsert.Name)
|
||||||
|
}
|
||||||
|
toUpsert.ID = *change.Set.ID
|
||||||
|
f.Records[toUpsert.Name] = toUpsert
|
||||||
|
}
|
||||||
|
} else if change.Delete != nil {
|
||||||
|
found := false
|
||||||
|
for name, record := range f.Records {
|
||||||
|
if record.ID == *change.Delete.ID {
|
||||||
|
delete(f.Records, name)
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found == false {
|
||||||
|
return nil, fmt.Errorf("could not find record %s to delete", *change.Delete.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("mock DNS not implemented for this method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var recordsList []*domain.Record
|
||||||
|
for _, record := range f.Records {
|
||||||
|
recordsList = append(recordsList, record)
|
||||||
|
}
|
||||||
|
return &domain.UpdateDNSZoneRecordsResponse{Records: recordsList}, nil
|
||||||
|
}
|
||||||
|
|
@ -38,6 +38,7 @@ import (
|
||||||
_ "k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/do"
|
_ "k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/do"
|
||||||
_ "k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/google/clouddns"
|
_ "k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/google/clouddns"
|
||||||
_ "k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/openstack/designate"
|
_ "k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/openstack/designate"
|
||||||
|
_ "k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/scaleway"
|
||||||
"k8s.io/kops/pkg/wellknownports"
|
"k8s.io/kops/pkg/wellknownports"
|
||||||
"k8s.io/kops/protokube/pkg/gossip"
|
"k8s.io/kops/protokube/pkg/gossip"
|
||||||
gossipdns "k8s.io/kops/protokube/pkg/gossip/dns"
|
gossipdns "k8s.io/kops/protokube/pkg/gossip/dns"
|
||||||
|
|
@ -67,7 +68,7 @@ func main() {
|
||||||
flags.BoolVar(&watchIngress, "watch-ingress", true, "Configure hostnames found in ingress resources")
|
flags.BoolVar(&watchIngress, "watch-ingress", true, "Configure hostnames found in ingress resources")
|
||||||
flags.StringSliceVar(&gossipSeeds, "gossip-seed", gossipSeeds, "If set, will enable gossip zones and seed using the provided addresses")
|
flags.StringSliceVar(&gossipSeeds, "gossip-seed", gossipSeeds, "If set, will enable gossip zones and seed using the provided addresses")
|
||||||
flags.StringSliceVarP(&zones, "zone", "z", []string{}, "Configure permitted zones and their mappings")
|
flags.StringSliceVarP(&zones, "zone", "z", []string{}, "Configure permitted zones and their mappings")
|
||||||
flags.StringVar(&dnsProviderID, "dns", "aws-route53", "DNS provider we should use (aws-route53, google-clouddns, digitalocean, gossip, openstack-designate)")
|
flags.StringVar(&dnsProviderID, "dns", "aws-route53", "DNS provider we should use (aws-route53, google-clouddns, digitalocean, gossip, openstack-designate, scaleway)")
|
||||||
flag.StringVar(&gossipProtocol, "gossip-protocol", "mesh", "mesh/memberlist")
|
flag.StringVar(&gossipProtocol, "gossip-protocol", "mesh", "mesh/memberlist")
|
||||||
flags.StringVar(&gossipListen, "gossip-listen", fmt.Sprintf("0.0.0.0:%d", wellknownports.DNSControllerGossipWeaveMesh), "The address on which to listen if gossip is enabled")
|
flags.StringVar(&gossipListen, "gossip-listen", fmt.Sprintf("0.0.0.0:%d", wellknownports.DNSControllerGossipWeaveMesh), "The address on which to listen if gossip is enabled")
|
||||||
flags.StringVar(&gossipSecret, "gossip-secret", gossipSecret, "Secret to use to secure gossip")
|
flags.StringVar(&gossipSecret, "gossip-secret", gossipSecret, "Secret to use to secure gossip")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,458 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1"
|
||||||
|
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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(domain.NewAPI(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 {
|
||||||
|
domainAPI DomainAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProvider returns an implementation of dnsprovider.Interface
|
||||||
|
func NewProvider(api DomainAPI) dnsprovider.Interface {
|
||||||
|
return &Interface{domainAPI: api}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zones returns an implementation of dnsprovider.Zones
|
||||||
|
func (d Interface) Zones() (dnsprovider.Zones, bool) {
|
||||||
|
return &zones{
|
||||||
|
domainAPI: d.domainAPI,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// zones is an implementation of dnsprovider.Zones
|
||||||
|
type zones struct {
|
||||||
|
domainAPI DomainAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns a list of all dns zones
|
||||||
|
func (z *zones) List() ([]dnsprovider.Zone, error) {
|
||||||
|
dnsZones, err := z.domainAPI.ListDNSZones(&domain.ListDNSZonesRequest{}, scw.WithAllPages())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list DNS zones: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zonesList := []dnsprovider.Zone(nil)
|
||||||
|
for _, dnsZone := range dnsZones.DNSZones {
|
||||||
|
newZone := &zone{
|
||||||
|
name: dnsZone.Domain,
|
||||||
|
domainAPI: z.domainAPI,
|
||||||
|
}
|
||||||
|
zonesList = append(zonesList, newZone)
|
||||||
|
}
|
||||||
|
|
||||||
|
return zonesList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a new DNS zone. The name of the new zone should be of the form "name.domain", otherwise we can't infer the
|
||||||
|
// domain name from anywhere else in this function
|
||||||
|
func (z *zones) Add(newZone dnsprovider.Zone) (dnsprovider.Zone, error) {
|
||||||
|
newZoneNameSplit := strings.SplitN(newZone.Name(), ".", 2)
|
||||||
|
if len(newZoneNameSplit) < 2 {
|
||||||
|
return nil, fmt.Errorf("new zone name should contain at least 1 '.', got %q", newZone.Name())
|
||||||
|
}
|
||||||
|
newZoneName := newZoneNameSplit[0]
|
||||||
|
domainName := newZoneNameSplit[1]
|
||||||
|
klog.V(8).Infof("Adding new DNS zone %s to domain %s", newZoneName, domainName)
|
||||||
|
|
||||||
|
_, err := z.domainAPI.CreateDNSZone(&domain.CreateDNSZoneRequest{
|
||||||
|
Subdomain: newZoneName,
|
||||||
|
Domain: domainName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
klog.V(4).Infof("Added new DNS zone %s to domain %s", newZoneName, domainName)
|
||||||
|
|
||||||
|
return &zone{
|
||||||
|
name: newZoneName,
|
||||||
|
domainAPI: z.domainAPI,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deletes a zone
|
||||||
|
func (z *zones) Remove(zone dnsprovider.Zone) error {
|
||||||
|
_, err := z.domainAPI.DeleteDNSZone(&domain.DeleteDNSZoneRequest{
|
||||||
|
DNSZone: zone.Name(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new implementation of dnsprovider.Zone
|
||||||
|
func (z *zones) New(name string) (dnsprovider.Zone, error) {
|
||||||
|
return &zone{
|
||||||
|
name: name,
|
||||||
|
domainAPI: z.domainAPI,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// zone implements dnsprovider.Zone
|
||||||
|
type zone struct {
|
||||||
|
name string
|
||||||
|
domainAPI DomainAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, domainAPI: z.domainAPI}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourceRecordSets implements dnsprovider.ResourceRecordSet
|
||||||
|
type resourceRecordSets struct {
|
||||||
|
zone *zone
|
||||||
|
domainAPI DomainAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns a list of dnsprovider.ResourceRecordSet
|
||||||
|
func (r *resourceRecordSets) List() ([]dnsprovider.ResourceRecordSet, error) {
|
||||||
|
records, err := listRecords(r.domainAPI, 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()
|
||||||
|
recordKey := recordName + "_" + record.Type.String()
|
||||||
|
if rrset, ok := rrsetsWithoutDups[recordKey]; !ok {
|
||||||
|
rrsetsWithoutDups[recordKey] = &resourceRecordSet{
|
||||||
|
name: recordName,
|
||||||
|
data: []string{record.Data},
|
||||||
|
ttl: int(record.TTL),
|
||||||
|
recordType: rrstype.RrsType(record.Type),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rrset.data = append(rrset.data, record.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rrset := range rrsetsWithoutDups {
|
||||||
|
rrsets = append(rrsets, rrset)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rrsets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a list of dnsprovider.ResourceRecordSet that matches the name parameter. The name should contain the domain name.
|
||||||
|
func (r *resourceRecordSets) Get(name string) ([]dnsprovider.ResourceRecordSet, error) {
|
||||||
|
rrsetList, err := r.List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var recordSets []dnsprovider.ResourceRecordSet
|
||||||
|
for _, rrset := range rrsetList {
|
||||||
|
if rrset.Name() == name {
|
||||||
|
recordSets = append(recordSets, rrset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return recordSets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns an implementation of dnsprovider.ResourceRecordSet. The name should contain the domain name.
|
||||||
|
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{
|
||||||
|
domainAPI: r.domainAPI,
|
||||||
|
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 {
|
||||||
|
domainAPI DomainAPI
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRecordsRequest := []*domain.RecordChange(nil)
|
||||||
|
klog.V(8).Infof("applying changes in record change set : [ %d additions | %d upserts | %d removals ]",
|
||||||
|
len(r.additions), len(r.upserts), len(r.removals))
|
||||||
|
|
||||||
|
records, err := listRecords(r.domainAPI, r.zone.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.additions) > 0 {
|
||||||
|
recordsToAdd := []*domain.Record(nil)
|
||||||
|
for _, rrset := range r.additions {
|
||||||
|
recordName := strings.TrimSuffix(rrset.Name(), ".")
|
||||||
|
recordName = strings.TrimSuffix(recordName, "."+r.zone.Name())
|
||||||
|
for _, rrdata := range rrset.Rrdatas() {
|
||||||
|
recordsToAdd = append(recordsToAdd, &domain.Record{
|
||||||
|
Name: recordName,
|
||||||
|
Data: rrdata,
|
||||||
|
TTL: uint32(rrset.Ttl()),
|
||||||
|
Type: domain.RecordType(rrset.Type()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
klog.V(8).Infof("adding new DNS record %q to zone %q", recordName, 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 {
|
||||||
|
recordNameWithZone := fmt.Sprintf("%s.%s.", record.Name, r.zone.Name())
|
||||||
|
if recordNameWithZone == dns.EnsureDotSuffix(rrset.Name()) && rrset.Type() == rrstype.RrsType(record.Type) {
|
||||||
|
klog.V(8).Infof("changing DNS record %q of zone %q", record.Name, r.zone.Name())
|
||||||
|
updateRecordsRequest = append(updateRecordsRequest, &domain.RecordChange{
|
||||||
|
Set: &domain.RecordChangeSet{
|
||||||
|
ID: &record.ID,
|
||||||
|
Records: []*domain.Record{
|
||||||
|
{
|
||||||
|
Name: record.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 {
|
||||||
|
recordNameWithZone := fmt.Sprintf("%s.%s.", record.Name, r.zone.Name())
|
||||||
|
if recordNameWithZone == dns.EnsureDotSuffix(rrset.Name()) && record.Data == rrset.Rrdatas()[0] &&
|
||||||
|
rrset.Type() == rrstype.RrsType(record.Type) {
|
||||||
|
klog.V(8).Infof("removing DNS record %q of zone %q", record.Name, r.zone.name)
|
||||||
|
updateRecordsRequest = append(updateRecordsRequest, &domain.RecordChange{
|
||||||
|
Delete: &domain.RecordChangeDelete{
|
||||||
|
ID: &record.ID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = r.domainAPI.UpdateDNSZoneRecords(&domain.UpdateDNSZoneRecordsRequest{
|
||||||
|
DNSZone: r.zone.Name(),
|
||||||
|
Changes: updateRecordsRequest,
|
||||||
|
}, scw.WithContext(ctx))
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// listRecords returns a list of scaleway records given a zone name (the name of the record doesn't end with the zone name)
|
||||||
|
func listRecords(api DomainAPI, zoneName string) ([]*domain.Record, error) {
|
||||||
|
records, err := api.ListDNSZoneRecords(&domain.ListDNSZoneRecordsRequest{
|
||||||
|
DNSZone: zoneName,
|
||||||
|
}, scw.WithAllPages())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list records: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return records.Records, err
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,301 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1"
|
||||||
|
"k8s.io/kops/cloudmock/scaleway/mockdns"
|
||||||
|
"k8s.io/kops/dnsprovider/pkg/dnsprovider"
|
||||||
|
"k8s.io/kops/dnsprovider/pkg/dnsprovider/rrstype"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setUpFakeZones() *mockdns.FakeDomainAPI {
|
||||||
|
return &mockdns.FakeDomainAPI{
|
||||||
|
DNSZones: []*domain.DNSZone{
|
||||||
|
{
|
||||||
|
Domain: "example.com",
|
||||||
|
Subdomain: "zone",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "example.com",
|
||||||
|
Subdomain: "kops",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "domain.fr",
|
||||||
|
Subdomain: "zone",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUpFakeZonesNil() *mockdns.FakeDomainAPI {
|
||||||
|
return &mockdns.FakeDomainAPI{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDNSProviderZones(api DomainAPI) dnsprovider.Zones {
|
||||||
|
dnsProvider := NewProvider(api)
|
||||||
|
zs, _ := dnsProvider.Zones()
|
||||||
|
return zs
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestZonesListValid(t *testing.T) {
|
||||||
|
domainAPI := setUpFakeZones()
|
||||||
|
z := &zones{domainAPI: domainAPI}
|
||||||
|
|
||||||
|
zoneList, err := z.List()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error listing zones: %v", err)
|
||||||
|
}
|
||||||
|
if len(zoneList) != 3 {
|
||||||
|
t.Errorf("expected at least 1 zone, got 0")
|
||||||
|
}
|
||||||
|
for i, zone := range zoneList {
|
||||||
|
if zone.Name() != domainAPI.DNSZones[i].Domain {
|
||||||
|
t.Errorf("expected %s as zone name, got: %s", domainAPI.DNSZones[i].Domain, zone.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestZonesListShouldFail(t *testing.T) {
|
||||||
|
domainAPI := setUpFakeZonesNil()
|
||||||
|
z := &zones{domainAPI: domainAPI}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
domainAPI := setUpFakeZones()
|
||||||
|
zs := getDNSProviderZones(domainAPI)
|
||||||
|
|
||||||
|
inZone := &zone{
|
||||||
|
name: "dns.example.com",
|
||||||
|
domainAPI: domainAPI,
|
||||||
|
}
|
||||||
|
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() != "dns" {
|
||||||
|
t.Errorf("unexpected zone name: %s", outZone.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddShouldFail(t *testing.T) {
|
||||||
|
domainAPI := setUpFakeZonesNil()
|
||||||
|
zs := getDNSProviderZones(domainAPI)
|
||||||
|
|
||||||
|
inZone := &zone{
|
||||||
|
name: "dns.example.com",
|
||||||
|
domainAPI: domainAPI,
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
domainAPI := setUpFakeZones()
|
||||||
|
zs := getDNSProviderZones(domainAPI)
|
||||||
|
|
||||||
|
inZone := &zone{
|
||||||
|
name: "kops.example.com",
|
||||||
|
domainAPI: domainAPI,
|
||||||
|
}
|
||||||
|
err := zs.Remove(inZone)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveShouldFail(t *testing.T) {
|
||||||
|
domainAPI := setUpFakeZonesNil()
|
||||||
|
zs := getDNSProviderZones(domainAPI)
|
||||||
|
|
||||||
|
inZone := &zone{
|
||||||
|
name: "kops.example.com",
|
||||||
|
domainAPI: domainAPI,
|
||||||
|
}
|
||||||
|
err := zs.Remove(inZone)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected non-nil err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewZone(t *testing.T) {
|
||||||
|
domainAPI := setUpFakeZones()
|
||||||
|
zs := getDNSProviderZones(domainAPI)
|
||||||
|
|
||||||
|
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 setUpFakeRecords() *mockdns.FakeDomainAPI {
|
||||||
|
return &mockdns.FakeDomainAPI{
|
||||||
|
Records: map[string]*domain.Record{
|
||||||
|
"test": {
|
||||||
|
Data: "1.2.3.4",
|
||||||
|
Name: "test",
|
||||||
|
TTL: 3600,
|
||||||
|
Type: "A",
|
||||||
|
ID: uuid.New().String(),
|
||||||
|
},
|
||||||
|
"to-remove": {
|
||||||
|
Data: "5.6.7.8",
|
||||||
|
Name: "to-remove",
|
||||||
|
TTL: 3600,
|
||||||
|
Type: "A",
|
||||||
|
ID: uuid.New().String(),
|
||||||
|
},
|
||||||
|
"to-upsert": {
|
||||||
|
Data: "127.0.0.1",
|
||||||
|
Name: "to-upsert",
|
||||||
|
TTL: 3600,
|
||||||
|
Type: "A",
|
||||||
|
ID: uuid.New().String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewResourceRecordSet(t *testing.T) {
|
||||||
|
domainAPI := setUpFakeRecords()
|
||||||
|
zoneName := "kops.example.com"
|
||||||
|
zone := zone{
|
||||||
|
domainAPI: domainAPI,
|
||||||
|
name: zoneName,
|
||||||
|
}
|
||||||
|
|
||||||
|
rrset, _ := zone.ResourceRecordSets()
|
||||||
|
rrsets, err := rrset.List()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error listing resource record sets: %v", err)
|
||||||
|
}
|
||||||
|
if len(rrsets) != 3 {
|
||||||
|
t.Errorf("unexpected number of records: %d", len(rrsets))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, record := range rrsets {
|
||||||
|
recordNameShort := strings.TrimSuffix(record.Name(), "."+zoneName)
|
||||||
|
expectedRecord, ok := domainAPI.Records[recordNameShort]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("could not find record %s in mock records list", record.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedName := fmt.Sprintf("%s.%s", expectedRecord.Name, zoneName)
|
||||||
|
if record.Name() != expectedName {
|
||||||
|
t.Errorf("expected %q as record name, got %q", expectedName, record.Name())
|
||||||
|
}
|
||||||
|
if record.Ttl() != int64(expectedRecord.TTL) {
|
||||||
|
t.Errorf("expected %d as record TTL, got %d", expectedRecord.TTL, record.Ttl())
|
||||||
|
}
|
||||||
|
if record.Type() != rrstype.RrsType(expectedRecord.Type) {
|
||||||
|
t.Errorf("expected %q as record type, got %q", expectedRecord.Type, record.Type())
|
||||||
|
}
|
||||||
|
if len(record.Rrdatas()) < 1 {
|
||||||
|
t.Errorf("expected at least 1 rrdata for record %s, got 0", record.Name())
|
||||||
|
} else if record.Rrdatas()[0] != expectedRecord.Data {
|
||||||
|
t.Errorf("expected %q as record data, got %q", expectedRecord.Data, record.Rrdatas())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceRecordChangeset(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
domainAPI := setUpFakeRecords()
|
||||||
|
zoneName := "kops.example.com"
|
||||||
|
zone := zone{
|
||||||
|
domainAPI: domainAPI,
|
||||||
|
name: zoneName,
|
||||||
|
}
|
||||||
|
|
||||||
|
rrsets, _ := zone.ResourceRecordSets()
|
||||||
|
changeset := rrsets.StartChangeset()
|
||||||
|
|
||||||
|
if !changeset.IsEmpty() {
|
||||||
|
t.Error("expected empty changeset")
|
||||||
|
}
|
||||||
|
|
||||||
|
rrset := rrsets.New(fmt.Sprintf("%s.%s", "to-add", zoneName), []string{"127.0.0.1"}, 3600, rrstype.A)
|
||||||
|
changeset.Add(rrset)
|
||||||
|
|
||||||
|
rrset = rrsets.New(fmt.Sprintf("%s.%s", "to-remove", zoneName), []string{"5.6.7.8"}, 3600, rrstype.A)
|
||||||
|
changeset.Remove(rrset)
|
||||||
|
|
||||||
|
rrset = rrsets.New(fmt.Sprintf("%s.%s", "to-upsert", zoneName), []string{"127.0.0.1"}, 3601, rrstype.A)
|
||||||
|
changeset.Upsert(rrset)
|
||||||
|
|
||||||
|
err := changeset.Apply(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error applying changeset: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = rrsets.Get(fmt.Sprintf("%s.%s", "test", zoneName))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error getting resource record set: %v", err)
|
||||||
|
}
|
||||||
|
_, err = rrsets.Get(fmt.Sprintf("%s.%s", "to-add", zoneName))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error getting resource record set: %v", err)
|
||||||
|
}
|
||||||
|
recordsRemove, _ := rrsets.Get(fmt.Sprintf("%s.%s", "to-remove", zoneName))
|
||||||
|
if recordsRemove != nil {
|
||||||
|
t.Errorf("record set 'to-remove' should have been deleted")
|
||||||
|
}
|
||||||
|
recordsUpsert, err := rrsets.Get(fmt.Sprintf("%s.%s", "to-upsert", zoneName))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error getting resource record set: %v", err)
|
||||||
|
}
|
||||||
|
if recordsUpsert[0].Ttl() != 3601 {
|
||||||
|
t.Errorf("unexpected record TTL: %d, expected 3601", recordsUpsert[0].Ttl())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1"
|
||||||
|
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||||
|
"k8s.io/kops/cloudmock/scaleway/mockdns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DomainAPI interface {
|
||||||
|
ListDNSZones(req *domain.ListDNSZonesRequest, opts ...scw.RequestOption) (*domain.ListDNSZonesResponse, error)
|
||||||
|
CreateDNSZone(req *domain.CreateDNSZoneRequest, opts ...scw.RequestOption) (*domain.DNSZone, error)
|
||||||
|
DeleteDNSZone(req *domain.DeleteDNSZoneRequest, opts ...scw.RequestOption) (*domain.DeleteDNSZoneResponse, error)
|
||||||
|
ListDNSZoneRecords(req *domain.ListDNSZoneRecordsRequest, opts ...scw.RequestOption) (*domain.ListDNSZoneRecordsResponse, error)
|
||||||
|
UpdateDNSZoneRecords(req *domain.UpdateDNSZoneRecordsRequest, opts ...scw.RequestOption) (*domain.UpdateDNSZoneRecordsResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ DomainAPI = &domain.API{}
|
||||||
|
var _ DomainAPI = &mockdns.FakeDomainAPI{}
|
||||||
|
|
@ -10,15 +10,14 @@
|
||||||
|
|
||||||
### Coming soon
|
### Coming soon
|
||||||
|
|
||||||
* Scaleway DNS (to create clusters with a custom domain name)
|
* [Terraform](https://github.com/scaleway/terraform-provider-scaleway) support
|
||||||
* Private network
|
* Private network
|
||||||
|
|
||||||
### Next features to implement
|
### Next features to implement
|
||||||
|
|
||||||
* `kops rolling-update`
|
* `kops rolling-update`
|
||||||
* BareMetal servers
|
|
||||||
* [Terraform](https://github.com/scaleway/terraform-provider-scaleway) support
|
|
||||||
* [Autoscaler](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler/cloudprovider/scaleway) support
|
* [Autoscaler](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler/cloudprovider/scaleway) support
|
||||||
|
* BareMetal servers
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
|
@ -30,7 +29,7 @@
|
||||||
### Optional
|
### Optional
|
||||||
|
|
||||||
* [SSH key](https://www.scaleway.com/en/docs/configure-new-ssh-key/) : creating a cluster can be done without an SSH key, but it is required to update it. `id_rsa` and `id_ed25519` keys are supported
|
* [SSH key](https://www.scaleway.com/en/docs/configure-new-ssh-key/) : creating a cluster can be done without an SSH key, but it is required to update it. `id_rsa` and `id_ed25519` keys are supported
|
||||||
|
* [Domain name](https://www.scaleway.com/en/docs/network/domains-and-dns/quickstart/) : if you want to host your cluster on your own domain, you will have to register it with Scaleway.
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
|
|
@ -65,6 +64,8 @@ Note that for now you can only create a kops cluster in a single availability zo
|
||||||
kops create cluster --cloud=scaleway --name=mycluster.k8s.local --zones=fr-par-1 --yes
|
kops create cluster --cloud=scaleway --name=mycluster.k8s.local --zones=fr-par-1 --yes
|
||||||
# This creates a cluster with no DNS in zone nl-ams-2
|
# This creates a cluster with no DNS in zone nl-ams-2
|
||||||
kops create cluster --cloud=scaleway --name=my.cluster --zones=nl-ams-2 --yes
|
kops create cluster --cloud=scaleway --name=my.cluster --zones=nl-ams-2 --yes
|
||||||
|
# This creates a cluster with the Scaleway DNS (on a domain name that you own and have registered with Scaleway) in zone pl-waw-1
|
||||||
|
kops create cluster --cloud=scaleway --name=mycluster.mydomain.com --zones=pl-waw-1 --yes
|
||||||
```
|
```
|
||||||
|
|
||||||
### Editing your cluster
|
### Editing your cluster
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ limitations under the License.
|
||||||
package scaleway
|
package scaleway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1"
|
||||||
"k8s.io/kops/pkg/resources"
|
"k8s.io/kops/pkg/resources"
|
||||||
"k8s.io/kops/upup/pkg/fi"
|
"k8s.io/kops/upup/pkg/fi"
|
||||||
"k8s.io/kops/upup/pkg/fi/cloudup/scaleway"
|
"k8s.io/kops/upup/pkg/fi/cloudup/scaleway"
|
||||||
|
|
@ -27,6 +30,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
resourceTypeDNSRecord = "dns-record"
|
||||||
resourceTypeLoadBalancer = "load-balancer"
|
resourceTypeLoadBalancer = "load-balancer"
|
||||||
resourceTypeServer = "server"
|
resourceTypeServer = "server"
|
||||||
resourceTypeSSHKey = "ssh-key"
|
resourceTypeSSHKey = "ssh-key"
|
||||||
|
|
@ -45,6 +49,9 @@ func ListResources(cloud scaleway.ScwCloud, clusterInfo resources.ClusterInfo) (
|
||||||
listSSHKeys,
|
listSSHKeys,
|
||||||
listVolumes,
|
listVolumes,
|
||||||
}
|
}
|
||||||
|
if !strings.HasSuffix(clusterName, ".k8s.local") && !clusterInfo.UsesNoneDNS {
|
||||||
|
listFunctions = append(listFunctions, listDNSRecords)
|
||||||
|
}
|
||||||
|
|
||||||
for _, fn := range listFunctions {
|
for _, fn := range listFunctions {
|
||||||
rt, err := fn(cloud, clusterName)
|
rt, err := fn(cloud, clusterName)
|
||||||
|
|
@ -59,6 +66,30 @@ func ListResources(cloud scaleway.ScwCloud, clusterInfo resources.ClusterInfo) (
|
||||||
return resourceTrackers, nil
|
return resourceTrackers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listDNSRecords(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) {
|
||||||
|
c := cloud.(scaleway.ScwCloud)
|
||||||
|
records, err := c.GetClusterDNSRecords(clusterName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceTrackers := []*resources.Resource(nil)
|
||||||
|
for _, record := range records {
|
||||||
|
resourceTracker := &resources.Resource{
|
||||||
|
Name: record.Name,
|
||||||
|
ID: record.ID,
|
||||||
|
Type: resourceTypeDNSRecord,
|
||||||
|
Deleter: func(cloud fi.Cloud, tracker *resources.Resource) error {
|
||||||
|
return deleteDNSRecord(cloud, tracker, clusterName)
|
||||||
|
},
|
||||||
|
Obj: record,
|
||||||
|
}
|
||||||
|
resourceTrackers = append(resourceTrackers, resourceTracker)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceTrackers, nil
|
||||||
|
}
|
||||||
|
|
||||||
func listLoadBalancers(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) {
|
func listLoadBalancers(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) {
|
||||||
c := cloud.(scaleway.ScwCloud)
|
c := cloud.(scaleway.ScwCloud)
|
||||||
lbs, err := c.GetClusterLoadBalancers(clusterName)
|
lbs, err := c.GetClusterLoadBalancers(clusterName)
|
||||||
|
|
@ -158,6 +189,13 @@ func listVolumes(cloud fi.Cloud, clusterName string) ([]*resources.Resource, err
|
||||||
return resourceTrackers, nil
|
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 deleteLoadBalancer(cloud fi.Cloud, tracker *resources.Resource) error {
|
func deleteLoadBalancer(cloud fi.Cloud, tracker *resources.Resource) error {
|
||||||
c := cloud.(scaleway.ScwCloud)
|
c := cloud.(scaleway.ScwCloud)
|
||||||
loadBalancer := tracker.Obj.(*lb.LB)
|
loadBalancer := tracker.Obj.(*lb.LB)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ spec:
|
||||||
version: 9.99.0
|
version: 9.99.0
|
||||||
- id: k8s-1.12
|
- id: k8s-1.12
|
||||||
manifest: dns-controller.addons.k8s.io/k8s-1.12.yaml
|
manifest: dns-controller.addons.k8s.io/k8s-1.12.yaml
|
||||||
manifestHash: 304cbbd52a3dbff42b8302023297ce34cae0da69ac0884e8cff0d9f1b5c74028
|
manifestHash: 4814c76684eae04bb46a11f7f2a9485aedc41200205b9216020dc7e8cdc73ded
|
||||||
name: dns-controller.addons.k8s.io
|
name: dns-controller.addons.k8s.io
|
||||||
selector:
|
selector:
|
||||||
k8s-addon: dns-controller.addons.k8s.io
|
k8s-addon: dns-controller.addons.k8s.io
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,9 @@ spec:
|
||||||
env:
|
env:
|
||||||
- name: KUBERNETES_SERVICE_HOST
|
- name: KUBERNETES_SERVICE_HOST
|
||||||
value: 127.0.0.1
|
value: 127.0.0.1
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: scaleway-secret
|
||||||
image: registry.k8s.io/kops/dns-controller:1.27.0-beta.1
|
image: registry.k8s.io/kops/dns-controller:1.27.0-beta.1
|
||||||
name: dns-controller
|
name: dns-controller
|
||||||
resources:
|
resources:
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,11 @@ spec:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: digitalocean
|
name: digitalocean
|
||||||
key: access-token
|
key: access-token
|
||||||
|
{{- end }}
|
||||||
|
{{- if eq GetCloudProvider "scaleway" }}
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: scaleway-secret
|
||||||
{{- end }}
|
{{- end }}
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1"
|
||||||
iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1"
|
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/instance/v1"
|
||||||
"github.com/scaleway/scaleway-sdk-go/api/lb/v1"
|
"github.com/scaleway/scaleway-sdk-go/api/lb/v1"
|
||||||
|
|
@ -29,6 +30,7 @@ import (
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
kopsv "k8s.io/kops"
|
kopsv "k8s.io/kops"
|
||||||
"k8s.io/kops/dnsprovider/pkg/dnsprovider"
|
"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/apis/kops"
|
||||||
"k8s.io/kops/pkg/cloudinstances"
|
"k8s.io/kops/pkg/cloudinstances"
|
||||||
"k8s.io/kops/upup/pkg/fi"
|
"k8s.io/kops/upup/pkg/fi"
|
||||||
|
|
@ -54,6 +56,7 @@ type ScwCloud interface {
|
||||||
Region() string
|
Region() string
|
||||||
Zone() string
|
Zone() string
|
||||||
|
|
||||||
|
DomainService() *domain.API
|
||||||
IamService() *iam.API
|
IamService() *iam.API
|
||||||
InstanceService() *instance.API
|
InstanceService() *instance.API
|
||||||
LBService() *lb.ZonedAPI
|
LBService() *lb.ZonedAPI
|
||||||
|
|
@ -67,11 +70,13 @@ type ScwCloud interface {
|
||||||
GetApiIngressStatus(cluster *kops.Cluster) ([]fi.ApiIngressStatus, error)
|
GetApiIngressStatus(cluster *kops.Cluster) ([]fi.ApiIngressStatus, error)
|
||||||
GetCloudGroups(cluster *kops.Cluster, instancegroups []*kops.InstanceGroup, warnUnmatched bool, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error)
|
GetCloudGroups(cluster *kops.Cluster, instancegroups []*kops.InstanceGroup, warnUnmatched bool, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error)
|
||||||
|
|
||||||
|
GetClusterDNSRecords(clusterName string) ([]*domain.Record, error)
|
||||||
GetClusterLoadBalancers(clusterName string) ([]*lb.LB, error)
|
GetClusterLoadBalancers(clusterName string) ([]*lb.LB, error)
|
||||||
GetClusterServers(clusterName string, instanceGroupName *string) ([]*instance.Server, error)
|
GetClusterServers(clusterName string, instanceGroupName *string) ([]*instance.Server, error)
|
||||||
GetClusterSSHKeys(clusterName string) ([]*iam.SSHKey, error)
|
GetClusterSSHKeys(clusterName string) ([]*iam.SSHKey, error)
|
||||||
GetClusterVolumes(clusterName string) ([]*instance.Volume, error)
|
GetClusterVolumes(clusterName string) ([]*instance.Volume, error)
|
||||||
|
|
||||||
|
DeleteDNSRecord(record *domain.Record, clusterName string) error
|
||||||
DeleteLoadBalancer(loadBalancer *lb.LB) error
|
DeleteLoadBalancer(loadBalancer *lb.LB) error
|
||||||
DeleteServer(server *instance.Server) error
|
DeleteServer(server *instance.Server) error
|
||||||
DeleteSSHKey(sshkey *iam.SSHKey) error
|
DeleteSSHKey(sshkey *iam.SSHKey) error
|
||||||
|
|
@ -86,8 +91,10 @@ type scwCloudImplementation struct {
|
||||||
client *scw.Client
|
client *scw.Client
|
||||||
region scw.Region
|
region scw.Region
|
||||||
zone scw.Zone
|
zone scw.Zone
|
||||||
|
dns dnsprovider.Interface
|
||||||
tags map[string]string
|
tags map[string]string
|
||||||
|
|
||||||
|
domainAPI *domain.API
|
||||||
iamAPI *iam.API
|
iamAPI *iam.API
|
||||||
instanceAPI *instance.API
|
instanceAPI *instance.API
|
||||||
lbAPI *lb.ZonedAPI
|
lbAPI *lb.ZonedAPI
|
||||||
|
|
@ -130,7 +137,9 @@ func NewScwCloud(tags map[string]string) (ScwCloud, error) {
|
||||||
client: scwClient,
|
client: scwClient,
|
||||||
region: region,
|
region: region,
|
||||||
zone: zone,
|
zone: zone,
|
||||||
|
dns: dns.NewProvider(domain.NewAPI(scwClient)),
|
||||||
tags: tags,
|
tags: tags,
|
||||||
|
domainAPI: domain.NewAPI(scwClient),
|
||||||
iamAPI: iam.NewAPI(scwClient),
|
iamAPI: iam.NewAPI(scwClient),
|
||||||
instanceAPI: instance.NewAPI(scwClient),
|
instanceAPI: instance.NewAPI(scwClient),
|
||||||
lbAPI: lb.NewZonedAPI(scwClient),
|
lbAPI: lb.NewZonedAPI(scwClient),
|
||||||
|
|
@ -147,8 +156,11 @@ func (s *scwCloudImplementation) ClusterName(tags []string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *scwCloudImplementation) DNS() (dnsprovider.Interface, error) {
|
func (s *scwCloudImplementation) DNS() (dnsprovider.Interface, error) {
|
||||||
klog.V(8).Infof("Scaleway DNS is not implemented yet")
|
provider, err := dnsprovider.GetDnsProvider(dns.ProviderName, nil)
|
||||||
return nil, fmt.Errorf("DNS is not implemented yet for Scaleway")
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error building DNS provider: %w", err)
|
||||||
|
}
|
||||||
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *scwCloudImplementation) ProviderID() kops.CloudProviderID {
|
func (s *scwCloudImplementation) ProviderID() kops.CloudProviderID {
|
||||||
|
|
@ -163,6 +175,10 @@ func (s *scwCloudImplementation) Zone() string {
|
||||||
return string(s.zone)
|
return string(s.zone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *scwCloudImplementation) DomainService() *domain.API {
|
||||||
|
return s.domainAPI
|
||||||
|
}
|
||||||
|
|
||||||
func (s *scwCloudImplementation) IamService() *iam.API {
|
func (s *scwCloudImplementation) IamService() *iam.API {
|
||||||
return s.iamAPI
|
return s.iamAPI
|
||||||
}
|
}
|
||||||
|
|
@ -373,6 +389,27 @@ func buildCloudGroup(ig *kops.InstanceGroup, sg []*instance.Server, nodeMap map[
|
||||||
return cloudInstanceGroup, nil
|
return cloudInstanceGroup, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *scwCloudImplementation) GetClusterDNSRecords(clusterName string) ([]*domain.Record, error) {
|
||||||
|
names := strings.SplitN(clusterName, ".", 2)
|
||||||
|
clusterNameShort := names[0]
|
||||||
|
domainName := names[1]
|
||||||
|
|
||||||
|
records, err := s.domainAPI.ListDNSZoneRecords(&domain.ListDNSZoneRecordsRequest{
|
||||||
|
DNSZone: domainName,
|
||||||
|
}, scw.WithAllPages())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listing cluster DNS records: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clusterDNSRecords := []*domain.Record(nil)
|
||||||
|
for _, record := range records.Records {
|
||||||
|
if strings.HasSuffix(record.Name, clusterNameShort) {
|
||||||
|
clusterDNSRecords = append(clusterDNSRecords, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clusterDNSRecords, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *scwCloudImplementation) GetClusterLoadBalancers(clusterName string) ([]*lb.LB, error) {
|
func (s *scwCloudImplementation) GetClusterLoadBalancers(clusterName string) ([]*lb.LB, error) {
|
||||||
loadBalancerName := "api." + clusterName
|
loadBalancerName := "api." + clusterName
|
||||||
lbs, err := s.lbAPI.ListLBs(&lb.ZonedAPIListLBsRequest{
|
lbs, err := s.lbAPI.ListLBs(&lb.ZonedAPIListLBsRequest{
|
||||||
|
|
@ -430,6 +467,29 @@ func (s *scwCloudImplementation) GetClusterVolumes(clusterName string) ([]*insta
|
||||||
return volumes.Volumes, nil
|
return volumes.Volumes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *scwCloudImplementation) DeleteDNSRecord(record *domain.Record, clusterName string) error {
|
||||||
|
domainName := strings.SplitN(clusterName, ".", 2)[1]
|
||||||
|
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) DeleteLoadBalancer(loadBalancer *lb.LB) error {
|
func (s *scwCloudImplementation) DeleteLoadBalancer(loadBalancer *lb.LB) error {
|
||||||
ipsToRelease := loadBalancer.IP
|
ipsToRelease := loadBalancer.IP
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -613,6 +613,8 @@ func (tf *TemplateFunctions) DNSControllerArgv() ([]string, error) {
|
||||||
argv = append(argv, "--dns=digitalocean")
|
argv = append(argv, "--dns=digitalocean")
|
||||||
case kops.CloudProviderOpenstack:
|
case kops.CloudProviderOpenstack:
|
||||||
argv = append(argv, "--dns=openstack-designate")
|
argv = append(argv, "--dns=openstack-designate")
|
||||||
|
case kops.CloudProviderScaleway:
|
||||||
|
argv = append(argv, "--dns=scaleway")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unhandled cloudprovider %q", cluster.Spec.GetCloudProvider())
|
return nil, fmt.Errorf("unhandled cloudprovider %q", cluster.Spec.GetCloudProvider())
|
||||||
|
|
|
||||||
3916
vendor/github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1/domain_sdk.go
generated
vendored
Normal file
3916
vendor/github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1/domain_sdk.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
81
vendor/github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1/domain_utils.go
generated
vendored
Normal file
81
vendor/github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1/domain_utils.go
generated
vendored
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -767,6 +767,7 @@ github.com/russross/blackfriday/v2
|
||||||
github.com/sahilm/fuzzy
|
github.com/sahilm/fuzzy
|
||||||
# github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17
|
# github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17
|
||||||
## explicit; go 1.17
|
## explicit; go 1.17
|
||||||
|
github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1
|
||||||
github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1
|
github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1
|
||||||
github.com/scaleway/scaleway-sdk-go/api/instance/v1
|
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/lb/v1
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue