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/google/clouddns"
|
||||
_ "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/protokube/pkg/gossip"
|
||||
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.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.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")
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
* Scaleway DNS (to create clusters with a custom domain name)
|
||||
* [Terraform](https://github.com/scaleway/terraform-provider-scaleway) support
|
||||
* Private network
|
||||
|
||||
### Next features to implement
|
||||
|
||||
* `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
|
||||
* BareMetal servers
|
||||
|
||||
## Requirements
|
||||
|
||||
|
|
@ -30,7 +29,7 @@
|
|||
### 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
|
||||
|
||||
* [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
|
||||
|
||||
|
|
@ -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
|
||||
# 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
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ limitations under the License.
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1"
|
||||
"k8s.io/kops/pkg/resources"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/upup/pkg/fi/cloudup/scaleway"
|
||||
|
|
@ -27,6 +30,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
resourceTypeDNSRecord = "dns-record"
|
||||
resourceTypeLoadBalancer = "load-balancer"
|
||||
resourceTypeServer = "server"
|
||||
resourceTypeSSHKey = "ssh-key"
|
||||
|
|
@ -45,6 +49,9 @@ func ListResources(cloud scaleway.ScwCloud, clusterInfo resources.ClusterInfo) (
|
|||
listSSHKeys,
|
||||
listVolumes,
|
||||
}
|
||||
if !strings.HasSuffix(clusterName, ".k8s.local") && !clusterInfo.UsesNoneDNS {
|
||||
listFunctions = append(listFunctions, listDNSRecords)
|
||||
}
|
||||
|
||||
for _, fn := range listFunctions {
|
||||
rt, err := fn(cloud, clusterName)
|
||||
|
|
@ -59,6 +66,30 @@ 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)
|
||||
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) {
|
||||
c := cloud.(scaleway.ScwCloud)
|
||||
lbs, err := c.GetClusterLoadBalancers(clusterName)
|
||||
|
|
@ -158,6 +189,13 @@ func listVolumes(cloud fi.Cloud, clusterName string) ([]*resources.Resource, err
|
|||
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 {
|
||||
c := cloud.(scaleway.ScwCloud)
|
||||
loadBalancer := tracker.Obj.(*lb.LB)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ spec:
|
|||
version: 9.99.0
|
||||
- id: k8s-1.12
|
||||
manifest: dns-controller.addons.k8s.io/k8s-1.12.yaml
|
||||
manifestHash: 304cbbd52a3dbff42b8302023297ce34cae0da69ac0884e8cff0d9f1b5c74028
|
||||
manifestHash: 4814c76684eae04bb46a11f7f2a9485aedc41200205b9216020dc7e8cdc73ded
|
||||
name: dns-controller.addons.k8s.io
|
||||
selector:
|
||||
k8s-addon: dns-controller.addons.k8s.io
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ spec:
|
|||
env:
|
||||
- name: KUBERNETES_SERVICE_HOST
|
||||
value: 127.0.0.1
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: scaleway-secret
|
||||
image: registry.k8s.io/kops/dns-controller:1.27.0-beta.1
|
||||
name: dns-controller
|
||||
resources:
|
||||
|
|
|
|||
|
|
@ -72,6 +72,11 @@ spec:
|
|||
secretKeyRef:
|
||||
name: digitalocean
|
||||
key: access-token
|
||||
{{- end }}
|
||||
{{- if eq GetCloudProvider "scaleway" }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: scaleway-secret
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"os"
|
||||
"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"
|
||||
|
|
@ -29,6 +30,7 @@ import (
|
|||
"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"
|
||||
|
|
@ -54,6 +56,7 @@ type ScwCloud interface {
|
|||
Region() string
|
||||
Zone() string
|
||||
|
||||
DomainService() *domain.API
|
||||
IamService() *iam.API
|
||||
InstanceService() *instance.API
|
||||
LBService() *lb.ZonedAPI
|
||||
|
|
@ -67,11 +70,13 @@ 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)
|
||||
|
||||
GetClusterDNSRecords(clusterName string) ([]*domain.Record, error)
|
||||
GetClusterLoadBalancers(clusterName string) ([]*lb.LB, error)
|
||||
GetClusterServers(clusterName string, instanceGroupName *string) ([]*instance.Server, error)
|
||||
GetClusterSSHKeys(clusterName string) ([]*iam.SSHKey, error)
|
||||
GetClusterVolumes(clusterName string) ([]*instance.Volume, error)
|
||||
|
||||
DeleteDNSRecord(record *domain.Record, clusterName string) error
|
||||
DeleteLoadBalancer(loadBalancer *lb.LB) error
|
||||
DeleteServer(server *instance.Server) error
|
||||
DeleteSSHKey(sshkey *iam.SSHKey) error
|
||||
|
|
@ -86,8 +91,10 @@ type scwCloudImplementation struct {
|
|||
client *scw.Client
|
||||
region scw.Region
|
||||
zone scw.Zone
|
||||
dns dnsprovider.Interface
|
||||
tags map[string]string
|
||||
|
||||
domainAPI *domain.API
|
||||
iamAPI *iam.API
|
||||
instanceAPI *instance.API
|
||||
lbAPI *lb.ZonedAPI
|
||||
|
|
@ -130,7 +137,9 @@ func NewScwCloud(tags map[string]string) (ScwCloud, error) {
|
|||
client: scwClient,
|
||||
region: region,
|
||||
zone: zone,
|
||||
dns: dns.NewProvider(domain.NewAPI(scwClient)),
|
||||
tags: tags,
|
||||
domainAPI: domain.NewAPI(scwClient),
|
||||
iamAPI: iam.NewAPI(scwClient),
|
||||
instanceAPI: instance.NewAPI(scwClient),
|
||||
lbAPI: lb.NewZonedAPI(scwClient),
|
||||
|
|
@ -147,8 +156,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 {
|
||||
|
|
@ -163,6 +175,10 @@ func (s *scwCloudImplementation) Zone() string {
|
|||
return string(s.zone)
|
||||
}
|
||||
|
||||
func (s *scwCloudImplementation) DomainService() *domain.API {
|
||||
return s.domainAPI
|
||||
}
|
||||
|
||||
func (s *scwCloudImplementation) IamService() *iam.API {
|
||||
return s.iamAPI
|
||||
}
|
||||
|
|
@ -373,6 +389,27 @@ func buildCloudGroup(ig *kops.InstanceGroup, sg []*instance.Server, nodeMap map[
|
|||
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) {
|
||||
loadBalancerName := "api." + clusterName
|
||||
lbs, err := s.lbAPI.ListLBs(&lb.ZonedAPIListLBsRequest{
|
||||
|
|
@ -430,6 +467,29 @@ func (s *scwCloudImplementation) GetClusterVolumes(clusterName string) ([]*insta
|
|||
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 {
|
||||
ipsToRelease := loadBalancer.IP
|
||||
|
||||
|
|
|
|||
|
|
@ -613,6 +613,8 @@ func (tf *TemplateFunctions) DNSControllerArgv() ([]string, error) {
|
|||
argv = append(argv, "--dns=digitalocean")
|
||||
case kops.CloudProviderOpenstack:
|
||||
argv = append(argv, "--dns=openstack-designate")
|
||||
case kops.CloudProviderScaleway:
|
||||
argv = append(argv, "--dns=scaleway")
|
||||
|
||||
default:
|
||||
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/scaleway/scaleway-sdk-go v1.0.0-beta.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/instance/v1
|
||||
github.com/scaleway/scaleway-sdk-go/api/lb/v1
|
||||
|
|
|
|||
Loading…
Reference in New Issue