From 75429cc504ee4d079538e268aeff0bf8abfd9b4a Mon Sep 17 00:00:00 2001 From: Edward Lynes Date: Mon, 23 Nov 2020 14:34:44 -0500 Subject: [PATCH 01/44] Refactor and clean up akamai provider refactor: remove dns api logic and use dns api library enhancement: add additional args for auth credential retieval cleanup: simplify, organize processing logic test: update automation and validate --- go.mod | 2 +- go.sum | 2 + main.go | 7 +- pkg/apis/externaldns/types.go | 14 +- pkg/apis/externaldns/types_test.go | 8 + pkg/apis/externaldns/validation/validation.go | 12 +- provider/akamai/akamai.go | 591 +++++++++++------- provider/akamai/akamai_test.go | 410 +++++++----- 8 files changed, 632 insertions(+), 414 deletions(-) diff --git a/go.mod b/go.mod index eb260eb76..06b632e7a 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/Azure/go-autorest/autorest/adal v0.9.5 github.com/Azure/go-autorest/autorest/azure/auth v0.5.3 github.com/Azure/go-autorest/autorest/to v0.4.0 - github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.11 + github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0 github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect github.com/alecthomas/colour v0.1.0 // indirect github.com/alecthomas/kingpin v2.2.5+incompatible diff --git a/go.sum b/go.sum index 48ee1381e..12e341d3b 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,8 @@ github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4Rq github.com/ahmetb/gen-crd-api-reference-docs v0.1.5/go.mod h1:P/XzJ+c2+khJKNKABcm2biRwk2QAuwbLf8DlXuaL7WM= github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.11 h1:QGjNHMwoPYxE5NpOAc8kpd2KTY293/oFk5BWdjkza+k= github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.11/go.mod h1:L+HB2uBoDgi3+r1pJEJcbGwyyHhd2QXaGsKLbDwtm8Q= +github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0 h1:FJF58TWBaQnGqTIcziIP5/z3TTqWUn8fh26z03oZE2c= +github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk= diff --git a/main.go b/main.go index 867de5da9..a3c18a2fd 100644 --- a/main.go +++ b/main.go @@ -151,7 +151,7 @@ func main() { var p provider.Provider switch cfg.Provider { case "akamai": - p = akamai.NewAkamaiProvider( + p, err = akamai.NewAkamaiProvider( akamai.AkamaiConfig{ DomainFilter: domainFilter, ZoneIDFilter: zoneIDFilter, @@ -159,9 +159,10 @@ func main() { ClientToken: cfg.AkamaiClientToken, ClientSecret: cfg.AkamaiClientSecret, AccessToken: cfg.AkamaiAccessToken, + EdgercPath: cfg.AkamaiEdgercPath, + EdgercSection: cfg.AkamaiEdgercSection, DryRun: cfg.DryRun, - }, - ) + }, nil) case "alibabacloud": p, err = alibabacloud.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun) case "aws": diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index c6f96ae1a..621818f36 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -88,6 +88,8 @@ type Config struct { AkamaiClientToken string AkamaiClientSecret string AkamaiAccessToken string + AkamaiEdgercPath string + AkamaiEdgercSection string InfobloxGridHost string InfobloxWapiPort int InfobloxWapiUsername string @@ -194,6 +196,8 @@ var defaultConfig = &Config{ AkamaiClientToken: "", AkamaiClientSecret: "", AkamaiAccessToken: "", + AkamaiEdgercSection: "", + AkamaiEdgercPath: "", InfobloxGridHost: "", InfobloxWapiPort: 443, InfobloxWapiUsername: "admin", @@ -353,10 +357,12 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("cloudflare-proxied", "When using the Cloudflare provider, specify if the proxy mode must be enabled (default: disabled)").BoolVar(&cfg.CloudflareProxied) app.Flag("cloudflare-zones-per-page", "When using the Cloudflare provider, specify how many zones per page listed, max. possible 50 (default: 50)").Default(strconv.Itoa(defaultConfig.CloudflareZonesPerPage)).IntVar(&cfg.CloudflareZonesPerPage) app.Flag("coredns-prefix", "When using the CoreDNS provider, specify the prefix name").Default(defaultConfig.CoreDNSPrefix).StringVar(&cfg.CoreDNSPrefix) - app.Flag("akamai-serviceconsumerdomain", "When using the Akamai provider, specify the base URL (required when --provider=akamai)").Default(defaultConfig.AkamaiServiceConsumerDomain).StringVar(&cfg.AkamaiServiceConsumerDomain) - app.Flag("akamai-client-token", "When using the Akamai provider, specify the client token (required when --provider=akamai)").Default(defaultConfig.AkamaiClientToken).StringVar(&cfg.AkamaiClientToken) - app.Flag("akamai-client-secret", "When using the Akamai provider, specify the client secret (required when --provider=akamai)").Default(defaultConfig.AkamaiClientSecret).StringVar(&cfg.AkamaiClientSecret) - app.Flag("akamai-access-token", "When using the Akamai provider, specify the access token (required when --provider=akamai)").Default(defaultConfig.AkamaiAccessToken).StringVar(&cfg.AkamaiAccessToken) + app.Flag("akamai-serviceconsumerdomain", "When using the Akamai provider, specify the base URL (required when --provider=akamai and edgerc-path not specified)").Default(defaultConfig.AkamaiServiceConsumerDomain).StringVar(&cfg.AkamaiServiceConsumerDomain) + app.Flag("akamai-client-token", "When using the Akamai provider, specify the client token (required when --provider=akamai and edgerc-path not specified)").Default(defaultConfig.AkamaiClientToken).StringVar(&cfg.AkamaiClientToken) + app.Flag("akamai-client-secret", "When using the Akamai provider, specify the client secret (required when --provider=akamai and edgerc-path not specified)").Default(defaultConfig.AkamaiClientSecret).StringVar(&cfg.AkamaiClientSecret) + app.Flag("akamai-access-token", "When using the Akamai provider, specify the access token (required when --provider=akamai and edgerc-path not specified)").Default(defaultConfig.AkamaiAccessToken).StringVar(&cfg.AkamaiAccessToken) + app.Flag("akamai-edgerc-path", "When using the Akamai provider, specify the .edgerc file path. Path must be reachable form invocation environment. (required when --provider=akamai and *-token, secret serviceconsumerdomain not specified)").Default(defaultConfig.AkamaiEdgercPath).StringVar(&cfg.AkamaiEdgercPath) + app.Flag("akamai-edgerc-section", "When using the Akamai provider, specify the .edgerc file path (Optional when edgerc-path is specified)").Default(defaultConfig.AkamaiEdgercSection).StringVar(&cfg.AkamaiEdgercSection) app.Flag("infoblox-grid-host", "When using the Infoblox provider, specify the Grid Manager host (required when --provider=infoblox)").Default(defaultConfig.InfobloxGridHost).StringVar(&cfg.InfobloxGridHost) app.Flag("infoblox-wapi-port", "When using the Infoblox provider, specify the WAPI port (default: 443)").Default(strconv.Itoa(defaultConfig.InfobloxWapiPort)).IntVar(&cfg.InfobloxWapiPort) app.Flag("infoblox-wapi-username", "When using the Infoblox provider, specify the WAPI username (default: admin)").Default(defaultConfig.InfobloxWapiUsername).StringVar(&cfg.InfobloxWapiUsername) diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index 02fd16ffd..6eec94302 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -66,6 +66,8 @@ var ( AkamaiClientToken: "", AkamaiClientSecret: "", AkamaiAccessToken: "", + AkamaiEdgercPath: "", + AkamaiEdgercSection: "", InfobloxGridHost: "", InfobloxWapiPort: 443, InfobloxWapiUsername: "admin", @@ -144,6 +146,8 @@ var ( AkamaiClientToken: "o184671d5307a388180fbf7f11dbdf46", AkamaiClientSecret: "o184671d5307a388180fbf7f11dbdf46", AkamaiAccessToken: "o184671d5307a388180fbf7f11dbdf46", + AkamaiEdgercPath: "/home/test/.edgerc", + AkamaiEdgercSection: "default", InfobloxGridHost: "127.0.0.1", InfobloxWapiPort: 8443, InfobloxWapiUsername: "infoblox", @@ -235,6 +239,8 @@ func TestParseFlags(t *testing.T) { "--akamai-client-token=o184671d5307a388180fbf7f11dbdf46", "--akamai-client-secret=o184671d5307a388180fbf7f11dbdf46", "--akamai-access-token=o184671d5307a388180fbf7f11dbdf46", + "--akamai-edgerc-path=/home/test/.edgerc", + "--akamai-edgerc-section=default", "--infoblox-grid-host=127.0.0.1", "--infoblox-wapi-port=8443", "--infoblox-wapi-username=infoblox", @@ -328,6 +334,8 @@ func TestParseFlags(t *testing.T) { "EXTERNAL_DNS_AKAMAI_CLIENT_TOKEN": "o184671d5307a388180fbf7f11dbdf46", "EXTERNAL_DNS_AKAMAI_CLIENT_SECRET": "o184671d5307a388180fbf7f11dbdf46", "EXTERNAL_DNS_AKAMAI_ACCESS_TOKEN": "o184671d5307a388180fbf7f11dbdf46", + "EXTERNAL_DNS_AKAMAI_EDGERC_PATH": "/home/test/.edgerc", + "EXTERNAL_DNS_AKAMAI_EDGERC_SECTION": "default", "EXTERNAL_DNS_INFOBLOX_GRID_HOST": "127.0.0.1", "EXTERNAL_DNS_INFOBLOX_WAPI_PORT": "8443", "EXTERNAL_DNS_INFOBLOX_WAPI_USERNAME": "infoblox", diff --git a/pkg/apis/externaldns/validation/validation.go b/pkg/apis/externaldns/validation/validation.go index 58371ed6a..517c0ea64 100644 --- a/pkg/apis/externaldns/validation/validation.go +++ b/pkg/apis/externaldns/validation/validation.go @@ -45,16 +45,20 @@ func ValidateConfig(cfg *externaldns.Config) error { // Akamai provider specific validations if cfg.Provider == "akamai" { - if cfg.AkamaiServiceConsumerDomain == "" { + edgerc := false + if cfg.AkamaiEdgercPath != "" { + edgerc = true + } + if cfg.AkamaiServiceConsumerDomain == "" && !edgerc { return errors.New("no Akamai ServiceConsumerDomain specified") } - if cfg.AkamaiClientToken == "" { + if cfg.AkamaiClientToken == "" && !edgerc { return errors.New("no Akamai client token specified") } - if cfg.AkamaiClientSecret == "" { + if cfg.AkamaiClientSecret == "" && !edgerc { return errors.New("no Akamai client secret specified") } - if cfg.AkamaiAccessToken == "" { + if cfg.AkamaiAccessToken == "" && !edgerc { return errors.New("no Akamai access token specified") } } diff --git a/provider/akamai/akamai.go b/provider/akamai/akamai.go index 15acd1ed1..61dee2eac 100644 --- a/provider/akamai/akamai.go +++ b/provider/akamai/akamai.go @@ -17,15 +17,13 @@ limitations under the License. package akamai import ( - "bytes" "context" - "encoding/json" "fmt" - "io" - "net/http" + "os" + "strconv" "strings" - c "github.com/akamai/AkamaiOPEN-edgegrid-golang/client-v1" + dns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2" "github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid" log "github.com/sirupsen/logrus" @@ -34,22 +32,23 @@ import ( "sigs.k8s.io/external-dns/provider" ) -type akamaiClient interface { - NewRequest(config edgegrid.Config, method, path string, body io.Reader) (*http.Request, error) - Do(config edgegrid.Config, req *http.Request) (*http.Response, error) +const ( + // Default Record TTL + edgeDNSRecordTTL = 600 + maxUint = ^uint(0) + maxInt = int(maxUint >> 1) +) + +// edgeDNSClient is a proxy interface of the Akamai edgegrid configdns-v2 package that can be stubbed for testing. +type AkamaiDNSService interface { + ListZones(queryArgs dns.ZoneListQueryArgs) (*dns.ZoneListResponse, error) + GetRecordsets(zone string, queryArgs dns.RecordsetQueryArgs) (*dns.RecordSetResponse, error) + GetRecord(zone string, name string, recordtype string) (*dns.RecordBody, error) + DeleteRecord(record *dns.RecordBody, zone string, recLock bool) error + UpdateRecord(record *dns.RecordBody, zone string, recLock bool) error + CreateRecordsets(recordsets *dns.Recordsets, zone string, recLock bool) error } -type akamaiOpenClient struct{} - -func (*akamaiOpenClient) NewRequest(config edgegrid.Config, method, path string, body io.Reader) (*http.Request, error) { - return c.NewRequest(config, method, path, body) -} - -func (*akamaiOpenClient) Do(config edgegrid.Config, req *http.Request) (*http.Response, error) { - return c.Do(config, req) -} - -// AkamaiConfig clarifies the method signature type AkamaiConfig struct { DomainFilter endpoint.DomainFilter ZoneIDFilter provider.ZoneIDFilter @@ -57,17 +56,25 @@ type AkamaiConfig struct { ClientToken string ClientSecret string AccessToken string + EdgercPath string + EdgercSection string + MaxBody int + AccountKey string DryRun bool } // AkamaiProvider implements the DNS provider for Akamai. type AkamaiProvider struct { provider.BaseProvider + // Edgedns zones to filter on domainFilter endpoint.DomainFilter + // Contract Ids to filter on zoneIDFilter provider.ZoneIDFilter - config edgegrid.Config - dryRun bool - client akamaiClient + // Edgegrid library configuration + config *edgegrid.Config + dryRun bool + // Defines client. Allows for mocking. + client AkamaiDNSService } type akamaiZones struct { @@ -79,84 +86,126 @@ type akamaiZone struct { Zone string `json:"zone"` } -type akamaiRecordsets struct { - Recordsets []akamaiRecord `json:"recordsets"` -} - -type akamaiRecord struct { - Name string `json:"name"` - Type string `json:"type"` - TTL int64 `json:"ttl"` - Rdata []interface{} `json:"rdata"` -} - // NewAkamaiProvider initializes a new Akamai DNS based Provider. -func NewAkamaiProvider(akamaiConfig AkamaiConfig) *AkamaiProvider { - edgeGridConfig := edgegrid.Config{ - Host: akamaiConfig.ServiceConsumerDomain, - ClientToken: akamaiConfig.ClientToken, - ClientSecret: akamaiConfig.ClientSecret, - AccessToken: akamaiConfig.AccessToken, - MaxBody: 1024, - HeaderToSign: []string{ - "X-External-DNS", - }, - Debug: false, +func NewAkamaiProvider(akamaiConfig AkamaiConfig, akaService AkamaiDNSService) (provider.Provider, error) { + var edgeGridConfig edgegrid.Config + + /* + log.Debugf("Host: %s", akamaiConfig.ServiceConsumerDomain) + log.Debugf("ClientToken: %s", akamaiConfig.ClientToken) + log.Debugf("ClientSecret: %s", akamaiConfig.ClientSecret) + log.Debugf("AccessToken: %s", akamaiConfig.AccessToken) + log.Debugf("EdgePath: %s", akamaiConfig.EdgercPath) + log.Debugf("EdgeSection: %s", akamaiConfig.EdgercSection) + */ + // environment overrides edgerc file but config needs to be complete + if akamaiConfig.ServiceConsumerDomain == "" || akamaiConfig.ClientToken == "" || akamaiConfig.ClientSecret == "" || akamaiConfig.AccessToken == "" { + // Kubernetes config incomplete or non existent. Can't mix and match. + // Look for Akamai environment or .edgerd creds + var err error + edgeGridConfig, err = edgegrid.Init(akamaiConfig.EdgercPath, akamaiConfig.EdgercSection) // use default .edgerc location and section + if err != nil { + log.Errorf("Edgegrid Init Failed") + return &AkamaiProvider{}, err // return empty provider for backward compatibility + } + edgeGridConfig.HeaderToSign = append(edgeGridConfig.HeaderToSign, "X-External-DNS") + } else { + // Use external-dns config + edgeGridConfig = edgegrid.Config{ + Host: akamaiConfig.ServiceConsumerDomain, + ClientToken: akamaiConfig.ClientToken, + ClientSecret: akamaiConfig.ClientSecret, + AccessToken: akamaiConfig.AccessToken, + MaxBody: 131072, // same default val as used by Edgegrid + HeaderToSign: []string{ + "X-External-DNS", + }, + Debug: false, + } + // Check for edgegrid overrides + if envval, ok := os.LookupEnv("AKAMAI_MAX_BODY"); ok { + if i, err := strconv.Atoi(envval); err == nil { + edgeGridConfig.MaxBody = i + log.Debugf("Edgegrid maxbody set to %s", envval) + } + } + if envval, ok := os.LookupEnv("AKAMAI_ACCOUNT_KEY"); ok { + edgeGridConfig.AccountKey = envval + log.Debugf("Edgegrid applying account key %s", envval) + } + if envval, ok := os.LookupEnv("AKAMAI_DEBUG"); ok { + if dbgval, err := strconv.ParseBool(envval); err == nil { + edgeGridConfig.Debug = dbgval + log.Debugf("Edgegrid debug set to %s", envval) + } + } } provider := &AkamaiProvider{ domainFilter: akamaiConfig.DomainFilter, zoneIDFilter: akamaiConfig.ZoneIDFilter, - config: edgeGridConfig, + config: &edgeGridConfig, dryRun: akamaiConfig.DryRun, - client: &akamaiOpenClient{}, } - return provider + if akaService != nil { + log.Debugf("Using STUB") + provider.client = akaService + } else { + provider.client = provider + } + + // Init library for direct endpoint calls + dns.Init(edgeGridConfig) + + return provider, nil } -func (p *AkamaiProvider) request(method, path string, body io.Reader) (*http.Response, error) { - req, err := p.client.NewRequest(p.config, method, fmt.Sprintf("https://%s/%s", p.config.Host, path), body) - if err != nil { - log.Errorf("Akamai client failed to prepare the request") - return nil, err - } - resp, err := p.client.Do(p.config, req) - - if err != nil { - log.Errorf("Akamai client failed to do the request") - return nil, err - } - if !c.IsSuccess(resp) { - return nil, c.NewAPIError(resp) - } - - return resp, err +func (p AkamaiProvider) ListZones(queryArgs dns.ZoneListQueryArgs) (*dns.ZoneListResponse, error) { + return dns.ListZones(queryArgs) } -//Look here for endpoint documentation -> https://developer.akamai.com/api/web_performance/fast_dns_zone_management/v2.html#getzones -func (p *AkamaiProvider) fetchZones() (zones akamaiZones, err error) { - log.Debugf("Trying to fetch zones from Akamai") - resp, err := p.request("GET", "config-dns/v2/zones?showAll=true&types=primary%2Csecondary", nil) +func (p AkamaiProvider) GetRecordsets(zone string, queryArgs dns.RecordsetQueryArgs) (*dns.RecordSetResponse, error) { + return dns.GetRecordsets(zone, queryArgs) +} + +func (p AkamaiProvider) CreateRecordsets(recordsets *dns.Recordsets, zone string, reclock bool) error { + return recordsets.Save(zone, reclock) +} + +func (p AkamaiProvider) GetRecord(zone string, name string, recordtype string) (*dns.RecordBody, error) { + return dns.GetRecord(zone, name, recordtype) +} + +func (p AkamaiProvider) DeleteRecord(record *dns.RecordBody, zone string, recLock bool) error { + return record.Delete(zone, recLock) +} + +func (p AkamaiProvider) UpdateRecord(record *dns.RecordBody, zone string, recLock bool) error { + return record.Update(zone, recLock) +} + +// Fetch zones using Edgegrid DNS v2 API +func (p AkamaiProvider) fetchZones() (akamaiZones, error) { + log.Debugf("Fetching Akamai Edge DNS zones") + filteredZones := akamaiZones{Zones: make([]akamaiZone, 0)} + queryArgs := dns.ZoneListQueryArgs{Types: "primary", ShowAll: true} + // filter based on contractIds + if len(p.zoneIDFilter.ZoneIDs) > 0 { + queryArgs.ContractIds = strings.Join(p.zoneIDFilter.ZoneIDs, ",") + } + resp, err := p.client.ListZones(queryArgs) // don't worry about paged results + if err != nil { log.Errorf("Failed to fetch zones from Akamai") - return zones, err + return filteredZones, err } - err = json.NewDecoder(resp.Body).Decode(&zones) - if err != nil { - log.Errorf("Could not decode json response from Akamai on zone request") - return zones, err - } - defer resp.Body.Close() - - filteredZones := akamaiZones{} - for _, zone := range zones.Zones { - if !p.zoneIDFilter.Match(zone.ContractID) { - log.Debugf("Skipping zone: '%s' with ZoneID: '%s', it does not match against ZoneID filters", zone.Zone, zone.ContractID) - continue + for _, zone := range resp.Zones { + //log.Debugf("Evaluating zone: %s", zone.Zone) + if p.domainFilter.Match(zone.Zone) || !p.domainFilter.IsConfigured() { + filteredZones.Zones = append(filteredZones.Zones, akamaiZone{ContractID: zone.ContractId, Zone: zone.Zone}) + log.Debugf("Fetched zone: '%s' (ZoneID: %s)", zone.Zone, zone.ContractId) } - filteredZones.Zones = append(filteredZones.Zones, akamaiZone{ContractID: zone.ContractID, Zone: zone.Zone}) - log.Debugf("Fetched zone: '%s' (ZoneID: %s)", zone.Zone, zone.ContractID) } lenFilteredZones := len(filteredZones.Zones) if lenFilteredZones == 0 { @@ -168,53 +217,45 @@ func (p *AkamaiProvider) fetchZones() (zones akamaiZones, err error) { return filteredZones, nil } -//Look here for endpoint documentation -> https://developer.akamai.com/api/web_performance/fast_dns_zone_management/v2.html#getzonerecordsets -func (p *AkamaiProvider) fetchRecordSet(zone string) (recordSet akamaiRecordsets, err error) { - log.Debugf("Trying to fetch endpoints for zone: '%s' from Akamai", zone) - resp, err := p.request("GET", "config-dns/v2/zones/"+zone+"/recordsets?showAll=true&types=A%2CTXT%2CCNAME", nil) - if err != nil { - log.Errorf("Failed to fetch records from Akamai for zone: '%s'", zone) - return recordSet, err - } - defer resp.Body.Close() - - err = json.NewDecoder(resp.Body).Decode(&recordSet) - if err != nil { - log.Errorf("Could not decode json response from Akamai for zone: '%s' on request", zone) - return recordSet, err - } - - return recordSet, nil -} - //Records returns the list of records in a given zone. -func (p *AkamaiProvider) Records(context.Context) (endpoints []*endpoint.Endpoint, err error) { - zones, err := p.fetchZones() +func (p AkamaiProvider) Records(context.Context) (endpoints []*endpoint.Endpoint, err error) { + log.Debugf("Entering Records function") + if p.config == nil { + log.Errorf("Akamai provider failed initialization!") + return endpoints, fmt.Errorf("edge dns provider is not initialized") + } + + zones, err := p.fetchZones() // returns a filtered set of zones if err != nil { - log.Warnf("No zones to fetch endpoints from!") + log.Warnf("Failed to identify target zones! Error: %s", err.Error()) return endpoints, err } for _, zone := range zones.Zones { - records, err := p.fetchRecordSet(zone.Zone) + recordsets, err := p.client.GetRecordsets(zone.Zone, dns.RecordsetQueryArgs{}) if err != nil { - log.Warnf("No recordsets could be fetched for zone: '%s'!", zone.Zone) + log.Errorf("Recordsets retrieval for zone: '%s' failed! %s", zone.Zone, err.Error()) continue } + if len(recordsets.Recordsets) == 0 { + log.Warnf("Zone %s contains no recordsets", zone.Zone) + } - for _, record := range records.Recordsets { - rdata := make([]string, len(record.Rdata)) - - for i, v := range record.Rdata { - rdata[i] = v.(string) - } - - if !p.domainFilter.Match(record.Name) { - log.Debugf("Skipping endpoint DNSName: '%s' RecordType: '%s', it does not match against Domain filters", record.Name, record.Type) + for _, recordset := range recordsets.Recordsets { + if !provider.SupportedRecordType(recordset.Type) { + log.Debugf("Skipping endpoint DNSName: '%s' RecordType: '%s'. Record type not supported.", recordset.Name, recordset.Type) continue } - - endpoints = append(endpoints, endpoint.NewEndpoint(record.Name, record.Type, rdata...)) - log.Debugf("Fetched endpoint DNSName: '%s' RecordType: '%s' Rdata: '%s')", record.Name, record.Type, rdata) + if !p.domainFilter.Match(recordset.Name) { + log.Debugf("Skipping endpoint. Record name %s doesn't match containing zone %s.", recordset.Name, zone) + continue + } + var temp interface{} = int64(recordset.TTL) + var ttl endpoint.TTL = endpoint.TTL(temp.(int64)) //endpoint.TTL) + endpoints = append(endpoints, endpoint.NewEndpointWithTTL(recordset.Name, + recordset.Type, + ttl, + trimTxtRdata(recordset.Rdata, recordset.Type)...)) + log.Debugf("Fetched endpoint DNSName: '%s' RecordType: '%s' Rdata: '%s')", recordset.Name, recordset.Type, recordset.Rdata) } } lenEndpoints := len(endpoints) @@ -222,161 +263,247 @@ func (p *AkamaiProvider) Records(context.Context) (endpoints []*endpoint.Endpoin log.Warnf("No endpoints could be fetched") } else { log.Debugf("Fetched '%d' endpoints from Akamai", lenEndpoints) + log.Debugf("Endpoints [%v]", endpoints) } return endpoints, nil } // ApplyChanges applies a given set of changes in a given zone. -func (p *AkamaiProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { +func (p AkamaiProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { + log.Debugf("Entering ApplyChanges") + + if p.config == nil { + log.Errorf("Akamai provider failed initialization!") + return fmt.Errorf("edge dns provider failed initialization") + } + zoneNameIDMapper := provider.ZoneIDName{} zones, err := p.fetchZones() if err != nil { - log.Warnf("No zones to fetch endpoints from!") - return nil + log.Errorf("Failed to fetch zones from Akamai") + return err } for _, z := range zones.Zones { zoneNameIDMapper[z.Zone] = z.Zone } + log.Debugf("Processing zones: [%v]", zoneNameIDMapper) - _, cf := p.createRecords(zoneNameIDMapper, changes.Create) - if !p.dryRun { - if len(cf) > 0 { - log.Warnf("Not all desired endpoints could be created, retrying next iteration") - for _, f := range cf { - log.Warnf("Not created was DNSName: '%s' RecordType: '%s'", f.DNSName, f.RecordType) + // Create recodsets + log.Debugf("Create Changes requested [%v]", changes.Create) + if err := p.createRecordsets(zoneNameIDMapper, changes.Create); err != nil { + return err + } + // Delete recordsets + log.Debugf("Delete Changes requested [%v]", changes.Delete) + if err := p.deleteRecordsets(zoneNameIDMapper, changes.Delete); err != nil { + return err + } + // Update recordsets + log.Debugf("Update Changes requested [%v]", changes.UpdateNew) + if err := p.updateNewRecordsets(zoneNameIDMapper, changes.UpdateNew); err != nil { + return err + } + // Check that all old endpoints were accounted for + revRecs := changes.Delete + revRecs = append(revRecs, changes.UpdateNew...) + for _, rec := range changes.UpdateOld { + found := false + for _, r := range revRecs { + if rec.DNSName == r.DNSName { + found = true + break } } - } - - _, df := p.deleteRecords(zoneNameIDMapper, changes.Delete) - if !p.dryRun { - if len(df) > 0 { - log.Warnf("Not all endpoints that require deletion could be deleted, retrying next iteration") - for _, f := range df { - log.Warnf("Not deleted was DNSName: '%s' RecordType: '%s'", f.DNSName, f.RecordType) - } - } - } - - _, uf := p.updateNewRecords(zoneNameIDMapper, changes.UpdateNew) - if !p.dryRun { - if len(uf) > 0 { - log.Warnf("Not all endpoints that require updating could be updated, retrying next iteration") - for _, f := range uf { - log.Warnf("Not updated was DNSName: '%s' RecordType: '%s'", f.DNSName, f.RecordType) - } - } - } - - for _, uold := range changes.UpdateOld { - if !p.dryRun { - log.Debugf("UpdateOld (ignored) for DNSName: '%s' RecordType: '%s'", uold.DNSName, uold.RecordType) + if !found { + log.Warnf("UpdateOld endpoint '%s' is not accounted for in UpdateNew|Delete endpoint list", rec.DNSName) } } return nil } -func (p *AkamaiProvider) newAkamaiRecord(dnsName, recordType string, targets ...string) *akamaiRecord { - cleanTargets := make([]interface{}, len(targets)) - for idx, target := range targets { - cleanTargets[idx] = strings.TrimSuffix(target, ".") - } - return &akamaiRecord{ +// Create DNS Recordset +func newAkamaiRecordset(dnsName, recordType string, ttl int, targets []string) dns.Recordset { + return dns.Recordset{ Name: strings.TrimSuffix(dnsName, "."), - Rdata: cleanTargets, + Rdata: targets, Type: recordType, - TTL: 300, + TTL: ttl, } } -func (p *AkamaiProvider) createRecords(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) (created []*endpoint.Endpoint, failed []*endpoint.Endpoint) { - for _, endpoint := range endpoints { - if !p.domainFilter.Match(endpoint.DNSName) { - log.Debugf("Skipping creation at Akamai of endpoint DNSName: '%s' RecordType: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType) +// cleanTargets preps recordset rdata if necessary for EdgeDNS +func cleanTargets(rtype string, targets ...string) []string { + log.Debugf("Targets to clean: [%v]", targets) + //var targets []string = tgts + if rtype == "CNAME" || rtype == "SRV" { + for idx, target := range targets { + targets[idx] = strings.TrimSuffix(target, ".") + } + } else if rtype == "TXT" { + for idx, target := range targets { + log.Debugf("TXT data to clean: [%s]", target) + // need to embed text data in quotes. Make sure not piling on + target = strings.Trim(target, "\"") + // bug in DNS API with embedded quotes. + if strings.Contains(target, "owner") && strings.Contains(target, "\"") { + target = strings.ReplaceAll(target, "\"", "`") + } + targets[idx] = "\"" + target + "\"" + } + } + log.Debugf("Clean targets: [%v]", targets) + + return targets +} + +// trimTxtRdata removes surrounding quotes for received TXT rdata +func trimTxtRdata(rdata []string, rtype string) []string { + if rtype == "TXT" { + for idx, d := range rdata { + //rdata[idx] = strings.Trim(d, "\"") + if strings.Contains(d, "`") { + rdata[idx] = strings.ReplaceAll(d, "`", "\"") + } + } + } + log.Debugf("Trimmed data: [%v]", rdata) + + return rdata +} + +func ttlAsInt(src endpoint.TTL) int { + var temp interface{} = int64(src) + var temp64 = temp.(int64) + var ttl int = edgeDNSRecordTTL // int + if temp64 > 0 && temp64 <= int64(maxInt) { + ttl = int(temp64) + } + + return ttl +} + +// Create Endpoint Recordsets +func (p AkamaiProvider) createRecordsets(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) error { + if len(endpoints) == 0 { + log.Info("No endpoints to create") + return nil + } + + endpointsByZone := edgeChangesByZone(zoneNameIDMapper, endpoints) + + // create all recordsets by zone + for zone, endpoints := range endpointsByZone { + recordsets := &dns.Recordsets{Recordsets: make([]dns.Recordset, 0)} + for _, endpoint := range endpoints { + newrec := newAkamaiRecordset(endpoint.DNSName, + endpoint.RecordType, + ttlAsInt(endpoint.RecordTTL), + cleanTargets(endpoint.RecordType, endpoint.Targets...)) + logfields := log.Fields{ + "record": newrec.Name, + "type": newrec.Type, + "ttl": newrec.TTL, + "target": fmt.Sprintf("%v", newrec.Rdata), + "zone": zone, + } + log.WithFields(logfields).Info("Creating recordsets") + recordsets.Recordsets = append(recordsets.Recordsets, newrec) + } + + if p.dryRun { continue } - if zoneName, _ := zoneNameIDMapper.FindZone(endpoint.DNSName); zoneName != "" { - akamaiRecord := p.newAkamaiRecord(endpoint.DNSName, endpoint.RecordType, endpoint.Targets...) - body, _ := json.MarshalIndent(akamaiRecord, "", " ") - - log.Infof("Create new Endpoint at Akamai FastDNS - Zone: '%s', DNSName: '%s', RecordType: '%s', Targets: '%+v'", zoneName, endpoint.DNSName, endpoint.RecordType, endpoint.Targets) - - if p.dryRun { - continue - } - _, err := p.request("POST", "config-dns/v2/zones/"+zoneName+"/names/"+endpoint.DNSName+"/types/"+endpoint.RecordType, bytes.NewReader(body)) - if err != nil { - log.Errorf("Failed to create Akamai endpoint DNSName: '%s' RecordType: '%s' for zone: '%s'", endpoint.DNSName, endpoint.RecordType, zoneName) - failed = append(failed, endpoint) - continue - } - created = append(created, endpoint) - } else { - log.Warnf("No matching zone for endpoint addition DNSName: '%s' RecordType: '%s'", endpoint.DNSName, endpoint.RecordType) - failed = append(failed, endpoint) + // Create recordsets all at once + err := p.client.CreateRecordsets(recordsets, zone, true) + if err != nil { + log.Errorf("Failed to create endpoints for DNS zone %s. Error: %s", zone, err.Error()) + return err } } - return created, failed + + return nil } -func (p *AkamaiProvider) deleteRecords(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) (deleted []*endpoint.Endpoint, failed []*endpoint.Endpoint) { +func (p AkamaiProvider) deleteRecordsets(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) error { for _, endpoint := range endpoints { - if !p.domainFilter.Match(endpoint.DNSName) { - log.Debugf("Skipping deletion at Akamai of endpoint: '%s' type: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType) + zoneName, _ := zoneNameIDMapper.FindZone(endpoint.DNSName) + if zoneName == "" { + log.Debugf("Skipping Akamai Edge DNS endpoint deletion: '%s' type: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType) continue } - if zoneName, _ := zoneNameIDMapper.FindZone(endpoint.DNSName); zoneName != "" { - log.Infof("Deletion at Akamai FastDNS - Zone: '%s', DNSName: '%s', RecordType: '%s', Targets: '%+v'", zoneName, endpoint.DNSName, endpoint.RecordType, endpoint.Targets) + log.Infof("Akamai Edge DNS recordset deletion- Zone: '%s', DNSName: '%s', RecordType: '%s', Targets: '%+v'", zoneName, endpoint.DNSName, endpoint.RecordType, endpoint.Targets) - if p.dryRun { - continue - } - - _, err := p.request("DELETE", "config-dns/v2/zones/"+zoneName+"/names/"+endpoint.DNSName+"/types/"+endpoint.RecordType, nil) - if err != nil { - log.Errorf("Failed to delete Akamai endpoint DNSName: '%s' for zone: '%s'", endpoint.DNSName, zoneName) - failed = append(failed, endpoint) - continue - } - deleted = append(deleted, endpoint) - } else { - log.Warnf("No matching zone for endpoint deletion DNSName: '%s' RecordType: '%s'", endpoint.DNSName, endpoint.RecordType) - failed = append(failed, endpoint) - } - } - return deleted, failed -} - -func (p *AkamaiProvider) updateNewRecords(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) (updated []*endpoint.Endpoint, failed []*endpoint.Endpoint) { - for _, endpoint := range endpoints { - if !p.domainFilter.Match(endpoint.DNSName) { - log.Debugf("Skipping update at Akamai of endpoint DNSName: '%s' RecordType: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType) + if p.dryRun { continue } - if zoneName, _ := zoneNameIDMapper.FindZone(endpoint.DNSName); zoneName != "" { - akamaiRecord := p.newAkamaiRecord(endpoint.DNSName, endpoint.RecordType, endpoint.Targets...) - body, _ := json.MarshalIndent(akamaiRecord, "", " ") - log.Infof("Updating endpoint at Akamai FastDNS - Zone: '%s', DNSName: '%s', RecordType: '%s', Targets: '%+v'", zoneName, endpoint.DNSName, endpoint.RecordType, endpoint.Targets) - - if p.dryRun { - continue + recName := strings.TrimSuffix(endpoint.DNSName, ".") + rec, err := p.client.GetRecord(zoneName, recName, endpoint.RecordType) + if err != nil { + // error not found? + if _, ok := err.(*dns.RecordError); !ok { + return fmt.Errorf("endpoint deletion. record validation failed. error: %s", err.Error()) } - - _, err := p.request("PUT", "config-dns/v2/zones/"+zoneName+"/names/"+endpoint.DNSName+"/types/"+endpoint.RecordType, bytes.NewReader(body)) - if err != nil { - log.Errorf("Failed to update Akamai endpoint DNSName: '%s' for zone: '%s'", endpoint.DNSName, zoneName) - failed = append(failed, endpoint) - continue - } - updated = append(updated, endpoint) - } else { - log.Warnf("No matching zone for endpoint update DNSName: '%s' RecordType: '%s'", endpoint.DNSName, endpoint.RecordType) - failed = append(failed, endpoint) + log.Infof("Endpoint deletion. Record doesn't exist. Name: %s, Type: %s", recName, endpoint.RecordType) + continue + } + if err := p.client.DeleteRecord(rec, zoneName, true); err != nil { + log.Errorf("edge dns recordset deletion failed. error: %s", err.Error()) + return err } } - return updated, failed + + return nil +} + +// Update endpoint recordsets +func (p AkamaiProvider) updateNewRecordsets(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) error { + for _, endpoint := range endpoints { + zoneName, _ := zoneNameIDMapper.FindZone(endpoint.DNSName) + if zoneName == "" { + log.Debugf("Skipping Akamai Edge DNS endpoint update: '%s' type: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType) + continue + } + log.Infof("Akamai Edge DNS recordset update - Zone: '%s', DNSName: '%s', RecordType: '%s', Targets: '%+v'", zoneName, endpoint.DNSName, endpoint.RecordType, endpoint.Targets) + + if p.dryRun { + continue + } + + recName := strings.TrimSuffix(endpoint.DNSName, ".") + rec, err := p.client.GetRecord(zoneName, recName, endpoint.RecordType) + if err != nil { + log.Errorf("Endpoint update. Record validation failed. Error: %s", err.Error()) + return err + } + rec.TTL = ttlAsInt(endpoint.RecordTTL) + rec.Target = cleanTargets(endpoint.RecordType, endpoint.Targets...) + if err := p.client.UpdateRecord(rec, zoneName, true); err != nil { + log.Errorf("Akamai Edge DNS recordset update failed. Error: %s", err.Error()) + return err + } + } + + return nil +} + +// edgeChangesByZone separates a multi-zone change into a single change per zone. +func edgeChangesByZone(zoneMap provider.ZoneIDName, endpoints []*endpoint.Endpoint) map[string][]*endpoint.Endpoint { + createsByZone := make(map[string][]*endpoint.Endpoint, len(zoneMap)) + for _, z := range zoneMap { + createsByZone[z] = make([]*endpoint.Endpoint, 0) + } + for _, ep := range endpoints { + zone, _ := zoneMap.FindZone(ep.DNSName) + if zone != "" { + createsByZone[zone] = append(createsByZone[zone], ep) + continue + } + log.Debugf("Skipping Akamai Edge DNS creation of endpoint: '%s' type: '%s', it does not match against Domain filters", ep.DNSName, ep.RecordType) + } + + return createsByZone } diff --git a/provider/akamai/akamai_test.go b/provider/akamai/akamai_test.go index 7d0bd7a5e..a865ec7d9 100644 --- a/provider/akamai/akamai_test.go +++ b/provider/akamai/akamai_test.go @@ -17,148 +17,208 @@ limitations under the License. package akamai import ( - "bytes" "context" "encoding/json" - "io" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" + log "github.com/sirupsen/logrus" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid" + dns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" "sigs.k8s.io/external-dns/provider" ) -type mockAkamaiClient struct { - mock.Mock +type edgednsStubData struct { + objType string // zone, record, recordsets + output []interface{} + updateRecords []interface{} + createRecords []interface{} } -func (m *mockAkamaiClient) NewRequest(config edgegrid.Config, met, p string, b io.Reader) (*http.Request, error) { - switch { - case met == "GET": - switch { - case strings.HasPrefix(p, "https:///config-dns/v2/zones?"): - b = bytes.NewReader([]byte("{\"zones\":[{\"contractId\":\"Test\",\"zone\":\"example.com\"},{\"contractId\":\"Exclude-Me\",\"zone\":\"exclude.me\"}]}")) - case strings.HasPrefix(p, "https:///config-dns/v2/zones/example.com/"): - b = bytes.NewReader([]byte("{\"recordsets\":[{\"name\":\"www.example.com\",\"type\":\"A\",\"ttl\":300,\"rdata\":[\"10.0.0.2\",\"10.0.0.3\"]},{\"name\":\"www.example.com\",\"type\":\"TXT\",\"ttl\":300,\"rdata\":[\"heritage=external-dns,external-dns/owner=default\"]}]}")) - case strings.HasPrefix(p, "https:///config-dns/v2/zones/exclude.me/"): - b = bytes.NewReader([]byte("{\"recordsets\":[{\"name\":\"www.exclude.me\",\"type\":\"A\",\"ttl\":300,\"rdata\":[\"192.168.0.1\",\"192.168.0.2\"]}]}")) - } - case met == "DELETE": - b = bytes.NewReader([]byte("{\"title\": \"Success\", \"status\": 200, \"detail\": \"Record deleted\", \"requestId\": \"4321\"}")) - case met == "ERROR": - b = bytes.NewReader([]byte("{\"status\": 404 }")) - } - req := httptest.NewRequest(met, p, b) - return req, nil +type edgednsStub struct { + stubData map[string]edgednsStubData } -func (m *mockAkamaiClient) Do(config edgegrid.Config, req *http.Request) (*http.Response, error) { - handler := func(w http.ResponseWriter, r *http.Request) (isError bool) { - b, _ := ioutil.ReadAll(r.Body) - io.WriteString(w, string(b)) - return string(b) == "{\"status\": 404 }" +func newStub() *edgednsStub { + return &edgednsStub{ + stubData: make(map[string]edgednsStubData), } - w := httptest.NewRecorder() - err := handler(w, req) - resp := w.Result() +} - if err == true { - resp.StatusCode = 400 +func createAkamaiStubProvider(stub *edgednsStub, domfilter endpoint.DomainFilter, idfilter provider.ZoneIDFilter) (*AkamaiProvider, error) { + + akamaiConfig := AkamaiConfig{ + DomainFilter: domfilter, + ZoneIDFilter: idfilter, + ServiceConsumerDomain: "testzone.com", + ClientToken: "test_token", + ClientSecret: "test_client_secret", + AccessToken: "test_access_token", } + prov, err := NewAkamaiProvider(akamaiConfig, stub) + aprov := prov.(*AkamaiProvider) + return aprov, err +} + +func (r *edgednsStub) createStubDataEntry(objtype string) { + + log.Debugf("Creating stub data entry") + if _, exists := r.stubData[objtype]; !exists { + r.stubData[objtype] = edgednsStubData{objType: objtype} + } + + return +} + +func (r *edgednsStub) setOutput(objtype string, output []interface{}) { + + log.Debugf("Setting output to %v", output) + r.createStubDataEntry(objtype) + stubdata := r.stubData[objtype] + stubdata.output = output + r.stubData[objtype] = stubdata + + return +} + +func (r *edgednsStub) setUpdateRecords(objtype string, records []interface{}) { + + log.Debugf("Setting updaterecords to %v", records) + r.createStubDataEntry(objtype) + stubdata := r.stubData[objtype] + stubdata.updateRecords = records + r.stubData[objtype] = stubdata + + return +} + +func (r *edgednsStub) setCreateRecords(objtype string, records []interface{}) { + + log.Debugf("Setting createrecords to %v", records) + r.createStubDataEntry(objtype) + stubdata := r.stubData[objtype] + stubdata.createRecords = records + r.stubData[objtype] = stubdata + + return +} + +func (r *edgednsStub) ListZones(queryArgs dns.ZoneListQueryArgs) (*dns.ZoneListResponse, error) { + + log.Debugf("Entering ListZones") + // Ignore Metadata` + resp := &dns.ZoneListResponse{} + zones := make([]*dns.ZoneResponse, 0) + for _, zname := range r.stubData["zone"].output { + log.Debugf("Processing output: %v", zname) + zn := &dns.ZoneResponse{Zone: zname.(string), ContractId: "contract"} + log.Debugf("Created Zone Object: %v", zn) + zones = append(zones, zn) + } + resp.Zones = zones + return resp, nil +} + +func (r *edgednsStub) GetRecordsets(zone string, queryArgs dns.RecordsetQueryArgs) (*dns.RecordSetResponse, error) { + + log.Debugf("Entering GetRecordsets") + // Ignore Metadata` + + resp := &dns.RecordSetResponse{} + sets := make([]dns.Recordset, 0) + for _, rec := range r.stubData["recordset"].output { + rset := rec.(dns.Recordset) + sets = append(sets, rset) + } + resp.Recordsets = sets + return resp, nil } -func TestRequestError(t *testing.T) { - config := AkamaiConfig{} +func (r *edgednsStub) CreateRecordsets(recordsets *dns.Recordsets, zone string, reclock bool) error { - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client - - m := "ERROR" - p := "" - b := "" - x, err := c.request(m, p, bytes.NewReader([]byte(b))) - assert.Nil(t, x) - assert.NotNil(t, err) + return nil } +func (r *edgednsStub) GetRecord(zone string, name string, record_type string) (*dns.RecordBody, error) { + + resp := &dns.RecordBody{} + + return resp, nil +} + +func (r *edgednsStub) DeleteRecord(record *dns.RecordBody, zone string, recLock bool) error { + + return nil +} + +func (r *edgednsStub) UpdateRecord(record *dns.RecordBody, zone string, recLock bool) error { + + return nil +} + +// Test FetchZones func TestFetchZonesZoneIDFilter(t *testing.T) { - config := AkamaiConfig{ - ZoneIDFilter: provider.NewZoneIDFilter([]string{"Test"}), - } - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client + stub := newStub() + domfilter := endpoint.DomainFilter{} + idfilter := provider.NewZoneIDFilter([]string{"Test"}) + c, err := createAkamaiStubProvider(stub, domfilter, idfilter) + assert.Nil(t, err) + stub.setOutput("zone", []interface{}{"test1.testzone.com", "test2.testzone.com"}) x, _ := c.fetchZones() y, _ := json.Marshal(x) if assert.NotNil(t, y) { - assert.Equal(t, "{\"zones\":[{\"contractId\":\"Test\",\"zone\":\"example.com\"}]}", string(y)) + assert.Equal(t, "{\"zones\":[{\"contractId\":\"contract\",\"zone\":\"test1.testzone.com\"},{\"contractId\":\"contract\",\"zone\":\"test2.testzone.com\"}]}", string(y)) } } +// func TestFetchZonesEmpty(t *testing.T) { - config := AkamaiConfig{ - DomainFilter: endpoint.NewDomainFilter([]string{"Nonexistent"}), - ZoneIDFilter: provider.NewZoneIDFilter([]string{"Nonexistent"}), - } - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client + stub := newStub() + domfilter := endpoint.NewDomainFilter([]string{"Nonexistent"}) + idfilter := provider.NewZoneIDFilter([]string{"Nonexistent"}) + c, err := createAkamaiStubProvider(stub, domfilter, idfilter) + assert.Nil(t, err) + stub.setOutput("zone", []interface{}{}) x, _ := c.fetchZones() y, _ := json.Marshal(x) if assert.NotNil(t, y) { - assert.Equal(t, "{\"zones\":null}", string(y)) - } -} - -func TestFetchRecordset1(t *testing.T) { - config := AkamaiConfig{} - - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client - - x, _ := c.fetchRecordSet("example.com") - y, _ := json.Marshal(x) - if assert.NotNil(t, y) { - assert.Equal(t, "{\"recordsets\":[{\"name\":\"www.example.com\",\"type\":\"A\",\"ttl\":300,\"rdata\":[\"10.0.0.2\",\"10.0.0.3\"]},{\"name\":\"www.example.com\",\"type\":\"TXT\",\"ttl\":300,\"rdata\":[\"heritage=external-dns,external-dns/owner=default\"]}]}", string(y)) - } -} - -func TestFetchRecordset2(t *testing.T) { - config := AkamaiConfig{} - - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client - - x, _ := c.fetchRecordSet("exclude.me") - y, _ := json.Marshal(x) - if assert.NotNil(t, y) { - assert.Equal(t, "{\"recordsets\":[{\"name\":\"www.exclude.me\",\"type\":\"A\",\"ttl\":300,\"rdata\":[\"192.168.0.1\",\"192.168.0.2\"]}]}", string(y)) + assert.Equal(t, "{\"zones\":[]}", string(y)) } } +// TestAkamaiRecords tests record endpoint func TestAkamaiRecords(t *testing.T) { - config := AkamaiConfig{} - - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client + stub := newStub() + domfilter := endpoint.DomainFilter{} + idfilter := provider.ZoneIDFilter{} + c, err := createAkamaiStubProvider(stub, domfilter, idfilter) + assert.Nil(t, err) + stub.setOutput("zone", []interface{}{"test1.testzone.com"}) + recordsets := make([]interface{}, 0) + recordsets = append(recordsets, dns.Recordset{ + Name: "www.example.com", + Type: endpoint.RecordTypeA, + Rdata: []string{"10.0.0.2", "10.0.0.3"}, + }) + recordsets = append(recordsets, dns.Recordset{ + Name: "www.example.com", + Type: endpoint.RecordTypeTXT, + Rdata: []string{"heritage=external-dns,external-dns/owner=default"}, + }) + recordsets = append(recordsets, dns.Recordset{ + Name: "www.exclude.me", + Type: endpoint.RecordTypeA, + Rdata: []string{"192.168.0.1", "192.168.0.2"}, + }) + stub.setOutput("recordset", recordsets) endpoints := make([]*endpoint.Endpoint, 0) endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3")) endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default")) @@ -170,29 +230,43 @@ func TestAkamaiRecords(t *testing.T) { } } +// func TestAkamaiRecordsEmpty(t *testing.T) { - config := AkamaiConfig{ - ZoneIDFilter: provider.NewZoneIDFilter([]string{"Nonexistent"}), - } - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client + stub := newStub() + domfilter := endpoint.DomainFilter{} + idfilter := provider.NewZoneIDFilter([]string{"Nonexistent"}) + c, err := createAkamaiStubProvider(stub, domfilter, idfilter) + assert.Nil(t, err) + stub.setOutput("zone", []interface{}{"test1.testzone.com"}) + recordsets := make([]interface{}, 0) + stub.setOutput("recordset", recordsets) x, _ := c.Records(context.Background()) assert.Nil(t, x) } +// func TestAkamaiRecordsFilters(t *testing.T) { - config := AkamaiConfig{ - DomainFilter: endpoint.NewDomainFilter([]string{"www.exclude.me"}), - ZoneIDFilter: provider.NewZoneIDFilter([]string{"Exclude-Me"}), - } - - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client + stub := newStub() + domfilter := endpoint.NewDomainFilter([]string{"www.exclude.me"}) + idfilter := provider.ZoneIDFilter{} + c, err := createAkamaiStubProvider(stub, domfilter, idfilter) + assert.Nil(t, err) + stub.setOutput("zone", []interface{}{"www.exclude.me"}) + recordsets := make([]interface{}, 0) + recordsets = append(recordsets, dns.Recordset{ + Name: "www.example.com", + Type: endpoint.RecordTypeA, + Rdata: []string{"10.0.0.2", "10.0.0.3"}, + }) + recordsets = append(recordsets, dns.Recordset{ + Name: "www.exclude.me", + Type: endpoint.RecordTypeA, + Rdata: []string{"192.168.0.1", "192.168.0.2"}, + }) + stub.setOutput("recordset", recordsets) endpoints := make([]*endpoint.Endpoint, 0) endpoints = append(endpoints, endpoint.NewEndpoint("www.exclude.me", endpoint.RecordTypeA, "192.168.0.1", "192.168.0.2")) @@ -202,32 +276,32 @@ func TestAkamaiRecordsFilters(t *testing.T) { } } +// TestCreateRecords tests create function +// (p AkamaiProvider) createRecordsets(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) error func TestCreateRecords(t *testing.T) { - config := AkamaiConfig{} - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client + stub := newStub() + domfilter := endpoint.DomainFilter{} + idfilter := provider.ZoneIDFilter{} + c, err := createAkamaiStubProvider(stub, domfilter, idfilter) + assert.Nil(t, err) zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"} endpoints := make([]*endpoint.Endpoint, 0) endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3")) endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default")) - x, _ := c.createRecords(zoneNameIDMapper, endpoints) - if assert.NotNil(t, x) { - assert.Equal(t, endpoints, x) - } + err = c.createRecordsets(zoneNameIDMapper, endpoints) + assert.Nil(t, err) } func TestCreateRecordsDomainFilter(t *testing.T) { - config := AkamaiConfig{ - DomainFilter: endpoint.NewDomainFilter([]string{"example.com"}), - } - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client + stub := newStub() + domfilter := endpoint.DomainFilter{} + idfilter := provider.ZoneIDFilter{} + c, err := createAkamaiStubProvider(stub, domfilter, idfilter) + assert.Nil(t, err) zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"} endpoints := make([]*endpoint.Endpoint, 0) @@ -235,38 +309,36 @@ func TestCreateRecordsDomainFilter(t *testing.T) { endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default")) exclude := append(endpoints, endpoint.NewEndpoint("www.exclude.me", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3")) - x, _ := c.createRecords(zoneNameIDMapper, exclude) - if assert.NotNil(t, x) { - assert.Equal(t, endpoints, x) - } + err = c.createRecordsets(zoneNameIDMapper, exclude) + assert.Nil(t, err) } +// TestDeleteRecords validate delete func TestDeleteRecords(t *testing.T) { - config := AkamaiConfig{} - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client + stub := newStub() + domfilter := endpoint.DomainFilter{} + idfilter := provider.ZoneIDFilter{} + c, err := createAkamaiStubProvider(stub, domfilter, idfilter) + assert.Nil(t, err) zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"} endpoints := make([]*endpoint.Endpoint, 0) endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3")) endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default")) - x, _ := c.deleteRecords(zoneNameIDMapper, endpoints) - if assert.NotNil(t, x) { - assert.Equal(t, endpoints, x) - } + err = c.deleteRecordsets(zoneNameIDMapper, endpoints) + assert.Nil(t, err) } +// func TestDeleteRecordsDomainFilter(t *testing.T) { - config := AkamaiConfig{ - DomainFilter: endpoint.NewDomainFilter([]string{"example.com"}), - } - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client + stub := newStub() + domfilter := endpoint.NewDomainFilter([]string{"example.com"}) + idfilter := provider.ZoneIDFilter{} + c, err := createAkamaiStubProvider(stub, domfilter, idfilter) + assert.Nil(t, err) zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"} endpoints := make([]*endpoint.Endpoint, 0) @@ -274,38 +346,36 @@ func TestDeleteRecordsDomainFilter(t *testing.T) { endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default")) exclude := append(endpoints, endpoint.NewEndpoint("www.exclude.me", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3")) - x, _ := c.deleteRecords(zoneNameIDMapper, exclude) - if assert.NotNil(t, x) { - assert.Equal(t, endpoints, x) - } + err = c.deleteRecordsets(zoneNameIDMapper, exclude) + assert.Nil(t, err) } +// Test record update func func TestUpdateRecords(t *testing.T) { - config := AkamaiConfig{} - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client + stub := newStub() + domfilter := endpoint.DomainFilter{} + idfilter := provider.ZoneIDFilter{} + c, err := createAkamaiStubProvider(stub, domfilter, idfilter) + assert.Nil(t, err) zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"} endpoints := make([]*endpoint.Endpoint, 0) endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3")) endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default")) - x, _ := c.updateNewRecords(zoneNameIDMapper, endpoints) - if assert.NotNil(t, x) { - assert.Equal(t, endpoints, x) - } + err = c.updateNewRecordsets(zoneNameIDMapper, endpoints) + assert.Nil(t, err) } +// func TestUpdateRecordsDomainFilter(t *testing.T) { - config := AkamaiConfig{ - DomainFilter: endpoint.NewDomainFilter([]string{"example.com"}), - } - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client + stub := newStub() + domfilter := endpoint.NewDomainFilter([]string{"example.com"}) + idfilter := provider.ZoneIDFilter{} + c, err := createAkamaiStubProvider(stub, domfilter, idfilter) + assert.Nil(t, err) zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"} endpoints := make([]*endpoint.Endpoint, 0) @@ -313,19 +383,19 @@ func TestUpdateRecordsDomainFilter(t *testing.T) { endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default")) exclude := append(endpoints, endpoint.NewEndpoint("www.exclude.me", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3")) - x, _ := c.updateNewRecords(zoneNameIDMapper, exclude) - if assert.NotNil(t, x) { - assert.Equal(t, endpoints, x) - } + err = c.updateNewRecordsets(zoneNameIDMapper, exclude) + assert.Nil(t, err) } func TestAkamaiApplyChanges(t *testing.T) { - config := AkamaiConfig{} - client := &mockAkamaiClient{} - c := NewAkamaiProvider(config) - c.client = client + stub := newStub() + domfilter := endpoint.NewDomainFilter([]string{"example.com"}) + idfilter := provider.ZoneIDFilter{} + c, err := createAkamaiStubProvider(stub, domfilter, idfilter) + assert.Nil(t, err) + stub.setOutput("zone", []interface{}{"example.com"}) changes := &plan.Changes{} changes.Create = []*endpoint.Endpoint{ {DNSName: "www.example.com", RecordType: "A", Targets: endpoint.Targets{"target"}, RecordTTL: 300}, From f33a90f88b4b12f80f1c27e9952e5fe1601692cc Mon Sep 17 00:00:00 2001 From: Edward Lynes Date: Mon, 23 Nov 2020 14:42:31 -0500 Subject: [PATCH 02/44] Update documentation update README.md to include akamai provider changes update CHANGEME.md to rename akamai-fastdns refs to akamai-edgedns update Akamai tutorial name and content for updated functionality and tested scenarios. --- CHANGELOG.md | 1 + README.md | 3 +- docs/tutorials/akamai-edgedns.md | 251 +++++++++++++++++++++++++++++++ docs/tutorials/akamai-fastdns.md | 192 ----------------------- 4 files changed, 254 insertions(+), 193 deletions(-) create mode 100644 docs/tutorials/akamai-edgedns.md delete mode 100644 docs/tutorials/akamai-fastdns.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c8517c51..3b52a37ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Fix NodePort with externaltrafficpolicy targets duplication @codearky - Update contributing section in README (#1760) @seanmalloy - Option to cache AWS zones list @bpineau +- Refactor, enhance and test Akamai provider and documentation (#1846) @edglynes ## v0.7.3 - 2020-08-05 diff --git a/README.md b/README.md index 16a90bb77..8b2864b1a 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ ExternalDNS' current release is `v0.7`. This version allows you to keep selected * [VinylDNS](https://www.vinyldns.io) * [OVH](https://www.ovh.com) * [Scaleway](https://www.scaleway.com) +* [Akamai Edge DNS](https://learn.akamai.com/en-us/products/cloud_security/edge_dns.html) From this release, ExternalDNS can become aware of the records it is managing (enabled via `--registry=txt`), therefore ExternalDNS can safely manage non-empty hosted zones. We strongly encourage you to use `v0.5` (or greater) with `--registry=txt` enabled and `--txt-owner-id` set to a unique value that doesn't change for the lifetime of your cluster. You might also want to run ExternalDNS in a dry run mode (`--dry-run` flag) to see the changes to be submitted to your DNS Provider API. @@ -77,6 +78,7 @@ The following table clarifies the current status of the providers according to t | Google Cloud DNS | Stable | | | AWS Route 53 | Stable | | | AWS Cloud Map | Beta | | +| Akamai Edge DNS | Beta | | | AzureDNS | Beta | | | CloudFlare | Beta | | | RcodeZero | Alpha | | @@ -96,7 +98,6 @@ The following table clarifies the current status of the providers according to t | TransIP | Alpha | | | VinylDNS | Alpha | | | RancherDNS | Alpha | | -| Akamai FastDNS | Alpha | | | OVH | Alpha | | | Scaleway DNS | Alpha | @Sh4d1 | | Vultr | Alpha | | diff --git a/docs/tutorials/akamai-edgedns.md b/docs/tutorials/akamai-edgedns.md new file mode 100644 index 000000000..5e5381b7d --- /dev/null +++ b/docs/tutorials/akamai-edgedns.md @@ -0,0 +1,251 @@ +# Setting up External-DNS for Services on Akamai Edge DNS + +## Prerequisites + +Akamai Edge DNS (formally known as Fast DNS) provider support was first released in External-DNS v0.5.18 + +### Zones + +External-DNS manages service endpoints in existing DNS zones. The Akamai provider does not add, remove or configure new zones in anyway. Edge DNS zones can be created and managed thru the [Akamai Control Center](https://control.akamai.com) or [Akamai DevOps Tools](https://developer.akamai.com/devops), [Akamai CLI](https://developer.akamai.com/cli) and [Akamai Terraform Provider](https://developer.akamai.com/tools/integrations/terraform) + +### Akamai Edge DNS Authentication + +The Akamai Edge DNS provider requires valid Akamai Edgegrid API authentication credentials to access zones and manage associated DNS records. + +Credentials can be provided to the provider either directly by key or indirectly via a file. The Akamai credential keys and mappings to the Akamai provider utilizing different presentation methods are: + +| Edgegrid Auth Key | External-DNS Cmd Line Key | Environment/ConfigMap Key | Description | +| ----------------- | ------------------------- | ------------------------- | ----------- | +| host | akamai-serviceconsumerdomain | EXTERNAL_DNS_AKAMAI_SERVICECONSUMERDOMAIN | Akamai Edgegrid API server | +| access_token | akamai-access-token | EXTERNAL_DNS_AKAMAI_ACCESS_TOKEN | Akamai Edgegrid API access token | +| client_token | akamai-client-token | EXTERNAL_DNS_AKAMAI_CLIENT_TOKEN |Akamai Edgegrid API client token | +| client-secret | akamai-client-secret | EXTERNAL_DNS_AKAMAI_CLIENT_SECRET |Akamai Edgegrid API client secret | + +In addition to specifying auth credentials individually, the credentials may be referenced indirectly by using the Akamai Edgegrid .edgerc file convention. + +| External-DNS Cmd Line | Environment/ConfigMap | Description | +| --------------------- | --------------------- | ----------- | +| akamai-edgerc-path | EXTERNAL_DNS_AKAMAI_EDGERC_PATH | Accessible path to Edgegrid credentials file, e.g /home/test/.edgerc | +| akamai-edgerc-section | EXTERNAL_DNS_AKAMAI_EDGERC_SECTION | Section in Edgegrid credentials file containing credentials | + +Note: akamai-edgerc-path and akamai-edgerc-section are present in External-DNS versions after v0.7.4 + +[Akamai API Authentication](https://developer.akamai.com/getting-started/edgegrid) provides an overview and further information pertaining to the generation of auth credentials for API base applications and tools. + +The following example defines and references a Kubernetes ConfigMap secret, applied by referencing the secret and its keys in the env section of the deployment. + + +## Deploy External-DNS + +An operational External-DNS deployment consists of an External-DNS container and service. The following sections demonstrate the ConfigMap objects that would make up an example functional external DNS kubernetes configuration utilizing NGINX as the exposed service. + +Connect your `kubectl` client to the cluster with which you want to test External-DNS, and then apply one of the following manifest files for deployment: + +### Manifest (for clusters without RBAC enabled) + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns +spec: + strategy: + type: Recreate + selector: + matchLabels: + app: external-dns + template: + metadata: + labels: + app: external-dns + spec: + containers: + - name: external-dns + image: k8s.gcr.io/external-dns/external-dns:v0.7.4 + args: + - --source=service # or ingress or both + - --provider=akamai + - --domain-filter=example.com + # zone-id-filter may be specified as well to filter on contract ID + - --registry=txt + - --txt-owner-id={{ owner-id-for-this-external-dns }} + env: + - name: EXTERNAL_DNS_AKAMAI_SERVICECONSUMERDOMAIN + valueFrom: + secretKeyRef: + name: external-dns + key: EXTERNAL_DNS_AKAMAI_SERVICECONSUMERDOMAIN + - name: EXTERNAL_DNS_AKAMAI_CLIENT_TOKEN + valueFrom: + secretKeyRef: + name: external-dns + key: EXTERNAL_DNS_AKAMAI_CLIENT_TOKEN + - name: EXTERNAL_DNS_AKAMAI_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: external-dns + key: EXTERNAL_DNS_AKAMAI_CLIENT_SECRET + - name: EXTERNAL_DNS_AKAMAI_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: external-dns + key: EXTERNAL_DNS_AKAMAI_ACCESS_TOKEN +``` + +### Manifest (for clusters with RBAC enabled) + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: external-dns +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: external-dns +rules: +- apiGroups: [""] + resources: ["services","endpoints","pods"] + verbs: ["get","watch","list"] +- apiGroups: ["extensions","networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get","watch","list"] +- apiGroups: [""] + resources: ["nodes"] + verbs: ["list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: external-dns-viewer +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: external-dns +subjects: +- kind: ServiceAccount + name: external-dns + namespace: default +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns +spec: + strategy: + type: Recreate + selector: + matchLabels: + app: external-dns + template: + metadata: + labels: + app: external-dns + spec: + containers: + - name: external-dns + image: k8s.gcr.io/external-dns/external-dns:v0.7.4 + args: + - --source=service # or ingress or both + - --provider=akamai + - --domain-filter=example.com + # zone-id-filter may be specified as well to filter on contract ID + - --registry=txt + - --txt-owner-id={{ owner-id-for-this-external-dns }} + env: + - name: EXTERNAL_DNS_AKAMAI_SERVICECONSUMERDOMAIN + valueFrom: + secretKeyRef: + name: external-dns + key: EXTERNAL_DNS_AKAMAI_SERVICECONSUMERDOMAIN + - name: EXTERNAL_DNS_AKAMAI_CLIENT_TOKEN + valueFrom: + secretKeyRef: + name: external-dns + key: EXTERNAL_DNS_AKAMAI_CLIENT_TOKEN + - name: EXTERNAL_DNS_AKAMAI_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: external-dns + key: EXTERNAL_DNS_AKAMAI_CLIENT_SECRET + - name: EXTERNAL_DNS_AKAMAI_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: external-dns + key: EXTERNAL_DNS_AKAMAI_ACCESS_TOKEN +``` + +Create the deployment for External-DNS: + +``` +$ kubectl create -f externaldns.yaml +``` + +## Deploying an Nginx Service + +Create a service file called 'nginx.yaml' with the following contents: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - image: nginx + name: nginx + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx + annotations: + external-dns.alpha.kubernetes.io/hostname: nginx.example.com + external-dns.alpha.kubernetes.io/ttl: "600" #optional +spec: + selector: + app: nginx + type: LoadBalancer + ports: + - protocol: TCP + port: 80 + targetPort: 80 +``` + +Create the deployment, service and ingress object: + +``` +$ kubectl create -f nginx.yaml +``` + +## Verify Akamai Edge DNS Records + +It is recommended to wait 3-5 minutes before validating the records to allow the record changes to propagate to all the Akamai name servers worldwide. + +The records can be validated using the [Akamai Control Center](http://control.akamai.com) or by executing a dig, nslookup or similar DNS command. + +## Cleanup + +Once you successfully configure and verify record management via External-DNS, you can delete the tutorial's example: + +``` +$ kubectl delete -f nginx.yaml +$ kubectl delete -f externaldns.yaml +``` + +## Additional Information + +* The Akamai provider allows the administrative user to filter zones by both name (domain-filter) and contract Id (zone-id-filter). The Edge DNS API will return a '500 Internal Error' if an invalid contract Id is provided. +* The provider will substitute any embedded quotes in TXT records with `` ` `` (back tick) when writing the records to the API. + diff --git a/docs/tutorials/akamai-fastdns.md b/docs/tutorials/akamai-fastdns.md deleted file mode 100644 index e4940b6b5..000000000 --- a/docs/tutorials/akamai-fastdns.md +++ /dev/null @@ -1,192 +0,0 @@ -# Setting up Akamai FastDNS - -## Prerequisites - -Akamai FastDNS provider support was added via [this PR](https://github.com/kubernetes-sigs/external-dns/pull/1384), thus you need to use a release where this pr is included. This should be at least v0.5.18 - -The Akamai FastDNS provider expects that your zones, you wish to add records to, already exists -and are configured correctly. It does not add, remove or configure new zones in anyway. - -To do this please refer to the [FastDNS documentation](https://learn.akamai.com/en-us/products/web_performance/fast_dns.html). - -Additional data you will have to provide: - -* Service Consumer Domain -* Access token -* Client token -* Client Secret - -Make these available to external DNS somehow. In the following example a secret is used by referencing the secret and its keys in the env section of the deployment. - -If you happen to have questions regarding authentication, please refer to the [API Client Authentication documentation](https://developer.akamai.com/legacy/introduction/Client_Auth.html) - -## Deployment - -Deploying external DNS for Akamai is actually nearly identical to deploying -it for other providers. This is what a sample `deployment.yaml` looks like: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: external-dns - labels: - app.kubernetes.io/name: external-dns - app.kubernetes.io/version: v0.6.0 -spec: - strategy: - type: Recreate - selector: - matchLabels: - app.kubernetes.io/name: external-dns - template: - metadata: - labels: - app.kubernetes.io/name: external-dns - app.kubernetes.io/version: v0.6.0 - spec: - # Only use if you're also using RBAC - # serviceAccountName: external-dns - containers: - - name: external-dns - image: k8s.gcr.io/external-dns/external-dns:v0.7.3 - args: - - --source=ingress # or service or both - - --provider=akamai - - --registry=txt - - --txt-owner-id={{ owner-id-for-this-external-dns }} - env: - - name: EXTERNAL_DNS_AKAMAI_SERVICECONSUMERDOMAIN - valueFrom: - secretKeyRef: - name: external-dns - key: EXTERNAL_DNS_AKAMAI_SERVICECONSUMERDOMAIN - - name: EXTERNAL_DNS_AKAMAI_CLIENT_TOKEN - valueFrom: - secretKeyRef: - name: external-dns - key: EXTERNAL_DNS_AKAMAI_CLIENT_TOKEN - - name: EXTERNAL_DNS_AKAMAI_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: external-dns - key: EXTERNAL_DNS_AKAMAI_CLIENT_SECRET - - name: EXTERNAL_DNS_AKAMAI_ACCESS_TOKEN - valueFrom: - secretKeyRef: - name: external-dns - key: EXTERNAL_DNS_AKAMAI_ACCESS_TOKEN -``` - -## RBAC - -If your cluster is RBAC enabled, you also need to setup the following, before you can run external-dns: - -```yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: external-dns - namespace: default ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: ClusterRole -metadata: - name: external-dns -rules: -- apiGroups: [""] - resources: ["services","endpoints","pods"] - verbs: ["get","watch","list"] -- apiGroups: ["extensions","networking.k8s.io"] - resources: ["ingresses"] - verbs: ["get","watch","list"] -- apiGroups: [""] - resources: ["nodes"] - verbs: ["list"] ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: ClusterRoleBinding -metadata: - name: external-dns-viewer -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: external-dns -subjects: -- kind: ServiceAccount - name: external-dns - namespace: default -``` -## Verify ExternalDNS works (Ingress example) - -Create an ingress resource manifest file. - -> For ingress objects ExternalDNS will create a DNS record based on the host specified for the ingress object. - -```yaml -apiVersion: networking.k8s.io/v1beta1 -kind: Ingress -metadata: - name: foo - annotations: - kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller. -spec: - rules: - - host: foo.bar.com - http: - paths: - - backend: - serviceName: foo - servicePort: 80 -``` - -## Verify ExternalDNS works (Service example) - -Create the following sample application to test that ExternalDNS works. - -> For services ExternalDNS will look for the annotation `external-dns.alpha.kubernetes.io/hostname` on the service and use the corresponding value. - -> If you want to give multiple names to service, you can set it to external-dns.alpha.kubernetes.io/hostname with a comma separator. - -```yaml -apiVersion: v1 -kind: Service -metadata: - name: nginx - annotations: - external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.my-org.com -spec: - type: LoadBalancer - ports: - - port: 80 - name: http - targetPort: 80 - selector: - app: nginx - ---- - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx -spec: - selector: - matchLabels: - app: nginx - template: - metadata: - labels: - app: nginx - spec: - containers: - - image: nginx - name: nginx - ports: - - containerPort: 80 - name: http -``` - - -**Important!**: Don't run dig, nslookup or similar immediately. You'll get hit by [negative DNS caching](https://tools.ietf.org/html/rfc2308), which is hard to flush. -Wait about 30s-1m (interval for external-dns to kick in) From 395879ebfbb12d301016387598bc6dea6a088133 Mon Sep 17 00:00:00 2001 From: Edward Lynes Date: Thu, 3 Dec 2020 13:24:55 -0500 Subject: [PATCH 03/44] Code review feedback updates - removed commented code - removed unnecessary provider checks in GetRecords and AddChanges - removed noisy debugf comments - updated validation keys check --- docs/tutorials/akamai-edgedns.md | 2 +- pkg/apis/externaldns/validation/validation.go | 12 +++------ provider/akamai/akamai.go | 26 +++---------------- provider/akamai/akamai_test.go | 4 --- 4 files changed, 9 insertions(+), 35 deletions(-) diff --git a/docs/tutorials/akamai-edgedns.md b/docs/tutorials/akamai-edgedns.md index 5e5381b7d..9e589f182 100644 --- a/docs/tutorials/akamai-edgedns.md +++ b/docs/tutorials/akamai-edgedns.md @@ -113,7 +113,7 @@ rules: verbs: ["get","watch","list"] - apiGroups: [""] resources: ["nodes"] - verbs: ["list"] + verbs: ["watch", "list"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/pkg/apis/externaldns/validation/validation.go b/pkg/apis/externaldns/validation/validation.go index 517c0ea64..3ec950311 100644 --- a/pkg/apis/externaldns/validation/validation.go +++ b/pkg/apis/externaldns/validation/validation.go @@ -45,20 +45,16 @@ func ValidateConfig(cfg *externaldns.Config) error { // Akamai provider specific validations if cfg.Provider == "akamai" { - edgerc := false - if cfg.AkamaiEdgercPath != "" { - edgerc = true - } - if cfg.AkamaiServiceConsumerDomain == "" && !edgerc { + if cfg.AkamaiServiceConsumerDomain == "" && cfg.AkamaiEdgercPath != "" { return errors.New("no Akamai ServiceConsumerDomain specified") } - if cfg.AkamaiClientToken == "" && !edgerc { + if cfg.AkamaiClientToken == "" && cfg.AkamaiEdgercPath != "" { return errors.New("no Akamai client token specified") } - if cfg.AkamaiClientSecret == "" && !edgerc { + if cfg.AkamaiClientSecret == "" && cfg.AkamaiEdgercPath != "" { return errors.New("no Akamai client secret specified") } - if cfg.AkamaiAccessToken == "" && !edgerc { + if cfg.AkamaiAccessToken == "" && cfg.AkamaiEdgercPath != "" { return errors.New("no Akamai access token specified") } } diff --git a/provider/akamai/akamai.go b/provider/akamai/akamai.go index 61dee2eac..45165f251 100644 --- a/provider/akamai/akamai.go +++ b/provider/akamai/akamai.go @@ -186,14 +186,13 @@ func (p AkamaiProvider) UpdateRecord(record *dns.RecordBody, zone string, recLoc // Fetch zones using Edgegrid DNS v2 API func (p AkamaiProvider) fetchZones() (akamaiZones, error) { - log.Debugf("Fetching Akamai Edge DNS zones") filteredZones := akamaiZones{Zones: make([]akamaiZone, 0)} queryArgs := dns.ZoneListQueryArgs{Types: "primary", ShowAll: true} // filter based on contractIds if len(p.zoneIDFilter.ZoneIDs) > 0 { queryArgs.ContractIds = strings.Join(p.zoneIDFilter.ZoneIDs, ",") } - resp, err := p.client.ListZones(queryArgs) // don't worry about paged results + resp, err := p.client.ListZones(queryArgs) // retrieve all primary zones filtered by contract ids if err != nil { log.Errorf("Failed to fetch zones from Akamai") @@ -201,7 +200,6 @@ func (p AkamaiProvider) fetchZones() (akamaiZones, error) { } for _, zone := range resp.Zones { - //log.Debugf("Evaluating zone: %s", zone.Zone) if p.domainFilter.Match(zone.Zone) || !p.domainFilter.IsConfigured() { filteredZones.Zones = append(filteredZones.Zones, akamaiZone{ContractID: zone.ContractId, Zone: zone.Zone}) log.Debugf("Fetched zone: '%s' (ZoneID: %s)", zone.Zone, zone.ContractId) @@ -219,12 +217,6 @@ func (p AkamaiProvider) fetchZones() (akamaiZones, error) { //Records returns the list of records in a given zone. func (p AkamaiProvider) Records(context.Context) (endpoints []*endpoint.Endpoint, err error) { - log.Debugf("Entering Records function") - if p.config == nil { - log.Errorf("Akamai provider failed initialization!") - return endpoints, fmt.Errorf("edge dns provider is not initialized") - } - zones, err := p.fetchZones() // returns a filtered set of zones if err != nil { log.Warnf("Failed to identify target zones! Error: %s", err.Error()) @@ -250,7 +242,7 @@ func (p AkamaiProvider) Records(context.Context) (endpoints []*endpoint.Endpoint continue } var temp interface{} = int64(recordset.TTL) - var ttl endpoint.TTL = endpoint.TTL(temp.(int64)) //endpoint.TTL) + var ttl endpoint.TTL = endpoint.TTL(temp.(int64)) endpoints = append(endpoints, endpoint.NewEndpointWithTTL(recordset.Name, recordset.Type, ttl, @@ -271,13 +263,6 @@ func (p AkamaiProvider) Records(context.Context) (endpoints []*endpoint.Endpoint // ApplyChanges applies a given set of changes in a given zone. func (p AkamaiProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { - log.Debugf("Entering ApplyChanges") - - if p.config == nil { - log.Errorf("Akamai provider failed initialization!") - return fmt.Errorf("edge dns provider failed initialization") - } - zoneNameIDMapper := provider.ZoneIDName{} zones, err := p.fetchZones() if err != nil { @@ -290,7 +275,7 @@ func (p AkamaiProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) } log.Debugf("Processing zones: [%v]", zoneNameIDMapper) - // Create recodsets + // Create recordsets log.Debugf("Create Changes requested [%v]", changes.Create) if err := p.createRecordsets(zoneNameIDMapper, changes.Create); err != nil { return err @@ -337,7 +322,6 @@ func newAkamaiRecordset(dnsName, recordType string, ttl int, targets []string) d // cleanTargets preps recordset rdata if necessary for EdgeDNS func cleanTargets(rtype string, targets ...string) []string { log.Debugf("Targets to clean: [%v]", targets) - //var targets []string = tgts if rtype == "CNAME" || rtype == "SRV" { for idx, target := range targets { targets[idx] = strings.TrimSuffix(target, ".") @@ -363,7 +347,6 @@ func cleanTargets(rtype string, targets ...string) []string { func trimTxtRdata(rdata []string, rtype string) []string { if rtype == "TXT" { for idx, d := range rdata { - //rdata[idx] = strings.Trim(d, "\"") if strings.Contains(d, "`") { rdata[idx] = strings.ReplaceAll(d, "`", "\"") } @@ -377,7 +360,7 @@ func trimTxtRdata(rdata []string, rtype string) []string { func ttlAsInt(src endpoint.TTL) int { var temp interface{} = int64(src) var temp64 = temp.(int64) - var ttl int = edgeDNSRecordTTL // int + var ttl int = edgeDNSRecordTTL if temp64 > 0 && temp64 <= int64(maxInt) { ttl = int(temp64) } @@ -443,7 +426,6 @@ func (p AkamaiProvider) deleteRecordsets(zoneNameIDMapper provider.ZoneIDName, e recName := strings.TrimSuffix(endpoint.DNSName, ".") rec, err := p.client.GetRecord(zoneName, recName, endpoint.RecordType) if err != nil { - // error not found? if _, ok := err.(*dns.RecordError); !ok { return fmt.Errorf("endpoint deletion. record validation failed. error: %s", err.Error()) } diff --git a/provider/akamai/akamai_test.go b/provider/akamai/akamai_test.go index a865ec7d9..0edc07872 100644 --- a/provider/akamai/akamai_test.go +++ b/provider/akamai/akamai_test.go @@ -125,7 +125,6 @@ func (r *edgednsStub) GetRecordsets(zone string, queryArgs dns.RecordsetQueryArg log.Debugf("Entering GetRecordsets") // Ignore Metadata` - resp := &dns.RecordSetResponse{} sets := make([]dns.Recordset, 0) for _, rec := range r.stubData["recordset"].output { @@ -176,7 +175,6 @@ func TestFetchZonesZoneIDFilter(t *testing.T) { } } -// func TestFetchZonesEmpty(t *testing.T) { stub := newStub() @@ -230,7 +228,6 @@ func TestAkamaiRecords(t *testing.T) { } } -// func TestAkamaiRecordsEmpty(t *testing.T) { stub := newStub() @@ -246,7 +243,6 @@ func TestAkamaiRecordsEmpty(t *testing.T) { assert.Nil(t, x) } -// func TestAkamaiRecordsFilters(t *testing.T) { stub := newStub() From 7d0ee0b146455c3722410ca16bb540d39fb0c2b4 Mon Sep 17 00:00:00 2001 From: Edward Lynes Date: Wed, 9 Dec 2020 13:01:23 -0500 Subject: [PATCH 04/44] change vers refs from v0.7.4 to v0.7.5 --- docs/tutorials/akamai-edgedns.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/akamai-edgedns.md b/docs/tutorials/akamai-edgedns.md index 9e589f182..c817aa137 100644 --- a/docs/tutorials/akamai-edgedns.md +++ b/docs/tutorials/akamai-edgedns.md @@ -28,7 +28,7 @@ In addition to specifying auth credentials individually, the credentials may be | akamai-edgerc-path | EXTERNAL_DNS_AKAMAI_EDGERC_PATH | Accessible path to Edgegrid credentials file, e.g /home/test/.edgerc | | akamai-edgerc-section | EXTERNAL_DNS_AKAMAI_EDGERC_SECTION | Section in Edgegrid credentials file containing credentials | -Note: akamai-edgerc-path and akamai-edgerc-section are present in External-DNS versions after v0.7.4 +Note: akamai-edgerc-path and akamai-edgerc-section are present in External-DNS versions after v0.7.5 [Akamai API Authentication](https://developer.akamai.com/getting-started/edgegrid) provides an overview and further information pertaining to the generation of auth credentials for API base applications and tools. @@ -61,7 +61,7 @@ spec: spec: containers: - name: external-dns - image: k8s.gcr.io/external-dns/external-dns:v0.7.4 + image: k8s.gcr.io/external-dns/external-dns:v0.7.5 args: - --source=service # or ingress or both - --provider=akamai @@ -145,7 +145,7 @@ spec: spec: containers: - name: external-dns - image: k8s.gcr.io/external-dns/external-dns:v0.7.4 + image: k8s.gcr.io/external-dns/external-dns:v0.7.5 args: - --source=service # or ingress or both - --provider=akamai From b714e9f7a3526d2fe288e927179bb0a3ca6562db Mon Sep 17 00:00:00 2001 From: Guangwen Feng Date: Thu, 7 Jan 2021 11:01:04 +0800 Subject: [PATCH 05/44] Fix typo in comment Signed-off-by: Guangwen Feng --- controller/controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/controller.go b/controller/controller.go index 1806904a6..1a87d335a 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -165,7 +165,7 @@ func (c *Controller) RunOnce(ctx context.Context) error { // MinInterval is used as window for batching events const MinInterval = 5 * time.Second -// RunOnceThrottled makes sure execution happens at most once per interval. +// ScheduleRunOnce makes sure execution happens at most once per interval. func (c *Controller) ScheduleRunOnce(now time.Time) { c.nextRunAtMux.Lock() defer c.nextRunAtMux.Unlock() From c851a7973e0030fd7f00fd8fa8b03cf0292362b7 Mon Sep 17 00:00:00 2001 From: Jonas-Taha El Sesiy Date: Fri, 8 Jan 2021 12:01:10 -0800 Subject: [PATCH 06/44] Refactor azure private dns auth Add common config to be shared by both azure and azure-private-dns providers Update tests & docs --- docs/tutorials/azure-private-dns.md | 61 +++++----- main.go | 2 +- provider/azure/azure.go | 147 +++++------------------- provider/azure/azure_private_dns.go | 86 +++++++------- provider/azure/azure_privatedns_test.go | 35 ------ provider/azure/common.go | 129 +++++++++++++++++++++ provider/azure/common_test.go | 67 +++++++++++ 7 files changed, 299 insertions(+), 228 deletions(-) create mode 100644 provider/azure/common.go create mode 100644 provider/azure/common_test.go diff --git a/docs/tutorials/azure-private-dns.md b/docs/tutorials/azure-private-dns.md index 425fb9807..4aa2a5197 100644 --- a/docs/tutorials/azure-private-dns.md +++ b/docs/tutorials/azure-private-dns.md @@ -1,12 +1,12 @@ # Set up ExternalDNS for Azure Private DNS -This tutorial describes how to set up ExternalDNS for managing records in Azure Private DNS. +This tutorial describes how to set up ExternalDNS for managing records in Azure Private DNS. It comprises of the following steps: 1) Install NGINX Ingress Controller 2) Provision Azure Private DNS 3) Configure service principal for managing the zone -4) Deploy ExternalDNS +4) Deploy ExternalDNS Everything will be deployed on Kubernetes. Therefore, please see the subsequent prerequisites. @@ -26,25 +26,27 @@ $ helm install stable/nginx-ingress \ --name nginx-ingress \ --set controller.publishService.enabled=true ``` - -The parameter `controller.publishService.enabled` needs to be set to `true.` + +The parameter `controller.publishService.enabled` needs to be set to `true.` It will make the ingress controller update the endpoint records of ingress-resources to contain the external-ip of the loadbalancer serving the ingress-controller. This is crucial as ExternalDNS reads those endpoints records when creating DNS-Records from ingress-resources. In the subsequent parameter we will make use of this. If you don't want to work with ingress-resources in your later use, you can leave the parameter out. Verify the correct propagation of the loadbalancer's ip by listing the ingresses. + ``` $ kubectl get ingress ``` + The address column should contain the ip for each ingress. ExternalDNS will pick up exactly this piece of information. + ``` NAME HOSTS ADDRESS PORTS AGE nginx1 sample1.aks.com 52.167.195.110 80 6d22h nginx2 sample2.aks.com 52.167.195.110 80 6d21h ``` - If you do not want to deploy the ingress controller with Helm, ensure to pass the following cmdline-flags to it through the mechanism of your choice: ``` @@ -144,6 +146,8 @@ This is per default done through the file `~/.kube/config`. For general background information on this see [kubernetes-docs](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/). Azure-CLI features functionality for automatically maintaining this file for AKS-Clusters. See [Azure-Docs](https://docs.microsoft.com/de-de/cli/azure/aks?view=azure-cli-latest#az-aks-get-credentials). +Follow the steps for [azure-dns provider](./azure.md#creating-configuration-file) to create a configuration file. + Then apply one of the following manifests depending on whether you use RBAC or not. The credentials of the service principal are provided to ExternalDNS as environment-variables. @@ -175,13 +179,14 @@ spec: - --provider=azure-private-dns - --azure-resource-group=externaldns - --azure-subscription-id= - env: - - name: AZURE_TENANT_ID - value: "" - - name: AZURE_CLIENT_ID - value: "" - - name: AZURE_CLIENT_SECRET - value: "" + volumeMounts: + - name: azure-config-file + mountPath: /etc/kubernetes + readOnly: true + volumes: + - name: azure-config-file + secret: + secretName: azure-config-file ``` ### Manifest (for clusters with RBAC enabled, cluster access) @@ -200,7 +205,7 @@ rules: resources: ["services","endpoints","pods"] verbs: ["get","watch","list"] - apiGroups: ["extensions","networking.k8s.io"] - resources: ["ingresses"] + resources: ["ingresses"] verbs: ["get","watch","list"] - apiGroups: [""] resources: ["nodes"] @@ -245,13 +250,14 @@ spec: - --provider=azure-private-dns - --azure-resource-group=externaldns - --azure-subscription-id= - env: - - name: AZURE_TENANT_ID - value: "" - - name: AZURE_CLIENT_ID - value: "" - - name: AZURE_CLIENT_SECRET - value: "" + volumeMounts: + - name: azure-config-file + mountPath: /etc/kubernetes + readOnly: true + volumes: + - name: azure-config-file + secret: + secretName: azure-config-file ``` ### Manifest (for clusters with RBAC enabled, namespace access) @@ -315,13 +321,14 @@ spec: - --provider=azure-private-dns - --azure-resource-group=externaldns - --azure-subscription-id= - env: - - name: AZURE_TENANT_ID - value: "" - - name: AZURE_CLIENT_ID - value: "" - - name: AZURE_CLIENT_SECRET - value: "" + volumeMounts: + - name: azure-config-file + mountPath: /etc/kubernetes + readOnly: true + volumes: + - name: azure-config-file + secret: + secretName: azure-config-file ``` Create the deployment for ExternalDNS: diff --git a/main.go b/main.go index 3b2cf7923..a12a67300 100644 --- a/main.go +++ b/main.go @@ -191,7 +191,7 @@ func main() { case "azure-dns", "azure": p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun) case "azure-private-dns": - p, err = azure.NewAzurePrivateDNSProvider(domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureSubscriptionID, cfg.DryRun) + p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun) case "vinyldns": p, err = vinyldns.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun) case "vultr": diff --git a/provider/azure/azure.go b/provider/azure/azure.go index 1c82b0136..455179d9c 100644 --- a/provider/azure/azure.go +++ b/provider/azure/azure.go @@ -19,17 +19,12 @@ package azure import ( "context" "fmt" - "io/ioutil" "strings" log "github.com/sirupsen/logrus" - yaml "gopkg.in/yaml.v2" - "github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2018-05-01/dns" "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" - "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/to" "sigs.k8s.io/external-dns/endpoint" @@ -41,18 +36,6 @@ const ( azureRecordTTL = 300 ) -type config struct { - Cloud string `json:"cloud" yaml:"cloud"` - TenantID string `json:"tenantId" yaml:"tenantId"` - SubscriptionID string `json:"subscriptionId" yaml:"subscriptionId"` - ResourceGroup string `json:"resourceGroup" yaml:"resourceGroup"` - Location string `json:"location" yaml:"location"` - ClientID string `json:"aadClientId" yaml:"aadClientId"` - ClientSecret string `json:"aadClientSecret" yaml:"aadClientSecret"` - UseManagedIdentityExtension bool `json:"useManagedIdentityExtension" yaml:"useManagedIdentityExtension"` - UserAssignedIdentityID string `json:"userAssignedIdentityID" yaml:"userAssignedIdentityID"` -} - // ZonesClient is an interface of dns.ZoneClient that can be stubbed for testing. type ZonesClient interface { ListByResourceGroupComplete(ctx context.Context, resourceGroupName string, top *int32) (result dns.ZoneListResultIterator, err error) @@ -82,46 +65,22 @@ type AzureProvider struct { // // Returns the provider or an error if a provider could not be created. func NewAzureProvider(configFile string, domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, resourceGroup string, userAssignedIdentityClientID string, dryRun bool) (*AzureProvider, error) { - contents, err := ioutil.ReadFile(configFile) - if err != nil { - return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err) - } - cfg := config{} - err = yaml.Unmarshal(contents, &cfg) + cfg, err := getConfig(configFile, resourceGroup, userAssignedIdentityClientID) if err != nil { return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err) } - // If a resource group was given, override what was present in the config file - if resourceGroup != "" { - cfg.ResourceGroup = resourceGroup - } - // If userAssignedIdentityClientID is provided explicitly, override existing one in config file - if userAssignedIdentityClientID != "" { - cfg.UserAssignedIdentityID = userAssignedIdentityClientID - } - - var environment azure.Environment - if cfg.Cloud == "" { - environment = azure.PublicCloud - } else { - environment, err = azure.EnvironmentFromName(cfg.Cloud) - if err != nil { - return nil, fmt.Errorf("invalid cloud value '%s': %v", cfg.Cloud, err) - } - } - - token, err := getAccessToken(cfg, environment) + token, err := getAccessToken(*cfg, cfg.Environment) if err != nil { return nil, fmt.Errorf("failed to get token: %v", err) } - zonesClient := dns.NewZonesClientWithBaseURI(environment.ResourceManagerEndpoint, cfg.SubscriptionID) + zonesClient := dns.NewZonesClientWithBaseURI(cfg.Environment.ResourceManagerEndpoint, cfg.SubscriptionID) zonesClient.Authorizer = autorest.NewBearerAuthorizer(token) - recordSetsClient := dns.NewRecordSetsClientWithBaseURI(environment.ResourceManagerEndpoint, cfg.SubscriptionID) + recordSetsClient := dns.NewRecordSetsClientWithBaseURI(cfg.Environment.ResourceManagerEndpoint, cfg.SubscriptionID) recordSetsClient.Authorizer = autorest.NewBearerAuthorizer(token) - provider := &AzureProvider{ + return &AzureProvider{ domainFilter: domainFilter, zoneNameFilter: zoneNameFilter, zoneIDFilter: zoneIDFilter, @@ -130,61 +89,7 @@ func NewAzureProvider(configFile string, domainFilter endpoint.DomainFilter, zon userAssignedIdentityClientID: cfg.UserAssignedIdentityID, zonesClient: zonesClient, recordSetsClient: recordSetsClient, - } - return provider, nil -} - -// getAccessToken retrieves Azure API access token. -func getAccessToken(cfg config, environment azure.Environment) (*adal.ServicePrincipalToken, error) { - // Try to retrieve token with service principal credentials. - // Try to use service principal first, some AKS clusters are in an intermediate state that `UseManagedIdentityExtension` is `true` - // and service principal exists. In this case, we still want to use service principal to authenticate. - if len(cfg.ClientID) > 0 && - len(cfg.ClientSecret) > 0 && - // due to some historical reason, for pure MSI cluster, - // they will use "msi" as placeholder in azure.json. - // In this case, we shouldn't try to use SPN to authenticate. - !strings.EqualFold(cfg.ClientID, "msi") && - !strings.EqualFold(cfg.ClientSecret, "msi") { - log.Info("Using client_id+client_secret to retrieve access token for Azure API.") - oauthConfig, err := adal.NewOAuthConfig(environment.ActiveDirectoryEndpoint, cfg.TenantID) - if err != nil { - return nil, fmt.Errorf("failed to retrieve OAuth config: %v", err) - } - - token, err := adal.NewServicePrincipalToken(*oauthConfig, cfg.ClientID, cfg.ClientSecret, environment.ResourceManagerEndpoint) - if err != nil { - return nil, fmt.Errorf("failed to create service principal token: %v", err) - } - return token, nil - } - - // Try to retrieve token with MSI. - if cfg.UseManagedIdentityExtension { - log.Info("Using managed identity extension to retrieve access token for Azure API.") - msiEndpoint, err := adal.GetMSIVMEndpoint() - if err != nil { - return nil, fmt.Errorf("failed to get the managed service identity endpoint: %v", err) - } - - if cfg.UserAssignedIdentityID != "" { - log.Infof("Resolving to user assigned identity, client id is %s.", cfg.UserAssignedIdentityID) - token, err := adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, environment.ServiceManagementEndpoint, cfg.UserAssignedIdentityID) - if err != nil { - return nil, fmt.Errorf("failed to create the managed service identity token: %v", err) - } - return token, nil - } - - log.Info("Resolving to system assigned identity.") - token, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, environment.ServiceManagementEndpoint) - if err != nil { - return nil, fmt.Errorf("failed to create the managed service identity token: %v", err) - } - return token, nil - } - - return nil, fmt.Errorf("no credentials provided for Azure API") + }, nil } // Records gets the current records. @@ -352,20 +257,20 @@ func (p *AzureProvider) mapChanges(zones []dns.Zone, changes *plan.Changes) (azu func (p *AzureProvider) deleteRecords(ctx context.Context, deleted azureChangeMap) { // Delete records first for zone, endpoints := range deleted { - for _, endpoint := range endpoints { - name := p.recordSetNameForZone(zone, endpoint) - if !p.domainFilter.Match(endpoint.DNSName) { - log.Debugf("Skipping deletion of record %s because it was filtered out by the specified --domain-filter", endpoint.DNSName) + for _, ep := range endpoints { + name := p.recordSetNameForZone(zone, ep) + if !p.domainFilter.Match(ep.DNSName) { + log.Debugf("Skipping deletion of record %s because it was filtered out by the specified --domain-filter", ep.DNSName) continue } if p.dryRun { - log.Infof("Would delete %s record named '%s' for Azure DNS zone '%s'.", endpoint.RecordType, name, zone) + log.Infof("Would delete %s record named '%s' for Azure DNS zone '%s'.", ep.RecordType, name, zone) } else { - log.Infof("Deleting %s record named '%s' for Azure DNS zone '%s'.", endpoint.RecordType, name, zone) - if _, err := p.recordSetsClient.Delete(ctx, p.resourceGroup, zone, name, dns.RecordType(endpoint.RecordType), ""); err != nil { + log.Infof("Deleting %s record named '%s' for Azure DNS zone '%s'.", ep.RecordType, name, zone) + if _, err := p.recordSetsClient.Delete(ctx, p.resourceGroup, zone, name, dns.RecordType(ep.RecordType), ""); err != nil { log.Errorf( "Failed to delete %s record named '%s' for Azure DNS zone '%s': %v", - endpoint.RecordType, + ep.RecordType, name, zone, err, @@ -378,18 +283,18 @@ func (p *AzureProvider) deleteRecords(ctx context.Context, deleted azureChangeMa func (p *AzureProvider) updateRecords(ctx context.Context, updated azureChangeMap) { for zone, endpoints := range updated { - for _, endpoint := range endpoints { - name := p.recordSetNameForZone(zone, endpoint) - if !p.domainFilter.Match(endpoint.DNSName) { - log.Debugf("Skipping update of record %s because it was filtered out by the specified --domain-filter", endpoint.DNSName) + for _, ep := range endpoints { + name := p.recordSetNameForZone(zone, ep) + if !p.domainFilter.Match(ep.DNSName) { + log.Debugf("Skipping update of record %s because it was filtered out by the specified --domain-filter", ep.DNSName) continue } if p.dryRun { log.Infof( "Would update %s record named '%s' to '%s' for Azure DNS zone '%s'.", - endpoint.RecordType, + ep.RecordType, name, - endpoint.Targets, + ep.Targets, zone, ) continue @@ -397,20 +302,20 @@ func (p *AzureProvider) updateRecords(ctx context.Context, updated azureChangeMa log.Infof( "Updating %s record named '%s' to '%s' for Azure DNS zone '%s'.", - endpoint.RecordType, + ep.RecordType, name, - endpoint.Targets, + ep.Targets, zone, ) - recordSet, err := p.newRecordSet(endpoint) + recordSet, err := p.newRecordSet(ep) if err == nil { _, err = p.recordSetsClient.CreateOrUpdate( ctx, p.resourceGroup, zone, name, - dns.RecordType(endpoint.RecordType), + dns.RecordType(ep.RecordType), recordSet, "", "", @@ -419,9 +324,9 @@ func (p *AzureProvider) updateRecords(ctx context.Context, updated azureChangeMa if err != nil { log.Errorf( "Failed to update %s record named '%s' to '%s' for DNS zone '%s': %v", - endpoint.RecordType, + ep.RecordType, name, - endpoint.Targets, + ep.Targets, zone, err, ) diff --git a/provider/azure/azure_private_dns.go b/provider/azure/azure_private_dns.go index 093e4c031..320def42a 100644 --- a/provider/azure/azure_private_dns.go +++ b/provider/azure/azure_private_dns.go @@ -21,9 +21,8 @@ import ( "fmt" "strings" - "github.com/Azure/azure-sdk-for-go/profiles/latest/privatedns/mgmt/privatedns" + "github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns" "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/azure/auth" "github.com/Azure/go-autorest/autorest/to" log "github.com/sirupsen/logrus" @@ -47,44 +46,43 @@ type PrivateRecordSetsClient interface { // AzurePrivateDNSProvider implements the DNS provider for Microsoft's Azure Private DNS service type AzurePrivateDNSProvider struct { provider.BaseProvider - domainFilter endpoint.DomainFilter - zoneIDFilter provider.ZoneIDFilter - dryRun bool - subscriptionID string - resourceGroup string - zonesClient PrivateZonesClient - recordSetsClient PrivateRecordSetsClient + domainFilter endpoint.DomainFilter + zoneIDFilter provider.ZoneIDFilter + dryRun bool + resourceGroup string + userAssignedIdentityClientID string + zonesClient PrivateZonesClient + recordSetsClient PrivateRecordSetsClient } // NewAzurePrivateDNSProvider creates a new Azure Private DNS provider. // // Returns the provider or an error if a provider could not be created. -func NewAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, resourceGroup string, subscriptionID string, dryRun bool) (*AzurePrivateDNSProvider, error) { - authorizer, err := auth.NewAuthorizerFromEnvironment() +func NewAzurePrivateDNSProvider(configFile string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, resourceGroup, userAssignedIdentityClientID string, dryRun bool) (*AzurePrivateDNSProvider, error) { + cfg, err := getConfig(configFile, resourceGroup, userAssignedIdentityClientID) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err) } - settings, err := auth.GetSettingsFromEnvironment() + token, err := getAccessToken(*cfg, cfg.Environment) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get token: %v", err) } - zonesClient := privatedns.NewPrivateZonesClientWithBaseURI(settings.Environment.ResourceManagerEndpoint, subscriptionID) - zonesClient.Authorizer = authorizer - recordSetsClient := privatedns.NewRecordSetsClientWithBaseURI(settings.Environment.ResourceManagerEndpoint, subscriptionID) - recordSetsClient.Authorizer = authorizer + zonesClient := privatedns.NewPrivateZonesClientWithBaseURI(cfg.Environment.ResourceManagerEndpoint, cfg.SubscriptionID) + zonesClient.Authorizer = autorest.NewBearerAuthorizer(token) + recordSetsClient := privatedns.NewRecordSetsClientWithBaseURI(cfg.Environment.ResourceManagerEndpoint, cfg.SubscriptionID) + recordSetsClient.Authorizer = autorest.NewBearerAuthorizer(token) - provider := &AzurePrivateDNSProvider{ - domainFilter: domainFilter, - zoneIDFilter: zoneIDFilter, - dryRun: dryRun, - subscriptionID: subscriptionID, - resourceGroup: resourceGroup, - zonesClient: zonesClient, - recordSetsClient: recordSetsClient, - } - return provider, nil + return &AzurePrivateDNSProvider{ + domainFilter: domainFilter, + zoneIDFilter: zoneIDFilter, + dryRun: dryRun, + resourceGroup: cfg.ResourceGroup, + userAssignedIdentityClientID: cfg.UserAssignedIdentityID, + zonesClient: zonesClient, + recordSetsClient: recordSetsClient, + }, nil } // Records gets the current records. @@ -256,16 +254,16 @@ func (p *AzurePrivateDNSProvider) deleteRecords(ctx context.Context, deleted azu log.Debugf("Records to be deleted: %d", len(deleted)) // Delete records first for zone, endpoints := range deleted { - for _, endpoint := range endpoints { - name := p.recordSetNameForZone(zone, endpoint) + for _, ep := range endpoints { + name := p.recordSetNameForZone(zone, ep) if p.dryRun { - log.Infof("Would delete %s record named '%s' for Azure Private DNS zone '%s'.", endpoint.RecordType, name, zone) + log.Infof("Would delete %s record named '%s' for Azure Private DNS zone '%s'.", ep.RecordType, name, zone) } else { - log.Infof("Deleting %s record named '%s' for Azure Private DNS zone '%s'.", endpoint.RecordType, name, zone) - if _, err := p.recordSetsClient.Delete(ctx, p.resourceGroup, zone, privatedns.RecordType(endpoint.RecordType), name, ""); err != nil { + log.Infof("Deleting %s record named '%s' for Azure Private DNS zone '%s'.", ep.RecordType, name, zone) + if _, err := p.recordSetsClient.Delete(ctx, p.resourceGroup, zone, privatedns.RecordType(ep.RecordType), name, ""); err != nil { log.Errorf( "Failed to delete %s record named '%s' for Azure Private DNS zone '%s': %v", - endpoint.RecordType, + ep.RecordType, name, zone, err, @@ -279,14 +277,14 @@ func (p *AzurePrivateDNSProvider) deleteRecords(ctx context.Context, deleted azu func (p *AzurePrivateDNSProvider) updateRecords(ctx context.Context, updated azurePrivateDNSChangeMap) { log.Debugf("Records to be updated: %d", len(updated)) for zone, endpoints := range updated { - for _, endpoint := range endpoints { - name := p.recordSetNameForZone(zone, endpoint) + for _, ep := range endpoints { + name := p.recordSetNameForZone(zone, ep) if p.dryRun { log.Infof( "Would update %s record named '%s' to '%s' for Azure Private DNS zone '%s'.", - endpoint.RecordType, + ep.RecordType, name, - endpoint.Targets, + ep.Targets, zone, ) continue @@ -294,19 +292,19 @@ func (p *AzurePrivateDNSProvider) updateRecords(ctx context.Context, updated azu log.Infof( "Updating %s record named '%s' to '%s' for Azure Private DNS zone '%s'.", - endpoint.RecordType, + ep.RecordType, name, - endpoint.Targets, + ep.Targets, zone, ) - recordSet, err := p.newRecordSet(endpoint) + recordSet, err := p.newRecordSet(ep) if err == nil { _, err = p.recordSetsClient.CreateOrUpdate( ctx, p.resourceGroup, zone, - privatedns.RecordType(endpoint.RecordType), + privatedns.RecordType(ep.RecordType), name, recordSet, "", @@ -316,9 +314,9 @@ func (p *AzurePrivateDNSProvider) updateRecords(ctx context.Context, updated azu if err != nil { log.Errorf( "Failed to update %s record named '%s' to '%s' for Azure Private DNS zone '%s': %v", - endpoint.RecordType, + ep.RecordType, name, - endpoint.Targets, + ep.Targets, zone, err, ) diff --git a/provider/azure/azure_privatedns_test.go b/provider/azure/azure_privatedns_test.go index 1754493b2..6181524ab 100644 --- a/provider/azure/azure_privatedns_test.go +++ b/provider/azure/azure_privatedns_test.go @@ -18,16 +18,11 @@ package azure import ( "context" - "os" "testing" "github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns" "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/azure" - "github.com/Azure/go-autorest/autorest/azure/auth" "github.com/Azure/go-autorest/autorest/to" - "github.com/stretchr/testify/assert" - "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" "sigs.k8s.io/external-dns/provider" @@ -255,36 +250,6 @@ func newAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter } } -func validateAzurePrivateDNSClientsResourceManager(t *testing.T, environmentName string, expectedResourceManagerEndpoint string) { - err := os.Setenv(auth.EnvironmentName, environmentName) - if err != nil { - t.Fatal(err) - } - - azurePrivateDNSProvider, err := NewAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), "k8s", "sub", true) - - if err != nil { - t.Fatal(err) - } - - zonesClientBaseURI := azurePrivateDNSProvider.zonesClient.(privatedns.PrivateZonesClient).BaseURI - recordSetsClientBaseURI := azurePrivateDNSProvider.recordSetsClient.(privatedns.RecordSetsClient).BaseURI - - assert.Equal(t, zonesClientBaseURI, expectedResourceManagerEndpoint, "expected and actual resource manager endpoints don't match. expected: %s, got: %s", expectedResourceManagerEndpoint, zonesClientBaseURI) - assert.Equal(t, recordSetsClientBaseURI, expectedResourceManagerEndpoint, "expected and actual resource manager endpoints don't match. expected: %s, got: %s", expectedResourceManagerEndpoint, recordSetsClientBaseURI) -} - -func TestNewAzurePrivateDNSProvider(t *testing.T) { - // make sure to reset the environment variables at the end again - originalEnv := os.Getenv(auth.EnvironmentName) - defer os.Setenv(auth.EnvironmentName, originalEnv) - - validateAzurePrivateDNSClientsResourceManager(t, "", azure.PublicCloud.ResourceManagerEndpoint) - validateAzurePrivateDNSClientsResourceManager(t, "AZURECHINACLOUD", azure.ChinaCloud.ResourceManagerEndpoint) - validateAzurePrivateDNSClientsResourceManager(t, "AZUREGERMANCLOUD", azure.GermanCloud.ResourceManagerEndpoint) - validateAzurePrivateDNSClientsResourceManager(t, "AZUREUSGOVERNMENTCLOUD", azure.USGovernmentCloud.ResourceManagerEndpoint) -} - func TestAzurePrivateDNSRecord(t *testing.T) { provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s", &[]privatedns.PrivateZone{ diff --git a/provider/azure/common.go b/provider/azure/common.go new file mode 100644 index 000000000..02386c889 --- /dev/null +++ b/provider/azure/common.go @@ -0,0 +1,129 @@ +/* +Copyright 2017 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 azure + +import ( + "fmt" + "io/ioutil" + "strings" + + "github.com/Azure/go-autorest/autorest/adal" + "github.com/Azure/go-autorest/autorest/azure" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +// Config represents common config items for Azure DNS and Azure Private DNS +type config struct { + Cloud string `json:"cloud" yaml:"cloud"` + Environment azure.Environment `json:"-" yaml:"-"` + TenantID string `json:"tenantId" yaml:"tenantId"` + SubscriptionID string `json:"subscriptionId" yaml:"subscriptionId"` + ResourceGroup string `json:"resourceGroup" yaml:"resourceGroup"` + Location string `json:"location" yaml:"location"` + ClientID string `json:"aadClientId" yaml:"aadClientId"` + ClientSecret string `json:"aadClientSecret" yaml:"aadClientSecret"` + UseManagedIdentityExtension bool `json:"useManagedIdentityExtension" yaml:"useManagedIdentityExtension"` + UserAssignedIdentityID string `json:"userAssignedIdentityID" yaml:"userAssignedIdentityID"` +} + +func getConfig(configFile, resourceGroup, userAssignedIdentityClientID string) (*config, error) { + contents, err := ioutil.ReadFile(configFile) + if err != nil { + return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err) + } + cfg := &config{} + err = yaml.Unmarshal(contents, &cfg) + if err != nil { + return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err) + } + + // If a resource group was given, override what was present in the config file + if resourceGroup != "" { + cfg.ResourceGroup = resourceGroup + } + // If userAssignedIdentityClientID is provided explicitly, override existing one in config file + if userAssignedIdentityClientID != "" { + cfg.UserAssignedIdentityID = userAssignedIdentityClientID + } + + var environment azure.Environment + if cfg.Cloud == "" { + environment = azure.PublicCloud + } else { + environment, err = azure.EnvironmentFromName(cfg.Cloud) + if err != nil { + return nil, fmt.Errorf("invalid cloud value '%s': %v", cfg.Cloud, err) + } + } + cfg.Environment = environment + + return cfg, nil +} + +// getAccessToken retrieves Azure API access token. +func getAccessToken(cfg config, environment azure.Environment) (*adal.ServicePrincipalToken, error) { + // Try to retrieve token with service principal credentials. + // Try to use service principal first, some AKS clusters are in an intermediate state that `UseManagedIdentityExtension` is `true` + // and service principal exists. In this case, we still want to use service principal to authenticate. + if len(cfg.ClientID) > 0 && + len(cfg.ClientSecret) > 0 && + // due to some historical reason, for pure MSI cluster, + // they will use "msi" as placeholder in azure.json. + // In this case, we shouldn't try to use SPN to authenticate. + !strings.EqualFold(cfg.ClientID, "msi") && + !strings.EqualFold(cfg.ClientSecret, "msi") { + log.Info("Using client_id+client_secret to retrieve access token for Azure API.") + oauthConfig, err := adal.NewOAuthConfig(environment.ActiveDirectoryEndpoint, cfg.TenantID) + if err != nil { + return nil, fmt.Errorf("failed to retrieve OAuth config: %v", err) + } + + token, err := adal.NewServicePrincipalToken(*oauthConfig, cfg.ClientID, cfg.ClientSecret, environment.ResourceManagerEndpoint) + if err != nil { + return nil, fmt.Errorf("failed to create service principal token: %v", err) + } + return token, nil + } + + // Try to retrieve token with MSI. + if cfg.UseManagedIdentityExtension { + log.Info("Using managed identity extension to retrieve access token for Azure API.") + msiEndpoint, err := adal.GetMSIVMEndpoint() + if err != nil { + return nil, fmt.Errorf("failed to get the managed service identity endpoint: %v", err) + } + + if cfg.UserAssignedIdentityID != "" { + log.Infof("Resolving to user assigned identity, client id is %s.", cfg.UserAssignedIdentityID) + token, err := adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, environment.ServiceManagementEndpoint, cfg.UserAssignedIdentityID) + if err != nil { + return nil, fmt.Errorf("failed to create the managed service identity token: %v", err) + } + return token, nil + } + + log.Info("Resolving to system assigned identity.") + token, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, environment.ServiceManagementEndpoint) + if err != nil { + return nil, fmt.Errorf("failed to create the managed service identity token: %v", err) + } + return token, nil + } + + return nil, fmt.Errorf("no credentials provided for Azure API") +} diff --git a/provider/azure/common_test.go b/provider/azure/common_test.go new file mode 100644 index 000000000..bf591b0fc --- /dev/null +++ b/provider/azure/common_test.go @@ -0,0 +1,67 @@ +/* +Copyright 2017 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 azure + +import ( + "fmt" + "github.com/Azure/go-autorest/autorest/azure" + "io/ioutil" + "os" + "reflect" + "testing" +) + +func TestGetAzureEnvironmentConfig(t *testing.T) { + tmp, err := ioutil.TempFile("", "azureconf") + if err != nil { + t.Errorf("couldn't write temp file %v", err) + } + defer os.Remove(tmp.Name()) + + tests := map[string]struct { + cloud string + err error + }{ + "AzureChinaCloud": {"AzureChinaCloud", nil}, + "AzureGermanCloud": {"AzureGermanCloud", nil}, + "AzurePublicCloud": {"", nil}, + "AzureUSGovernment": {"AzureUSGovernmentCloud", nil}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + _, _ = tmp.Seek(0, 0) + _, _ = tmp.Write([]byte(fmt.Sprintf(`{"cloud": "%s"}`, test.cloud))) + got, err := getConfig(tmp.Name(), "", "") + if err != nil { + t.Errorf("got unexpected err %v", err) + } + + if test.cloud == "" { + test.cloud = "AzurePublicCloud" + } + want, err := azure.EnvironmentFromName(test.cloud) + if err != nil { + t.Errorf("couldn't get azure environment from provided name %v", err) + } + + if !reflect.DeepEqual(want, got.Environment) { + t.Errorf("got %v, want %v", got.Environment, want) + } + }) + } +} From 94a50ada464bfe7736180a634080a43e4727ec9c Mon Sep 17 00:00:00 2001 From: Jaromir Vanek Date: Fri, 8 Jan 2021 22:35:06 -0800 Subject: [PATCH 07/44] Correct format of SRV record for NodePort --- source/service.go | 15 +++++++++------ source/service_test.go | 14 +++++++------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/source/service.go b/source/service.go index ee2bef99d..18ab29041 100644 --- a/source/service.go +++ b/source/service.go @@ -628,14 +628,17 @@ func (sc *serviceSource) extractNodePortEndpoints(svc *v1.Service, nodeTargets e for _, port := range svc.Spec.Ports { if port.NodePort > 0 { + // following the RFC 2782, SRV record must have a following format + // _service._proto.name. TTL class SRV priority weight port + // see https://en.wikipedia.org/wiki/SRV_record + // build a target with a priority of 0, weight of 0, and pointing the given port on the given host target := fmt.Sprintf("0 50 %d %s", port.NodePort, hostname) - // figure out the portname - portName := port.Name - if portName == "" { - portName = fmt.Sprintf("%d", port.NodePort) - } + // take the service name from the K8s Service object + // it is safe to use since it is DNS compatible + // see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names + serviceName := svc.ObjectMeta.Name // figure out the protocol protocol := strings.ToLower(string(port.Protocol)) @@ -643,7 +646,7 @@ func (sc *serviceSource) extractNodePortEndpoints(svc *v1.Service, nodeTargets e protocol = "tcp" } - recordName := fmt.Sprintf("_%s._%s.%s", portName, protocol, hostname) + recordName := fmt.Sprintf("_%s._%s.%s", serviceName, protocol, hostname) var ep *endpoint.Endpoint if ttl.IsConfigured() { diff --git a/source/service_test.go b/source/service_test.go index b13084683..9349e7698 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -1642,7 +1642,7 @@ func TestNodePortServices(t *testing.T) { }, nil, []*endpoint.Endpoint{ - {DNSName: "_30192._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, + {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, false, @@ -1729,7 +1729,7 @@ func TestNodePortServices(t *testing.T) { map[string]string{}, nil, []*endpoint.Endpoint{ - {DNSName: "_30192._tcp.foo.bar.example.com", Targets: endpoint.Targets{"0 50 30192 foo.bar.example.com"}, RecordType: endpoint.RecordTypeSRV}, + {DNSName: "_foo._tcp.foo.bar.example.com", Targets: endpoint.Targets{"0 50 30192 foo.bar.example.com"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, false, @@ -1775,7 +1775,7 @@ func TestNodePortServices(t *testing.T) { }, nil, []*endpoint.Endpoint{ - {DNSName: "_30192._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, + {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA}, }, false, @@ -1819,7 +1819,7 @@ func TestNodePortServices(t *testing.T) { }, nil, []*endpoint.Endpoint{ - {DNSName: "_30192._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, + {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, false, @@ -1865,7 +1865,7 @@ func TestNodePortServices(t *testing.T) { }, nil, []*endpoint.Endpoint{ - {DNSName: "_30192._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, + {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, false, @@ -1912,7 +1912,7 @@ func TestNodePortServices(t *testing.T) { }, nil, []*endpoint.Endpoint{ - {DNSName: "_30192._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, + {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA}, }, false, @@ -1959,7 +1959,7 @@ func TestNodePortServices(t *testing.T) { }, nil, []*endpoint.Endpoint{ - {DNSName: "_30192._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, + {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, false, From 72f0be6eb7a288fe3124179b3a0e71b741599311 Mon Sep 17 00:00:00 2001 From: fboltz Date: Sun, 10 Jan 2021 19:23:02 +0100 Subject: [PATCH 08/44] FEAT: Add GoDaddy provider First commit --- main.go | 4 + pkg/apis/externaldns/types.go | 13 +- provider/godaddy/client.go | 299 +++++++++++++++ provider/godaddy/godaddy.go | 358 ++++++++++++++++++ provider/godaddy/godaddy_test.go | 630 +++++++++++++++++++++++++++++++ 5 files changed, 1303 insertions(+), 1 deletion(-) create mode 100644 provider/godaddy/client.go create mode 100644 provider/godaddy/godaddy.go create mode 100644 provider/godaddy/godaddy_test.go diff --git a/main.go b/main.go index 3b2cf7923..9fe13ba78 100644 --- a/main.go +++ b/main.go @@ -46,6 +46,7 @@ import ( "sigs.k8s.io/external-dns/provider/dnsimple" "sigs.k8s.io/external-dns/provider/dyn" "sigs.k8s.io/external-dns/provider/exoscale" + "sigs.k8s.io/external-dns/provider/godaddy" "sigs.k8s.io/external-dns/provider/google" "sigs.k8s.io/external-dns/provider/hetzner" "sigs.k8s.io/external-dns/provider/infoblox" @@ -297,6 +298,9 @@ func main() { p, err = transip.NewTransIPProvider(cfg.TransIPAccountName, cfg.TransIPPrivateKeyFile, domainFilter, cfg.DryRun) case "scaleway": p, err = scaleway.NewScalewayProvider(ctx, domainFilter, cfg.DryRun) + + case "godaddy": + p, err = godaddy.NewGoDaddyProvider(ctx, domainFilter, cfg.GoDaddyAPIKey, cfg.GoDaddySecretKey, cfg.GoDaddyProduction, cfg.DryRun) default: log.Fatalf("unknown dns provider: %s", cfg.Provider) } diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 01a09cdfa..b86c74350 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -148,6 +148,9 @@ type Config struct { TransIPAccountName string TransIPPrivateKeyFile string DigitalOceanAPIPageSize int + GoDaddyAPIKey string + GoDaddySecretKey string `secure:"yes"` + GoDaddyProduction bool } var defaultConfig = &Config{ @@ -250,6 +253,9 @@ var defaultConfig = &Config{ TransIPAccountName: "", TransIPPrivateKeyFile: "", DigitalOceanAPIPageSize: 50, + GoDaddyAPIKey: "", + GoDaddySecretKey: "", + GoDaddyProduction: true, } // NewConfig returns new Config object @@ -329,7 +335,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("service-type-filter", "The service types to take care about (default: all, expected: ClusterIP, NodePort, LoadBalancer or ExternalName)").StringsVar(&cfg.ServiceTypeFilter) // Flags related to providers - app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, hetzner, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "hetzner", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns") + app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, hetzner, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "hetzner", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns", "godaddy") app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter) app.Flag("exclude-domains", "Exclude subdomains (optional)").Default("").StringsVar(&cfg.ExcludeDomains) app.Flag("zone-name-filter", "Filter target zones by zone domain (For now, only AzureDNS provider is using this flag); specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneNameFilter) @@ -384,6 +390,11 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("ns1-min-ttl", "Minimal TTL (in seconds) for records. This value will be used if the provided TTL for a service/ingress is lower than this.").IntVar(&cfg.NS1MinTTLSeconds) app.Flag("digitalocean-api-page-size", "Configure the page size used when querying the DigitalOcean API.").Default(strconv.Itoa(defaultConfig.DigitalOceanAPIPageSize)).IntVar(&cfg.DigitalOceanAPIPageSize) + // GoDaddy flags + app.Flag("godaddy-api-key", "When using the GoDaddy provider, specify the API Key (required when --provider=godaddy)").Default(defaultConfig.GoDaddyAPIKey).StringVar(&cfg.GoDaddyAPIKey) + app.Flag("godaddy-api-secret", "When using the GoDaddy provider, specify the API secret (required when --provider=godaddy)").Default(defaultConfig.GoDaddySecretKey).StringVar(&cfg.GoDaddySecretKey) + app.Flag("godaddy-api-production", "When using the GoDaddy provider, specify if production or OTE use (required when --provider=godaddy)").Default(strconv.FormatBool(defaultConfig.GoDaddyProduction)).BoolVar(&cfg.GoDaddyProduction) + // Flags related to TLS communication app.Flag("tls-ca", "When using TLS communication, the path to the certificate authority to verify server communications (optionally specify --tls-client-cert for two-way TLS)").Default(defaultConfig.TLSCA).StringVar(&cfg.TLSCA) app.Flag("tls-client-cert", "When using TLS communication, the path to the certificate to present as a client (not required for TLS)").Default(defaultConfig.TLSClientCert).StringVar(&cfg.TLSClientCert) diff --git a/provider/godaddy/client.go b/provider/godaddy/client.go new file mode 100644 index 000000000..440c3ad27 --- /dev/null +++ b/provider/godaddy/client.go @@ -0,0 +1,299 @@ +/* +Copyright 2020 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 godaddy + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "time" + + "sigs.k8s.io/external-dns/pkg/apis/externaldns" +) + +// DefaultTimeout api requests after 180s +const DefaultTimeout = 180 * time.Second + +// Errors +var ( + ErrAPIDown = errors.New("go-vh: the GoDaddy API is down, it does't respond to /time anymore") +) + +// APIError error +type APIError struct { + Code string + Message string +} + +func (err *APIError) Error() string { + return fmt.Sprintf("Error %s: %q", err.Code, err.Message) +} + +// Logger is the interface that should be implemented for loggers that wish to +// log HTTP requests and HTTP responses. +type Logger interface { + // LogRequest logs an HTTP request. + LogRequest(*http.Request) + + // LogResponse logs an HTTP response. + LogResponse(*http.Response) +} + +// Client represents a client to call the GoDaddy API +type Client struct { + // APIKey holds the Application key + APIKey string + + // APISecret holds the Application secret key + APISecret string + + // API endpoint + APIEndPoint string + + // Client is the underlying HTTP client used to run the requests. It may be overloaded but a default one is instanciated in ``NewClient`` by default. + Client *http.Client + + // Logger is used to log HTTP requests and responses. + Logger Logger + + Timeout time.Duration +} + +// NewClient represents a new client to call the API +func NewClient(production bool, apiKey, apiSecret string) (*Client, error) { + var endpoint string + + if production { + endpoint = "https://api.godaddy.com" + } else { + endpoint = " https://api.ote-godaddy.com" + } + + client := Client{ + APIKey: apiKey, + APISecret: apiSecret, + APIEndPoint: endpoint, + Client: &http.Client{}, + Timeout: DefaultTimeout, + } + + // Get and check the configuration + if err := client.validate(); err != nil { + return nil, err + } + return &client, nil +} + +// +// Common request wrappers +// + +// Get is a wrapper for the GET method +func (c *Client) Get(url string, resType interface{}) error { + return c.CallAPI("GET", url, nil, resType, true) +} + +// Patch is a wrapper for the POST method +func (c *Client) Patch(url string, reqBody, resType interface{}) error { + return c.CallAPI("PATCH", url, reqBody, resType, true) +} + +// Post is a wrapper for the POST method +func (c *Client) Post(url string, reqBody, resType interface{}) error { + return c.CallAPI("POST", url, reqBody, resType, true) +} + +// Put is a wrapper for the PUT method +func (c *Client) Put(url string, reqBody, resType interface{}) error { + return c.CallAPI("PUT", url, reqBody, resType, true) +} + +// Delete is a wrapper for the DELETE method +func (c *Client) Delete(url string, resType interface{}) error { + return c.CallAPI("DELETE", url, nil, resType, true) +} + +// GetWithContext is a wrapper for the GET method +func (c *Client) GetWithContext(ctx context.Context, url string, resType interface{}) error { + return c.CallAPIWithContext(ctx, "GET", url, nil, resType, true) +} + +// PatchWithContext is a wrapper for the POST method +func (c *Client) PatchWithContext(ctx context.Context, url string, reqBody, resType interface{}) error { + return c.CallAPIWithContext(ctx, "PATCH", url, reqBody, resType, true) +} + +// PostWithContext is a wrapper for the POST method +func (c *Client) PostWithContext(ctx context.Context, url string, reqBody, resType interface{}) error { + return c.CallAPIWithContext(ctx, "POST", url, reqBody, resType, true) +} + +// PutWithContext is a wrapper for the PUT method +func (c *Client) PutWithContext(ctx context.Context, url string, reqBody, resType interface{}) error { + return c.CallAPIWithContext(ctx, "PUT", url, reqBody, resType, true) +} + +// DeleteWithContext is a wrapper for the DELETE method +func (c *Client) DeleteWithContext(ctx context.Context, url string, resType interface{}) error { + return c.CallAPIWithContext(ctx, "DELETE", url, nil, resType, true) +} + +// NewRequest returns a new HTTP request +func (c *Client) NewRequest(method, path string, reqBody interface{}, needAuth bool) (*http.Request, error) { + var body []byte + var err error + + if reqBody != nil { + body, err = json.Marshal(reqBody) + if err != nil { + return nil, err + } + } + + target := fmt.Sprintf("%s%s", c.APIEndPoint, path) + req, err := http.NewRequest(method, target, bytes.NewReader(body)) + if err != nil { + return nil, err + } + + // Inject headers + if body != nil { + req.Header.Set("Content-Type", "application/json;charset=utf-8") + } + req.Header.Set("Authorization", fmt.Sprintf("sso-key %s:%s", c.APIKey, c.APISecret)) + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "ExternalDNS/"+externaldns.Version) + + // Send the request with requested timeout + c.Client.Timeout = c.Timeout + + return req, nil +} + +// Do sends an HTTP request and returns an HTTP response +func (c *Client) Do(req *http.Request) (*http.Response, error) { + if c.Logger != nil { + c.Logger.LogRequest(req) + } + resp, err := c.Client.Do(req) + if err != nil { + return nil, err + } + if c.Logger != nil { + c.Logger.LogResponse(resp) + } + return resp, nil +} + +// CallAPI is the lowest level call helper. If needAuth is true, +// inject authentication headers and sign the request. +// +// Request signature is a sha1 hash on following fields, joined by '+': +// - applicationSecret (from Client instance) +// - consumerKey (from Client instance) +// - capitalized method (from arguments) +// - full request url, including any query string argument +// - full serialized request body +// - server current time (takes time delta into account) +// +// Call will automatically assemble the target url from the endpoint +// configured in the client instance and the path argument. If the reqBody +// argument is not nil, it will also serialize it as json and inject +// the required Content-Type header. +// +// If everything went fine, unmarshall response into resType and return nil +// otherwise, return the error +func (c *Client) CallAPI(method, path string, reqBody, resType interface{}, needAuth bool) error { + return c.CallAPIWithContext(context.Background(), method, path, reqBody, resType, needAuth) +} + +// CallAPIWithContext is the lowest level call helper. If needAuth is true, +// inject authentication headers and sign the request. +// +// Request signature is a sha1 hash on following fields, joined by '+': +// - applicationSecret (from Client instance) +// - consumerKey (from Client instance) +// - capitalized method (from arguments) +// - full request url, including any query string argument +// - full serialized request body +// - server current time (takes time delta into account) +// +// Context is used by http.Client to handle context cancelation +// +// Call will automatically assemble the target url from the endpoint +// configured in the client instance and the path argument. If the reqBody +// argument is not nil, it will also serialize it as json and inject +// the required Content-Type header. +// +// If everything went fine, unmarshall response into resType and return nil +// otherwise, return the error +func (c *Client) CallAPIWithContext(ctx context.Context, method, path string, reqBody, resType interface{}, needAuth bool) error { + req, err := c.NewRequest(method, path, reqBody, needAuth) + if err != nil { + return err + } + req = req.WithContext(ctx) + response, err := c.Do(req) + if err != nil { + return err + } + return c.UnmarshalResponse(response, resType) +} + +// UnmarshalResponse checks the response and unmarshals it into the response +// type if needed Helper function, called from CallAPI +func (c *Client) UnmarshalResponse(response *http.Response, resType interface{}) error { + // Read all the response body + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return err + } + + // < 200 && >= 300 : API error + if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices { + apiError := &APIError{ + Code: fmt.Sprintf("HTTPStatus: %d", response.StatusCode), + } + + if err = json.Unmarshal(body, apiError); err != nil { + return err + } + + return apiError + } + + // Nothing to unmarshal + if len(body) == 0 || resType == nil { + return nil + } + + return json.Unmarshal(body, &resType) +} + +func (c *Client) validate() error { + var response interface{} + + if err := c.Get("/v1/domains?statuses=ACTIVE", response); err != nil { + return err + } + + return nil +} diff --git a/provider/godaddy/godaddy.go b/provider/godaddy/godaddy.go new file mode 100644 index 000000000..6e25d7d6c --- /dev/null +++ b/provider/godaddy/godaddy.go @@ -0,0 +1,358 @@ +/* +Copyright 2020 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 godaddy + +import ( + "context" + "errors" + "fmt" + "strings" + + log "github.com/sirupsen/logrus" + "golang.org/x/sync/errgroup" + + "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/plan" + "sigs.k8s.io/external-dns/provider" +) + +const ( + gdDefaultTTL = 600 + gdCreate = iota + gdUpdate + gdDelete +) + +var ( + // ErrRecordToMutateNotFound when ApplyChange has to update/delete and didn't found the record in the existing zone (Change with no record ID) + ErrRecordToMutateNotFound = errors.New("record to mutate not found in current zone") + // ErrNoDryRun No dry run support for the moment + ErrNoDryRun = errors.New("dry run not supported") +) + +type gdClient interface { + Patch(string, interface{}, interface{}) error + Post(string, interface{}, interface{}) error + Put(string, interface{}, interface{}) error + Get(string, interface{}) error + Delete(string, interface{}) error +} + +// GDProvider declare GoDaddy provider +type GDProvider struct { + provider.BaseProvider + + domainFilter endpoint.DomainFilter + client gdClient + DryRun bool +} + +type gdRecordField struct { + Data string + Name string + Port int + Priority int + Protocol *string + Service *string + TTL int + Type string + Weight int +} + +type gdRecord struct { + gdRecordField + zone *string +} + +type gdChange struct { + gdRecord + Action int +} + +type gdZone struct { + CreatedAt string + Domain string + DomainID int64 + ExpirationProtected bool + Expires string + ExposeWhois bool + HoldRegistrar bool + Locked bool + NameServers *[]string + Privacy bool + RenewAuto bool + RenewDeadline string + Renewable bool + Status string + TransferProtected bool +} + +// NewGoDaddyProvider initializes a new OVH DNS based Provider. +func NewGoDaddyProvider(ctx context.Context, domainFilter endpoint.DomainFilter, apiKey, apiSecret string, production, dryRun bool) (*GDProvider, error) { + client, err := NewClient(production, apiKey, apiSecret) + + if err != nil { + return nil, err + } + + // TODO: Add Dry Run support + if dryRun { + return nil, ErrNoDryRun + } + + return &GDProvider{ + client: client, + domainFilter: domainFilter, + DryRun: dryRun, + }, nil +} + +func (p *GDProvider) zones() ([]string, error) { + zones := []gdZone{} + filteredZones := []string{} + + if err := p.client.Get("/v1/domains?statuses=ACTIVE", &zones); err != nil { + return nil, err + } + + for _, zone := range zones { + if p.domainFilter.Match(zone.Domain) { + filteredZones = append(filteredZones, zone.Domain) + } + } + + log.Infof("GoDaddy: %d zones found", len(filteredZones)) + + return filteredZones, nil +} + +func (p *GDProvider) zonesRecords(ctx context.Context) ([]string, []gdRecord, error) { + var allRecords []gdRecord + zones, err := p.zones() + + if err != nil { + return nil, nil, err + } + + chRecords := make(chan []gdRecord, len(zones)) + + eg, ctx := errgroup.WithContext(ctx) + + for _, zone := range zones { + zone := zone + eg.Go(func() error { + return p.records(&ctx, &zone, chRecords) + }) + } + + if err := eg.Wait(); err != nil { + return nil, nil, err + } + + close(chRecords) + + for records := range chRecords { + allRecords = append(allRecords, records...) + } + + return zones, allRecords, nil +} + +func (p *GDProvider) records(ctx *context.Context, zone *string, records chan<- []gdRecord) error { + var recordsIds []gdRecord + + log.Debugf("GoDaddy: Getting records for %s", *zone) + + if err := p.client.Get(fmt.Sprintf("/v1/domains/%s/records", *zone), &recordsIds); err != nil { + return err + } + + results := make([]gdRecord, 0, len(recordsIds)) + + for _, rec := range recordsIds { + if provider.SupportedRecordType(rec.Type) { + log.Debugf("GoDaddy: Record %s for %s is %+v", rec.Name, *zone, rec) + + rec.zone = zone + results = append(results, rec) + } + } + + records <- results + + return nil +} + +func (p *GDProvider) groupByNameAndType(records []gdRecord) []*endpoint.Endpoint { + endpoints := []*endpoint.Endpoint{} + + // group supported records by name and type + groups := map[string][]gdRecord{} + + for _, r := range records { + groupBy := fmt.Sprintf("%s - %s.%s", r.Type, r.Name, *r.zone) + + if _, ok := groups[groupBy]; !ok { + groups[groupBy] = []gdRecord{} + } + + groups[groupBy] = append(groups[groupBy], r) + } + + // create single endpoint with all the targets for each name/type + for _, records := range groups { + targets := []string{} + + for _, record := range records { + targets = append(targets, record.Data) + } + + endpoint := endpoint.NewEndpointWithTTL( + strings.TrimPrefix(fmt.Sprintf("%s.%s", records[0].Name, *records[0].zone), "."), + records[0].Type, + endpoint.TTL(records[0].TTL), + targets..., + ) + + endpoints = append(endpoints, endpoint) + } + + return endpoints +} + +// Records returns the list of records in all relevant zones. +func (p *GDProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) { + _, records, err := p.zonesRecords(ctx) + + if err != nil { + return nil, err + } + + endpoints := p.groupByNameAndType(records) + + log.Infof("GoDaddy: %d endpoints have been found", len(endpoints)) + + return endpoints, nil +} + +func (p *GDProvider) change(change gdChange) error { + switch change.Action { + case gdCreate: + log.Debugf("GoDaddy: Add an entry to %s", change.String()) + return p.client.Patch(fmt.Sprintf("/v1/domains/%s/records", *change.zone), []gdRecordField{change.gdRecord.gdRecordField}, nil) + case gdUpdate: + log.Debugf("GoDaddy: Update an entry to %s", change.String()) + return p.client.Put(fmt.Sprintf("/v1/domains/%s/records/%s/%s", *change.zone, change.Type, change.Name), []gdRecordField{change.gdRecord.gdRecordField}, nil) + case gdDelete: + log.Debugf("GoDaddy: Delete an entry to %s", change.String()) + return p.client.Delete(fmt.Sprintf("/v1/domains/%s/records/%s/%s", *change.zone, change.Type, change.Name), nil) + } + return nil +} + +// ApplyChanges applies a given set of changes in a given zone. +func (p *GDProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { + zones, records, err := p.zonesRecords(ctx) + zonesChangeUniques := map[string]bool{} + if err != nil { + return err + } + + allChanges := make([]gdChange, 0, countTargets(changes.Create, changes.UpdateNew, changes.UpdateOld, changes.Delete)) + + allChanges = append(allChanges, newGoDaddyChange(gdCreate, changes.Create, zones, records)...) + allChanges = append(allChanges, newGoDaddyChange(gdCreate, changes.UpdateNew, zones, records)...) + + allChanges = append(allChanges, newGoDaddyChange(gdDelete, changes.UpdateOld, zones, records)...) + allChanges = append(allChanges, newGoDaddyChange(gdDelete, changes.Delete, zones, records)...) + + log.Infof("GoDaddy: %d changes will be done", len(allChanges)) + + eg, _ := errgroup.WithContext(ctx) + + for _, change := range allChanges { + change := change + zonesChangeUniques[*change.zone] = true + + eg.Go(func() error { return p.change(change) }) + } + + if err := eg.Wait(); err != nil { + return err + } + + return nil +} + +func countTargets(allEndpoints ...[]*endpoint.Endpoint) int { + count := 0 + for _, endpoints := range allEndpoints { + for _, endpoint := range endpoints { + count += len(endpoint.Targets) + } + } + return count +} + +func newGoDaddyChange(action int, endpoints []*endpoint.Endpoint, zones []string, records []gdRecord) []gdChange { + gdChanges := make([]gdChange, 0, countTargets(endpoints)) + zoneNameIDMapper := provider.ZoneIDName{} + + for _, zone := range zones { + zoneNameIDMapper.Add(zone, zone) + } + + for _, e := range endpoints { + zone, _ := zoneNameIDMapper.FindZone(e.DNSName) + + if zone == "" { + log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", e.DNSName) + continue + } + + for _, target := range e.Targets { + if e.RecordType == endpoint.RecordTypeCNAME { + target = target + "." + } + + change := gdChange{ + Action: action, + gdRecord: gdRecord{ + zone: &zone, + gdRecordField: gdRecordField{ + Type: e.RecordType, + Name: strings.TrimSuffix(e.DNSName, "."+zone), + TTL: gdDefaultTTL, + Data: target, + }, + }, + } + + if e.RecordTTL.IsConfigured() { + change.TTL = int(e.RecordTTL) + } + + gdChanges = append(gdChanges, change) + } + } + + return gdChanges +} + +func (c *gdChange) String() string { + return fmt.Sprintf("%s zone : %s %d IN %s %s", *c.zone, c.Name, c.TTL, c.Type, c.Data) +} diff --git a/provider/godaddy/godaddy_test.go b/provider/godaddy/godaddy_test.go new file mode 100644 index 000000000..701ed78b8 --- /dev/null +++ b/provider/godaddy/godaddy_test.go @@ -0,0 +1,630 @@ +/* +Copyright 2017 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 godaddy + +import ( + "context" + "encoding/json" + "sort" + "testing" + + "github.com/ovh/go-ovh/ovh" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/plan" +) + +type mockGoDaddyClient struct { + mock.Mock +} + +var ( + zoneNameExampleOrg string = "example.org" + zoneNameExampleNet string = "example.net" +) + +func (c *mockGoDaddyClient) Post(endpoint string, input interface{}, output interface{}) error { + stub := c.Called(endpoint, input) + data, _ := json.Marshal(stub.Get(0)) + json.Unmarshal(data, output) + return stub.Error(1) +} + +func (c *mockGoDaddyClient) Patch(endpoint string, input interface{}, output interface{}) error { + stub := c.Called(endpoint, input) + data, _ := json.Marshal(stub.Get(0)) + json.Unmarshal(data, output) + return stub.Error(1) +} + +func (c *mockGoDaddyClient) Put(endpoint string, input interface{}, output interface{}) error { + stub := c.Called(endpoint, input) + data, _ := json.Marshal(stub.Get(0)) + json.Unmarshal(data, output) + return stub.Error(1) +} + +func (c *mockGoDaddyClient) Get(endpoint string, output interface{}) error { + stub := c.Called(endpoint) + data, _ := json.Marshal(stub.Get(0)) + json.Unmarshal(data, output) + return stub.Error(1) +} + +func (c *mockGoDaddyClient) Delete(endpoint string, output interface{}) error { + stub := c.Called(endpoint) + data, _ := json.Marshal(stub.Get(0)) + json.Unmarshal(data, output) + return stub.Error(1) +} + +func TestGoDaddyZones(t *testing.T) { + assert := assert.New(t) + client := new(mockGoDaddyClient) + provider := &GDProvider{ + client: client, + domainFilter: endpoint.NewDomainFilter([]string{"com"}), + } + + // Basic zones + client.On("Get", "/v1/domains?statuses=ACTIVE").Return([]gdZone{ + { + Domain: "example.com", + }, + { + Domain: "example.net", + }, + }, nil).Once() + + domains, err := provider.zones() + + assert.NoError(err) + assert.Contains(domains, "example.com") + assert.NotContains(domains, "example.net") + + client.AssertExpectations(t) + + // Error on getting zones + client.On("Get", "/v1/domains?statuses=ACTIVE").Return(nil, ErrAPIDown).Once() + domains, err = provider.zones() + assert.Error(err) + assert.Nil(domains) + client.AssertExpectations(t) +} + +func TestGoDaddyZoneRecords(t *testing.T) { + assert := assert.New(t) + client := new(mockGoDaddyClient) + provider := &GDProvider{ + client: client, + } + + // Basic zones records + client.On("Get", "/v1/domains?statuses=ACTIVE").Return([]gdZone{ + { + Domain: zoneNameExampleNet, + }, + }, nil).Once() + + client.On("Get", "/v1/domains/example.net/records").Return([]gdRecord{ + { + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy", + Type: "NS", + TTL: 10, + Data: "203.0.113.42", + }, + }, + { + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy", + Type: "A", + TTL: 10, + Data: "203.0.113.42", + }, + }, + }, nil).Once() + + zones, records, err := provider.zonesRecords(context.TODO()) + + assert.NoError(err) + + assert.ElementsMatch(zones, []string{ + zoneNameExampleNet, + }) + + assert.ElementsMatch(records, []gdRecord{ + { + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy", + Type: "NS", + TTL: 10, + Data: "203.0.113.42", + }, + }, + { + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy", + Type: "A", + TTL: 10, + Data: "203.0.113.42", + }, + }, + }) + + client.AssertExpectations(t) + + // Error on getting zones list + client.On("Get", "/v1/domains?statuses=ACTIVE").Return(nil, ErrAPIDown).Once() + zones, records, err = provider.zonesRecords(context.TODO()) + assert.Error(err) + assert.Nil(zones) + assert.Nil(records) + client.AssertExpectations(t) + + // Error on getting zone records + client.On("Get", "/v1/domains?statuses=ACTIVE").Return([]gdZone{ + { + Domain: zoneNameExampleNet, + }, + }, nil).Once() + + client.On("Get", "/v1/domains/example.net/records").Return(nil, ErrAPIDown).Once() + + zones, records, err = provider.zonesRecords(context.TODO()) + + assert.Error(err) + assert.Nil(zones) + assert.Nil(records) + client.AssertExpectations(t) + + // Error on getting zone record detail + client.On("Get", "/v1/domains?statuses=ACTIVE").Return([]gdZone{ + { + Domain: zoneNameExampleNet, + }, + }, nil).Once() + + client.On("Get", "/v1/domains/example.net/records").Return(nil, ErrAPIDown).Once() + + zones, records, err = provider.zonesRecords(context.TODO()) + assert.Error(err) + assert.Nil(zones) + assert.Nil(records) + client.AssertExpectations(t) +} + +func TestGoDaddyRecords(t *testing.T) { + assert := assert.New(t) + client := new(mockGoDaddyClient) + provider := &GDProvider{ + client: client, + } + + // Basic zones records + client.On("Get", "/v1/domains?statuses=ACTIVE").Return([]gdZone{ + { + Domain: zoneNameExampleOrg, + }, + { + Domain: zoneNameExampleNet, + }, + }, nil).Once() + + client.On("Get", "/v1/domains/example.org/records").Return([]gdRecord{ + { + zone: &zoneNameExampleOrg, + gdRecordField: gdRecordField{ + Name: "@", + Type: "A", + TTL: 10, + Data: "203.0.113.42", + }, + }, + { + zone: &zoneNameExampleOrg, + gdRecordField: gdRecordField{ + Name: "www", + Type: "CNAME", + TTL: 10, + Data: "example.org", + }, + }, + }, nil).Once() + + client.On("Get", "/v1/domains/example.net/records").Return([]gdRecord{ + { + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy", + Type: "A", + TTL: 10, + Data: "203.0.113.42", + }, + }, + { + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy", + Type: "A", + TTL: 10, + Data: "203.0.113.43", + }, + }, + }, nil).Once() + + endpoints, err := provider.Records(context.TODO()) + assert.NoError(err) + + // Little fix for multi targets endpoint + for _, endpoint := range endpoints { + sort.Strings(endpoint.Targets) + } + + assert.ElementsMatch(endpoints, []*endpoint.Endpoint{ + { + DNSName: "example.org", + RecordType: "A", + RecordTTL: 10, + Labels: endpoint.NewLabels(), + Targets: []string{ + "203.0.113.42", + }, + }, + { + DNSName: "www.example.org", + RecordType: "CNAME", + RecordTTL: 10, + Labels: endpoint.NewLabels(), + Targets: []string{ + "example.org", + }, + }, + { + DNSName: "godaddy.example.net", + RecordType: "A", + RecordTTL: 10, + Labels: endpoint.NewLabels(), + Targets: []string{ + "203.0.113.42", + "203.0.113.43", + }, + }, + }) + + client.AssertExpectations(t) + + // Error getting zone + client.On("Get", "/v1/domains?statuses=ACTIVE").Return(nil, ovh.ErrAPIDown).Once() + endpoints, err = provider.Records(context.TODO()) + assert.Error(err) + assert.Nil(endpoints) + client.AssertExpectations(t) +} + +func TestGoDaddyNewChange(t *testing.T) { + assert := assert.New(t) + endpoints := []*endpoint.Endpoint{ + { + DNSName: ".example.net", + RecordType: "A", + RecordTTL: 10, Targets: []string{ + "203.0.113.42", + }, + }, + { + DNSName: "godaddy.example.net", + RecordType: "A", + Targets: []string{ + "203.0.113.43", + }, + }, + { + DNSName: "godaddy2.example.net", + RecordType: "CNAME", + Targets: []string{ + "godaddy.example.net", + }, + }, + { + DNSName: "test.example.org", + }, + } + + // Create change + changes := newGoDaddyChange(gdCreate, endpoints, []string{"example.net"}, []gdRecord{}) + + assert.ElementsMatch(changes, []gdChange{ + { + Action: gdCreate, + gdRecord: gdRecord{ + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "", + Type: "A", + TTL: 10, + Data: "203.0.113.42", + }, + }, + }, + { + Action: gdCreate, + gdRecord: gdRecord{ + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy", + Type: "A", + TTL: gdDefaultTTL, + Data: "203.0.113.43", + }, + }, + }, + { + Action: gdCreate, + gdRecord: gdRecord{ + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy2", + Type: "CNAME", + TTL: gdDefaultTTL, + Data: "godaddy.example.net.", + }, + }, + }}) + + // Delete change + endpoints = []*endpoint.Endpoint{ + { + DNSName: "godaddy.example.net", + RecordType: "A", + Targets: []string{ + "203.0.113.42", + }, + }, + } + + records := []gdRecord{ + { + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Type: "A", + Name: "godaddy", + Data: "203.0.113.42", + }, + }, + } + + changes = newGoDaddyChange(gdDelete, endpoints, []string{ + zoneNameExampleNet, + }, records) + + assert.ElementsMatch(changes, []gdChange{ + { + Action: gdDelete, + gdRecord: gdRecord{ + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy", + Type: "A", + TTL: gdDefaultTTL, + Data: "203.0.113.42", + }, + }, + }, + }) +} + +func TestGoDaddyApplyChanges(t *testing.T) { + assert := assert.New(t) + client := new(mockGoDaddyClient) + + provider := &GDProvider{ + client: client, + } + + changes := plan.Changes{ + Create: []*endpoint.Endpoint{ + { + DNSName: ".example.net", + RecordType: "A", + RecordTTL: 10, + Targets: []string{ + "203.0.113.42", + }, + }, + }, + Delete: []*endpoint.Endpoint{ + { + DNSName: "godaddy.example.net", + RecordType: "A", + Targets: []string{ + "203.0.113.43", + }, + }, + }, + } + + client.On("Get", "/v1/domains?statuses=ACTIVE").Return([]gdZone{ + { + Domain: zoneNameExampleNet, + }, + }, nil).Once() + + client.On("Get", "/v1/domains/example.net/records").Return([]gdRecord{ + { + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy", + Type: "A", + TTL: 10, + Data: "203.0.113.43", + }, + }, + }, nil).Once() + + client.On("Get", "/v1/domains/example.net/records/A/goddady").Return([]gdRecord{ + { + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy", + Type: "A", + TTL: 10, + Data: "203.0.113.43", + }, + }, + }, nil).Once() + + client.On("Patch", "/v1/domains/example.net/records", []gdRecordField{ + { + Name: "@", + Type: "A", + TTL: 10, + Data: "203.0.113.42", + }, + }).Return(nil, nil).Once() + + client.On("Delete", "/v1/domains/example.net/records/A/@").Return(nil, nil).Once() + + // Basic changes + assert.NoError(provider.ApplyChanges(context.TODO(), &changes)) + + client.AssertExpectations(t) + + // Getting zones failed + client.On("Get", "/v1/domains?statuses=ACTIVE").Return(nil, ErrAPIDown).Once() + assert.Error(provider.ApplyChanges(context.TODO(), &changes)) + client.AssertExpectations(t) + + // Apply change failed + client.On("Get", "/v1/domains?statuses=ACTIVE").Return([]gdZone{ + { + Domain: zoneNameExampleNet, + }, + }, nil).Once() + + client.On("Get", "/v1/domains/example.net/records").Return([]gdRecord{}, nil).Once() + + client.On("Patch", "/v1/domains/example.net/records/A/godaddy", []gdRecordField{ + { + Name: "", + Type: "A", + TTL: 10, + Data: "203.0.113.42", + }, + }).Return(nil, ErrAPIDown).Once() + + assert.Error(provider.ApplyChanges(context.TODO(), &plan.Changes{ + Create: []*endpoint.Endpoint{ + { + DNSName: ".example.net", + RecordType: "A", + RecordTTL: 10, + Targets: []string{ + "203.0.113.42", + }, + }, + }, + })) + + client.AssertExpectations(t) +} + +func TestGoDaddyChange(t *testing.T) { + assert := assert.New(t) + client := new(mockGoDaddyClient) + provider := &GDProvider{ + client: client, + } + + // Record creation + client.On("Patch", "/v1/domains/example.net/records", []gdRecordField{ + { + Name: "godaddy", + Type: "A", + TTL: 10, + Data: "203.0.113.42", + }, + }).Return(nil, nil).Once() + + assert.NoError(provider.change(gdChange{ + Action: gdCreate, + gdRecord: gdRecord{ + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy", + Type: "A", + TTL: 10, + Data: "203.0.113.42", + }, + }, + })) + + client.AssertExpectations(t) + + // Record deletion + client.On("Delete", "/v1/domains/example.net/records/A/godaddy").Return(nil, nil).Once() + + assert.NoError(provider.change(gdChange{ + Action: gdDelete, + gdRecord: gdRecord{ + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy", + Type: "A", + }, + }, + })) + + client.AssertExpectations(t) + + // Record deletion error + assert.Error(provider.change(gdChange{ + Action: gdDelete, + gdRecord: gdRecord{ + zone: &zoneNameExampleNet, + gdRecordField: gdRecordField{ + Name: "godaddy", + Type: "A", + }, + }, + })) + + client.AssertExpectations(t) +} + +func TestOGoDaddyCountTargets(t *testing.T) { + cases := []struct { + endpoints [][]*endpoint.Endpoint + count int + }{ + {[][]*endpoint.Endpoint{{{DNSName: "godaddy.example.net", Targets: endpoint.Targets{"target"}}}}, 1}, + {[][]*endpoint.Endpoint{{{DNSName: "godaddy.example.net", Targets: endpoint.Targets{"target"}}, {DNSName: "godaddy.example.net", Targets: endpoint.Targets{"target"}}}}, 2}, + {[][]*endpoint.Endpoint{{{DNSName: "godaddy.example.net", Targets: endpoint.Targets{"target", "target", "target"}}}}, 3}, + {[][]*endpoint.Endpoint{{{DNSName: "godaddy.example.net", Targets: endpoint.Targets{"target", "target"}}}, {{DNSName: "godaddy.example.net", Targets: endpoint.Targets{"target", "target"}}}}, 4}, + } + for _, test := range cases { + count := countTargets(test.endpoints...) + if count != test.count { + t.Errorf("Wrong targets counts (Should be %d, get %d)", test.count, count) + } + } +} From 8a8e2412a6e1a3d8fcbdd81abf982bb82628115a Mon Sep 17 00:00:00 2001 From: fboltz Date: Mon, 11 Jan 2021 17:35:36 +0100 Subject: [PATCH 09/44] Handle wildcard subdomain --- provider/godaddy/godaddy.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/provider/godaddy/godaddy.go b/provider/godaddy/godaddy.go index 6e25d7d6c..b3c4aba7c 100644 --- a/provider/godaddy/godaddy.go +++ b/provider/godaddy/godaddy.go @@ -221,8 +221,16 @@ func (p *GDProvider) groupByNameAndType(records []gdRecord) []*endpoint.Endpoint targets = append(targets, record.Data) } + var recordName string + + if records[0].Name == "*" || records[0].Name == "@" { + recordName = strings.TrimPrefix(*records[0].zone, ".") + } else { + recordName = strings.TrimPrefix(fmt.Sprintf("%s.%s", records[0].Name, *records[0].zone), ".") + } + endpoint := endpoint.NewEndpointWithTTL( - strings.TrimPrefix(fmt.Sprintf("%s.%s", records[0].Name, *records[0].zone), "."), + recordName, records[0].Type, endpoint.TTL(records[0].TTL), targets..., From 1152334a1ecd7a235ee16fd0a91ad601cce628c7 Mon Sep 17 00:00:00 2001 From: fboltz Date: Mon, 11 Jan 2021 17:36:17 +0100 Subject: [PATCH 10/44] Sort the response to match test --- provider/godaddy/godaddy_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/provider/godaddy/godaddy_test.go b/provider/godaddy/godaddy_test.go index 701ed78b8..bb6930961 100644 --- a/provider/godaddy/godaddy_test.go +++ b/provider/godaddy/godaddy_test.go @@ -281,6 +281,16 @@ func TestGoDaddyRecords(t *testing.T) { } assert.ElementsMatch(endpoints, []*endpoint.Endpoint{ + { + DNSName: "godaddy.example.net", + RecordType: "A", + RecordTTL: 10, + Labels: endpoint.NewLabels(), + Targets: []string{ + "203.0.113.42", + "203.0.113.43", + }, + }, { DNSName: "example.org", RecordType: "A", @@ -299,16 +309,6 @@ func TestGoDaddyRecords(t *testing.T) { "example.org", }, }, - { - DNSName: "godaddy.example.net", - RecordType: "A", - RecordTTL: 10, - Labels: endpoint.NewLabels(), - Targets: []string{ - "203.0.113.42", - "203.0.113.43", - }, - }, }) client.AssertExpectations(t) From 78996ed8e18e45309db73c6005cb231f385861cd Mon Sep 17 00:00:00 2001 From: fboltz Date: Mon, 11 Jan 2021 17:48:34 +0100 Subject: [PATCH 11/44] FIX: Remove uneeded test --- provider/godaddy/godaddy_test.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/provider/godaddy/godaddy_test.go b/provider/godaddy/godaddy_test.go index bb6930961..1786a53d0 100644 --- a/provider/godaddy/godaddy_test.go +++ b/provider/godaddy/godaddy_test.go @@ -595,20 +595,6 @@ func TestGoDaddyChange(t *testing.T) { })) client.AssertExpectations(t) - - // Record deletion error - assert.Error(provider.change(gdChange{ - Action: gdDelete, - gdRecord: gdRecord{ - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy", - Type: "A", - }, - }, - })) - - client.AssertExpectations(t) } func TestOGoDaddyCountTargets(t *testing.T) { From 930067ae767b40a3db276e3030a164dc3bf3e9d9 Mon Sep 17 00:00:00 2001 From: fboltz Date: Mon, 11 Jan 2021 21:09:49 +0100 Subject: [PATCH 12/44] FIX: Wildcard handling --- provider/godaddy/godaddy.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/provider/godaddy/godaddy.go b/provider/godaddy/godaddy.go index b3c4aba7c..ca9ca00e0 100644 --- a/provider/godaddy/godaddy.go +++ b/provider/godaddy/godaddy.go @@ -223,7 +223,7 @@ func (p *GDProvider) groupByNameAndType(records []gdRecord) []*endpoint.Endpoint var recordName string - if records[0].Name == "*" || records[0].Name == "@" { + if records[0].Name == "@" { recordName = strings.TrimPrefix(*records[0].zone, ".") } else { recordName = strings.TrimPrefix(fmt.Sprintf("%s.%s", records[0].Name, *records[0].zone), ".") @@ -332,9 +332,18 @@ func newGoDaddyChange(action int, endpoints []*endpoint.Endpoint, zones []string continue } + dnsName := strings.TrimSuffix(e.DNSName, "."+zone) + + if e.RecordType == endpoint.RecordTypeA && (len(dnsName) == 0) { + dnsName = "@" + } + for _, target := range e.Targets { + if e.RecordType == endpoint.RecordTypeCNAME { target = target + "." + } else if e.RecordType == endpoint.RecordTypeA && (len(dnsName) == 0 || dnsName == ".") { + dnsName = "@" } change := gdChange{ @@ -343,7 +352,7 @@ func newGoDaddyChange(action int, endpoints []*endpoint.Endpoint, zones []string zone: &zone, gdRecordField: gdRecordField{ Type: e.RecordType, - Name: strings.TrimSuffix(e.DNSName, "."+zone), + Name: dnsName, TTL: gdDefaultTTL, Data: target, }, From 6d1d133ee775da62f6f940a023365984bc904e5f Mon Sep 17 00:00:00 2001 From: fboltz Date: Mon, 11 Jan 2021 21:10:56 +0100 Subject: [PATCH 13/44] FIX: Don't use channel, execution order is not garanty --- provider/godaddy/godaddy.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/provider/godaddy/godaddy.go b/provider/godaddy/godaddy.go index ca9ca00e0..df8e3f80e 100644 --- a/provider/godaddy/godaddy.go +++ b/provider/godaddy/godaddy.go @@ -275,7 +275,7 @@ func (p *GDProvider) change(change gdChange) error { // ApplyChanges applies a given set of changes in a given zone. func (p *GDProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { zones, records, err := p.zonesRecords(ctx) - zonesChangeUniques := map[string]bool{} + if err != nil { return err } @@ -290,17 +290,10 @@ func (p *GDProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) er log.Infof("GoDaddy: %d changes will be done", len(allChanges)) - eg, _ := errgroup.WithContext(ctx) - for _, change := range allChanges { - change := change - zonesChangeUniques[*change.zone] = true - - eg.Go(func() error { return p.change(change) }) - } - - if err := eg.Wait(); err != nil { - return err + if err = p.change(change); err != nil { + return err + } } return nil From 787fdd08bbebb6d0e98f7d075bc3fc941320ba3c Mon Sep 17 00:00:00 2001 From: fboltz Date: Mon, 11 Jan 2021 21:11:29 +0100 Subject: [PATCH 14/44] FIX: Make test working --- provider/godaddy/godaddy_test.go | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/provider/godaddy/godaddy_test.go b/provider/godaddy/godaddy_test.go index 1786a53d0..886d0c622 100644 --- a/provider/godaddy/godaddy_test.go +++ b/provider/godaddy/godaddy_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/ovh/go-ovh/ovh" + log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "sigs.k8s.io/external-dns/endpoint" @@ -31,6 +32,13 @@ import ( type mockGoDaddyClient struct { mock.Mock + currentTest *testing.T +} + +func newMockGoDaddyClient(t *testing.T) *mockGoDaddyClient { + return &mockGoDaddyClient{ + currentTest: t, + } } var ( @@ -39,6 +47,7 @@ var ( ) func (c *mockGoDaddyClient) Post(endpoint string, input interface{}, output interface{}) error { + log.Infof("POST: %s - %v", endpoint, input) stub := c.Called(endpoint, input) data, _ := json.Marshal(stub.Get(0)) json.Unmarshal(data, output) @@ -46,6 +55,7 @@ func (c *mockGoDaddyClient) Post(endpoint string, input interface{}, output inte } func (c *mockGoDaddyClient) Patch(endpoint string, input interface{}, output interface{}) error { + log.Infof("PATCH: %s - %v", endpoint, input) stub := c.Called(endpoint, input) data, _ := json.Marshal(stub.Get(0)) json.Unmarshal(data, output) @@ -53,6 +63,7 @@ func (c *mockGoDaddyClient) Patch(endpoint string, input interface{}, output int } func (c *mockGoDaddyClient) Put(endpoint string, input interface{}, output interface{}) error { + log.Infof("PUT: %s - %v", endpoint, input) stub := c.Called(endpoint, input) data, _ := json.Marshal(stub.Get(0)) json.Unmarshal(data, output) @@ -60,6 +71,7 @@ func (c *mockGoDaddyClient) Put(endpoint string, input interface{}, output inter } func (c *mockGoDaddyClient) Get(endpoint string, output interface{}) error { + log.Infof("GET: %s", endpoint) stub := c.Called(endpoint) data, _ := json.Marshal(stub.Get(0)) json.Unmarshal(data, output) @@ -67,6 +79,7 @@ func (c *mockGoDaddyClient) Get(endpoint string, output interface{}) error { } func (c *mockGoDaddyClient) Delete(endpoint string, output interface{}) error { + log.Infof("DELETE: %s", endpoint) stub := c.Called(endpoint) data, _ := json.Marshal(stub.Get(0)) json.Unmarshal(data, output) @@ -75,7 +88,7 @@ func (c *mockGoDaddyClient) Delete(endpoint string, output interface{}) error { func TestGoDaddyZones(t *testing.T) { assert := assert.New(t) - client := new(mockGoDaddyClient) + client := newMockGoDaddyClient(t) provider := &GDProvider{ client: client, domainFilter: endpoint.NewDomainFilter([]string{"com"}), @@ -109,7 +122,7 @@ func TestGoDaddyZones(t *testing.T) { func TestGoDaddyZoneRecords(t *testing.T) { assert := assert.New(t) - client := new(mockGoDaddyClient) + client := newMockGoDaddyClient(t) provider := &GDProvider{ client: client, } @@ -215,7 +228,7 @@ func TestGoDaddyZoneRecords(t *testing.T) { func TestGoDaddyRecords(t *testing.T) { assert := assert.New(t) - client := new(mockGoDaddyClient) + client := newMockGoDaddyClient(t) provider := &GDProvider{ client: client, } @@ -359,7 +372,7 @@ func TestGoDaddyNewChange(t *testing.T) { gdRecord: gdRecord{ zone: &zoneNameExampleNet, gdRecordField: gdRecordField{ - Name: "", + Name: "@", Type: "A", TTL: 10, Data: "203.0.113.42", @@ -435,7 +448,7 @@ func TestGoDaddyNewChange(t *testing.T) { func TestGoDaddyApplyChanges(t *testing.T) { assert := assert.New(t) - client := new(mockGoDaddyClient) + client := newMockGoDaddyClient(t) provider := &GDProvider{ client: client, @@ -462,7 +475,6 @@ func TestGoDaddyApplyChanges(t *testing.T) { }, }, } - client.On("Get", "/v1/domains?statuses=ACTIVE").Return([]gdZone{ { Domain: zoneNameExampleNet, @@ -481,18 +493,6 @@ func TestGoDaddyApplyChanges(t *testing.T) { }, }, nil).Once() - client.On("Get", "/v1/domains/example.net/records/A/goddady").Return([]gdRecord{ - { - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy", - Type: "A", - TTL: 10, - Data: "203.0.113.43", - }, - }, - }, nil).Once() - client.On("Patch", "/v1/domains/example.net/records", []gdRecordField{ { Name: "@", @@ -502,7 +502,7 @@ func TestGoDaddyApplyChanges(t *testing.T) { }, }).Return(nil, nil).Once() - client.On("Delete", "/v1/domains/example.net/records/A/@").Return(nil, nil).Once() + client.On("Delete", "/v1/domains/example.net/records/A/godaddy").Return(nil, nil).Once() // Basic changes assert.NoError(provider.ApplyChanges(context.TODO(), &changes)) @@ -523,9 +523,9 @@ func TestGoDaddyApplyChanges(t *testing.T) { client.On("Get", "/v1/domains/example.net/records").Return([]gdRecord{}, nil).Once() - client.On("Patch", "/v1/domains/example.net/records/A/godaddy", []gdRecordField{ + client.On("Patch", "/v1/domains/example.net/records", []gdRecordField{ { - Name: "", + Name: "@", Type: "A", TTL: 10, Data: "203.0.113.42", @@ -550,7 +550,7 @@ func TestGoDaddyApplyChanges(t *testing.T) { func TestGoDaddyChange(t *testing.T) { assert := assert.New(t) - client := new(mockGoDaddyClient) + client := newMockGoDaddyClient(t) provider := &GDProvider{ client: client, } From 8eaef452e737f28f3a8313c3dbb045ed75edf396 Mon Sep 17 00:00:00 2001 From: Victor Chan Date: Mon, 11 Jan 2021 21:33:03 -0800 Subject: [PATCH 15/44] added instructions for govcloud --- docs/tutorials/aws.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index 1ac463f27..2d49cbaeb 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -414,6 +414,39 @@ You can configure Route53 to associate DNS records with healthchecks for automat Note: ExternalDNS does not support creating healthchecks, and assumes that `` already exists. +## Govcloud caveats + +Due to the special nature with how Route53 runs in Govcloud, there are a few tweaks in the deployments settings that needs to be applied. + +* An Environment variable with name of AWS_REGION set to either us-gov-west-1 or us-gov-east-1 is required. Otherwise it tries to lookup a region that does not exist in govcloud and it errors out. + +```yaml +env: +- name: AWS_REGION + value: us-gov-west-1 +``` + +* Route 53 in Govcloud does not allow aliases. Therefore, container args must be set so that it uses CNAMES and a txt-prefix must be set to something. Otherwise, it will try to create a TXT record with the same value than the CNAME itself, which is not allowed. + +```yaml +args: +- --aws-prefer-cname +- --txt-prefix={YOUR_PREFIX} +``` + +* Route 53 in Govcloud does not support public zones. There are also no cross account IAM whatsoever between Govcloud and Commerical AWS accounts. If services and ingresses need to make Route 53 entries to an public zone in a commerical account, you will have set env variables of AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY with a key and secret to the commerical account that has the sufficient rights. + +```yaml +env: +- name: AWS_ACCESS_KEY_ID + value: XXXXXXXXX +- name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {YOUR_SECRET_NAME} + key: {YOUR_SECRET_KEY} +``` + ## Clean up Make sure to delete all Service objects before terminating the cluster so all load balancers get cleaned up correctly. From a4e469aa7fe741e18228cc5c5b32fe001e3f43b8 Mon Sep 17 00:00:00 2001 From: Victor Chan Date: Mon, 11 Jan 2021 21:34:26 -0800 Subject: [PATCH 16/44] added space --- docs/tutorials/aws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index 2d49cbaeb..9f215a28c 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -416,7 +416,7 @@ Note: ExternalDNS does not support creating healthchecks, and assumes that ` Date: Mon, 11 Jan 2021 21:36:39 -0800 Subject: [PATCH 17/44] tweak --- docs/tutorials/aws.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index 9f215a28c..be75391db 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -416,7 +416,7 @@ Note: ExternalDNS does not support creating healthchecks, and assumes that ` Date: Mon, 11 Jan 2021 21:59:35 -0800 Subject: [PATCH 18/44] changed wording to prevent confusion of using R53 in govcloud vs needing to make public facing r53 entries --- docs/tutorials/aws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index be75391db..5e720ffc6 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -434,7 +434,7 @@ args: - --txt-prefix={{ YOUR_PREFIX }} ``` -* Route 53 in Govcloud does not support public zones. There are also no cross account IAM whatsoever between Govcloud and Commerical AWS accounts. If services and ingresses need to make Route 53 entries to an public zone in a commerical account, you will have set env variables of AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY with a key and secret to the commerical account that has the sufficient rights. +* The 1st two changes are needed if you use Route 53 in Govcloud, which only supports private zones. There are also no cross account IAM whatsoever between Govcloud and Commerical AWS accounts. If services and ingresses need to make Route 53 entries to an public zone in a commerical account, you will have set env variables of AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY with a key and secret to the commerical account that has the sufficient rights. ```yaml env: From daeee26684afca4105ef9d33380c3fa98105fb60 Mon Sep 17 00:00:00 2001 From: Raffaele Di Fazio Date: Tue, 12 Jan 2021 15:32:26 +0100 Subject: [PATCH 19/44] Add flag to opt in for NS records management (#1915) * adds flag to opt in for NS records management Signed-off-by: Raffaele Di Fazio * go fmt Signed-off-by: Raffaele Di Fazio * goimports Signed-off-by: Raffaele Di Fazio * fix more tests Signed-off-by: Raffaele Di Fazio * go fmt again Signed-off-by: Raffaele Di Fazio * fix test Signed-off-by: Raffaele Di Fazio * more tests Signed-off-by: Raffaele Di Fazio * make ordering of managed records consistent Signed-off-by: Raffaele Di Fazio * fix flag Signed-off-by: Raffaele Di Fazio --- controller/controller.go | 3 + main.go | 11 +-- pkg/apis/externaldns/types.go | 5 ++ pkg/apis/externaldns/types_test.go | 4 + plan/plan.go | 32 ++++--- plan/plan_test.go | 118 ++++++++++++++----------- provider/aws/aws_test.go | 1 + provider/cloudflare/cloudflare_test.go | 51 +++++++---- registry/txt_test.go | 6 +- 9 files changed, 144 insertions(+), 87 deletions(-) diff --git a/controller/controller.go b/controller/controller.go index 1a87d335a..839327bb9 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -117,6 +117,8 @@ type Controller struct { nextRunAt time.Time // The nextRunAtMux is for atomic updating of nextRunAt nextRunAtMux sync.Mutex + // DNS record types that will be considered for management + ManagedRecordTypes []string } // RunOnce runs a single iteration of a reconciliation loop. @@ -147,6 +149,7 @@ func (c *Controller) RunOnce(ctx context.Context) error { Desired: endpoints, DomainFilter: c.DomainFilter, PropertyComparator: c.Registry.PropertyValuesEqual, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } plan = plan.Calculate() diff --git a/main.go b/main.go index 3b2cf7923..ccfd1d247 100644 --- a/main.go +++ b/main.go @@ -326,11 +326,12 @@ func main() { } ctrl := controller.Controller{ - Source: endpointsSource, - Registry: r, - Policy: policy, - Interval: cfg.Interval, - DomainFilter: domainFilter, + Source: endpointsSource, + Registry: r, + Policy: policy, + Interval: cfg.Interval, + DomainFilter: domainFilter, + ManagedRecordTypes: cfg.ManagedDNSRecordTypes, } if cfg.Once { diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 01a09cdfa..141a62b7e 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -22,6 +22,8 @@ import ( "strconv" "time" + "sigs.k8s.io/external-dns/endpoint" + "github.com/alecthomas/kingpin" "github.com/sirupsen/logrus" @@ -148,6 +150,7 @@ type Config struct { TransIPAccountName string TransIPPrivateKeyFile string DigitalOceanAPIPageSize int + ManagedDNSRecordTypes []string } var defaultConfig = &Config{ @@ -250,6 +253,7 @@ var defaultConfig = &Config{ TransIPAccountName: "", TransIPPrivateKeyFile: "", DigitalOceanAPIPageSize: 50, + ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } // NewConfig returns new Config object @@ -327,6 +331,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("crd-source-apiversion", "API version of the CRD for crd source, e.g. `externaldns.k8s.io/v1alpha1`, valid only when using crd source").Default(defaultConfig.CRDSourceAPIVersion).StringVar(&cfg.CRDSourceAPIVersion) app.Flag("crd-source-kind", "Kind of the CRD for the crd source in API group and version specified by crd-source-apiversion").Default(defaultConfig.CRDSourceKind).StringVar(&cfg.CRDSourceKind) app.Flag("service-type-filter", "The service types to take care about (default: all, expected: ClusterIP, NodePort, LoadBalancer or ExternalName)").StringsVar(&cfg.ServiceTypeFilter) + app.Flag("managed-record-types", "Comma separated list of record types to manage (default: A, CNAME) (supported records: CNAME, A, NS").Default("A", "CNAME").StringsVar(&cfg.ManagedDNSRecordTypes) // Flags related to providers app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, hetzner, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "hetzner", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns") diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index 02fd16ffd..9cd8db364 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -22,6 +22,8 @@ import ( "testing" "time" + "sigs.k8s.io/external-dns/endpoint" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -102,6 +104,7 @@ var ( TransIPAccountName: "", TransIPPrivateKeyFile: "", DigitalOceanAPIPageSize: 50, + ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } overriddenConfig = &Config{ @@ -186,6 +189,7 @@ var ( TransIPAccountName: "transip", TransIPPrivateKeyFile: "/path/to/transip.key", DigitalOceanAPIPageSize: 100, + ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } ) diff --git a/plan/plan.go b/plan/plan.go index 3e577d600..3ae346fcc 100644 --- a/plan/plan.go +++ b/plan/plan.go @@ -43,6 +43,8 @@ type Plan struct { DomainFilter endpoint.DomainFilter // Property comparator compares custom properties of providers PropertyComparator PropertyComparator + // DNS record types that will be considered for management + ManagedRecords []string } // Changes holds lists of actions to be executed by dns providers @@ -119,10 +121,10 @@ func (t planTable) addCandidate(e *endpoint.Endpoint) { func (p *Plan) Calculate() *Plan { t := newPlanTable() - for _, current := range filterRecordsForPlan(p.Current, p.DomainFilter) { + for _, current := range filterRecordsForPlan(p.Current, p.DomainFilter, p.ManagedRecords) { t.addCurrent(current) } - for _, desired := range filterRecordsForPlan(p.Desired, p.DomainFilter) { + for _, desired := range filterRecordsForPlan(p.Desired, p.DomainFilter, p.ManagedRecords) { t.addCandidate(desired) } @@ -155,9 +157,10 @@ func (p *Plan) Calculate() *Plan { } plan := &Plan{ - Current: p.Current, - Desired: p.Desired, - Changes: changes, + Current: p.Current, + Desired: p.Desired, + Changes: changes, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } return plan @@ -224,7 +227,7 @@ func (p *Plan) shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint) // Per RFC 1034, CNAME records conflict with all other records - it is the // only record with this property. The behavior of the planner may need to be // made more sophisticated to codify this. -func filterRecordsForPlan(records []*endpoint.Endpoint, domainFilter endpoint.DomainFilter) []*endpoint.Endpoint { +func filterRecordsForPlan(records []*endpoint.Endpoint, domainFilter endpoint.DomainFilter, managedRecords []string) []*endpoint.Endpoint { filtered := []*endpoint.Endpoint{} for _, record := range records { @@ -232,14 +235,8 @@ func filterRecordsForPlan(records []*endpoint.Endpoint, domainFilter endpoint.Do if !domainFilter.Match(record.DNSName) { continue } - - // Explicitly specify which records we want to use for planning. - // TODO: Add AAAA records as well when they are supported. - switch record.RecordType { - case endpoint.RecordTypeA, endpoint.RecordTypeCNAME, endpoint.RecordTypeNS: + if isManagedRecord(record.RecordType, managedRecords) { filtered = append(filtered, record) - default: - continue } } @@ -280,3 +277,12 @@ func CompareBoolean(defaultValue bool, name, current, previous string) bool { return v1 == v2 } + +func isManagedRecord(record string, managedRecords []string) bool { + for _, r := range managedRecords { + if record == r { + return true + } + } + return false +} diff --git a/plan/plan_test.go b/plan/plan_test.go index 5ea0f55ab..18b889143 100644 --- a/plan/plan_test.go +++ b/plan/plan_test.go @@ -205,9 +205,10 @@ func (suite *PlanTestSuite) TestSyncFirstRound() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -226,9 +227,10 @@ func (suite *PlanTestSuite) TestSyncSecondRound() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -247,9 +249,10 @@ func (suite *PlanTestSuite) TestSyncSecondRoundMigration() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -268,9 +271,10 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithTTLChange() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -289,9 +293,10 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificChange() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -316,6 +321,7 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificDefaultFalse( PropertyComparator: func(name, previous, current string) bool { return CompareBoolean(false, name, previous, current) }, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -368,9 +374,10 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithOwnerInherited() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -410,9 +417,10 @@ func (suite *PlanTestSuite) TestDifferentTypes() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -431,9 +439,10 @@ func (suite *PlanTestSuite) TestIgnoreTXT() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -452,9 +461,10 @@ func (suite *PlanTestSuite) TestRemoveEndpoint() { expectedDelete := []*endpoint.Endpoint{suite.bar192A} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -473,9 +483,10 @@ func (suite *PlanTestSuite) TestRemoveEndpointWithUpsert() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&UpsertOnlyPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&UpsertOnlyPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -495,9 +506,10 @@ func (suite *PlanTestSuite) TestDuplicatedEndpointsForSameResourceReplace() { expectedDelete := []*endpoint.Endpoint{suite.bar192A} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -518,9 +530,10 @@ func (suite *PlanTestSuite) TestDuplicatedEndpointsForSameResourceRetain() { expectedDelete := []*endpoint.Endpoint{suite.bar192A} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -540,9 +553,10 @@ func (suite *PlanTestSuite) TestMultipleRecordsSameNameDifferentSetIdentifier() expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -562,9 +576,10 @@ func (suite *PlanTestSuite) TestSetIdentifierUpdateCreatesAndDeletes() { expectedDelete := []*endpoint.Endpoint{suite.multiple2} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -584,10 +599,11 @@ func (suite *PlanTestSuite) TestDomainFiltersInitial() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, - DomainFilter: endpoint.NewDomainFilterWithExclusions([]string{"domain.tld"}, []string{"ex.domain.tld"}), + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + DomainFilter: endpoint.NewDomainFilterWithExclusions([]string{"domain.tld"}, []string{"ex.domain.tld"}), + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -607,10 +623,11 @@ func (suite *PlanTestSuite) TestDomainFiltersUpdate() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, - DomainFilter: endpoint.NewDomainFilterWithExclusions([]string{"domain.tld"}, []string{"ex.domain.tld"}), + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + DomainFilter: endpoint.NewDomainFilterWithExclusions([]string{"domain.tld"}, []string{"ex.domain.tld"}), + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -808,6 +825,7 @@ func TestShouldUpdateProviderSpecific(tt *testing.T) { Current: []*endpoint.Endpoint{test.current}, Desired: []*endpoint.Endpoint{test.desired}, PropertyComparator: test.propertyComparator, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } b := plan.shouldUpdateProviderSpecific(test.desired, test.current) assert.Equal(t, test.shouldUpdate, b) diff --git a/provider/aws/aws_test.go b/provider/aws/aws_test.go index b83e892ec..40e522a49 100644 --- a/provider/aws/aws_test.go +++ b/provider/aws/aws_test.go @@ -1152,6 +1152,7 @@ func TestAWSHealthTargetAnnotation(tt *testing.T) { Current: []*endpoint.Endpoint{test.current}, Desired: []*endpoint.Endpoint{test.desired}, PropertyComparator: provider.PropertyValuesEqual, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } plan = plan.Calculate() assert.Equal(t, test.shouldUpdate, len(plan.Changes.UpdateNew) == 1) diff --git a/provider/cloudflare/cloudflare_test.go b/provider/cloudflare/cloudflare_test.go index 440cc8b65..fae840b2b 100644 --- a/provider/cloudflare/cloudflare_test.go +++ b/provider/cloudflare/cloudflare_test.go @@ -229,7 +229,7 @@ func (m *mockCloudFlareClient) ZoneDetails(zoneID string) (cloudflare.Zone, erro return cloudflare.Zone{}, errors.New("Unknown zoneID: " + zoneID) } -func AssertActions(t *testing.T, provider *CloudFlareProvider, endpoints []*endpoint.Endpoint, actions []MockAction, args ...interface{}) { +func AssertActions(t *testing.T, provider *CloudFlareProvider, endpoints []*endpoint.Endpoint, actions []MockAction, managedRecords []string, args ...interface{}) { t.Helper() var client *mockCloudFlareClient @@ -250,9 +250,10 @@ func AssertActions(t *testing.T, provider *CloudFlareProvider, endpoints []*endp } plan := &plan.Plan{ - Current: records, - Desired: endpoints, - DomainFilter: endpoint.NewDomainFilter([]string{"bar.com"}), + Current: records, + Desired: endpoints, + DomainFilter: endpoint.NewDomainFilter([]string{"bar.com"}), + ManagedRecords: managedRecords, } changes := plan.Calculate().Changes @@ -305,7 +306,10 @@ func TestCloudflareA(t *testing.T) { Proxied: false, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) + } func TestCloudflareCname(t *testing.T) { @@ -340,7 +344,9 @@ func TestCloudflareCname(t *testing.T) { Proxied: false, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) } func TestCloudflareCustomTTL(t *testing.T) { @@ -365,7 +371,9 @@ func TestCloudflareCustomTTL(t *testing.T) { Proxied: false, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) } func TestCloudflareProxiedDefault(t *testing.T) { @@ -389,7 +397,9 @@ func TestCloudflareProxiedDefault(t *testing.T) { Proxied: true, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) } func TestCloudflareProxiedOverrideTrue(t *testing.T) { @@ -419,7 +429,9 @@ func TestCloudflareProxiedOverrideTrue(t *testing.T) { Proxied: true, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) } func TestCloudflareProxiedOverrideFalse(t *testing.T) { @@ -449,7 +461,9 @@ func TestCloudflareProxiedOverrideFalse(t *testing.T) { Proxied: false, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) } func TestCloudflareProxiedOverrideIllegal(t *testing.T) { @@ -479,7 +493,9 @@ func TestCloudflareProxiedOverrideIllegal(t *testing.T) { Proxied: true, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) } func TestCloudflareSetProxied(t *testing.T) { @@ -525,7 +541,7 @@ func TestCloudflareSetProxied(t *testing.T) { Proxied: testCase.proxiable, }, }, - }, testCase.recordType+" record on "+testCase.domain) + }, []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME, endpoint.RecordTypeNS}, testCase.recordType+" record on "+testCase.domain) } } @@ -1038,6 +1054,7 @@ func TestProviderPropertiesIdempotency(t *testing.T) { Current: current, Desired: desired, PropertyComparator: provider.PropertyValuesEqual, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } plan = *plan.Calculate() @@ -1091,7 +1108,8 @@ func TestCloudflareComplexUpdate(t *testing.T) { }, }, }, - DomainFilter: endpoint.NewDomainFilter([]string{"bar.com"}), + DomainFilter: endpoint.NewDomainFilter([]string{"bar.com"}), + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } planned := plan.Calculate() @@ -1178,9 +1196,10 @@ func TestCustomTTLWithEnabledProxyNotChanged(t *testing.T) { provider.AdjustEndpoints(endpoints) plan := &plan.Plan{ - Current: records, - Desired: endpoints, - DomainFilter: endpoint.NewDomainFilter([]string{"bar.com"}), + Current: records, + Desired: endpoints, + DomainFilter: endpoint.NewDomainFilter([]string{"bar.com"}), + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } planned := plan.Calculate() diff --git a/registry/txt_test.go b/registry/txt_test.go index d4993cb71..b3e20dd6e 100644 --- a/registry/txt_test.go +++ b/registry/txt_test.go @@ -172,9 +172,9 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) { }, }, { - DNSName: "*.wildcard.test-zone.example.org", - Targets: endpoint.Targets{"foo.loadbalancer.com"}, - RecordType: endpoint.RecordTypeCNAME, + DNSName: "*.wildcard.test-zone.example.org", + Targets: endpoint.Targets{"foo.loadbalancer.com"}, + RecordType: endpoint.RecordTypeCNAME, Labels: map[string]string{ endpoint.OwnerLabelKey: "owner", }, From 4175d2fba9ed1b38f77aa11e053bb110ee9c287c Mon Sep 17 00:00:00 2001 From: Raffaele Di Fazio Date: Wed, 13 Jan 2021 09:28:46 +0100 Subject: [PATCH 20/44] updates releaser.sh to include squashed commits Signed-off-by: Raffaele Di Fazio --- scripts/releaser.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/releaser.sh b/scripts/releaser.sh index a91f3e307..5af06a598 100755 --- a/scripts/releaser.sh +++ b/scripts/releaser.sh @@ -20,6 +20,14 @@ function generate_changelog { pr_author="$(gh pr view "$pr_num" | grep author | awk '{ print $2 }' | tr $'\n' ' ')" printf "* %s (%s) @%s\n\n" "$pr_desc" "$pr_num" "$pr_author" done + + git log "$previous_tag".. --reverse --oneline --grep='(#' | \ + while read -r sha title; do + pr_num="$(grep -o '#[[:digit:]]\+' <<<"$title")" + pr_desc="$(git show -s --format=%s "$sha")" + pr_author="$(gh pr view "$pr_num" | grep author | awk '{ print $2 }' | tr $'\n' ' ')" + printf "* %s (%s) @%s\n\n" "$pr_desc" "$pr_num" "$pr_author" + done } function create_release { From 8565d0fe78a0fea8ec7891f3e91222900831f7b8 Mon Sep 17 00:00:00 2001 From: Victor Chan Date: Wed, 13 Jan 2021 08:18:19 -0800 Subject: [PATCH 21/44] small changes to get PR approved --- docs/tutorials/aws.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index 5e720ffc6..d89b1aea0 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -416,9 +416,9 @@ Note: ExternalDNS does not support creating healthchecks, and assumes that ` Date: Thu, 14 Jan 2021 21:13:07 +0100 Subject: [PATCH 22/44] bumps kustomize version to 0.7.6 --- kustomize/kustomization.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kustomize/kustomization.yaml b/kustomize/kustomization.yaml index ebdc6ce79..960456772 100644 --- a/kustomize/kustomization.yaml +++ b/kustomize/kustomization.yaml @@ -3,7 +3,7 @@ kind: Kustomization images: - name: k8s.gcr.io/external-dns/external-dns - newTag: v0.7.5 + newTag: v0.7.6 resources: - ./external-dns-deployment.yaml From 7ccc70b12f71b127e181f990ad0aab6836648ffe Mon Sep 17 00:00:00 2001 From: fboltz Date: Sun, 17 Jan 2021 17:22:57 +0100 Subject: [PATCH 23/44] Change arguments: Switch to OTE and add TTL --- main.go | 2 +- pkg/apis/externaldns/types.go | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index 9fe13ba78..c56e959a8 100644 --- a/main.go +++ b/main.go @@ -300,7 +300,7 @@ func main() { p, err = scaleway.NewScalewayProvider(ctx, domainFilter, cfg.DryRun) case "godaddy": - p, err = godaddy.NewGoDaddyProvider(ctx, domainFilter, cfg.GoDaddyAPIKey, cfg.GoDaddySecretKey, cfg.GoDaddyProduction, cfg.DryRun) + p, err = godaddy.NewGoDaddyProvider(ctx, domainFilter, cfg.GoDaddyTTL, cfg.GoDaddyAPIKey, cfg.GoDaddySecretKey, cfg.GoDaddyOTE, cfg.DryRun) default: log.Fatalf("unknown dns provider: %s", cfg.Provider) } diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index b86c74350..312c95a93 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -148,9 +148,10 @@ type Config struct { TransIPAccountName string TransIPPrivateKeyFile string DigitalOceanAPIPageSize int - GoDaddyAPIKey string + GoDaddyAPIKey string `secure:"yes"` GoDaddySecretKey string `secure:"yes"` - GoDaddyProduction bool + GoDaddyTTL int64 + GoDaddyOTE bool } var defaultConfig = &Config{ @@ -255,7 +256,8 @@ var defaultConfig = &Config{ DigitalOceanAPIPageSize: 50, GoDaddyAPIKey: "", GoDaddySecretKey: "", - GoDaddyProduction: true, + GoDaddyTTL: 600, + GoDaddyOTE: false, } // NewConfig returns new Config object @@ -393,7 +395,8 @@ func (cfg *Config) ParseFlags(args []string) error { // GoDaddy flags app.Flag("godaddy-api-key", "When using the GoDaddy provider, specify the API Key (required when --provider=godaddy)").Default(defaultConfig.GoDaddyAPIKey).StringVar(&cfg.GoDaddyAPIKey) app.Flag("godaddy-api-secret", "When using the GoDaddy provider, specify the API secret (required when --provider=godaddy)").Default(defaultConfig.GoDaddySecretKey).StringVar(&cfg.GoDaddySecretKey) - app.Flag("godaddy-api-production", "When using the GoDaddy provider, specify if production or OTE use (required when --provider=godaddy)").Default(strconv.FormatBool(defaultConfig.GoDaddyProduction)).BoolVar(&cfg.GoDaddyProduction) + app.Flag("godaddy-api-ttl", "TTL (in seconds) for records. This value will be used if the provided TTL for a service/ingress is not provided.").Int64Var(&cfg.GoDaddyTTL) + app.Flag("godaddy-api-ote", "When using the GoDaddy provider, use OTE api (optional, default: false, when --provider=godaddy)").BoolVar(&cfg.GoDaddyOTE) // Flags related to TLS communication app.Flag("tls-ca", "When using TLS communication, the path to the certificate authority to verify server communications (optionally specify --tls-client-cert for two-way TLS)").Default(defaultConfig.TLSCA).StringVar(&cfg.TLSCA) From 0fdd2a8eaf740e7ca60ddadb5a9365b1a329efe2 Mon Sep 17 00:00:00 2001 From: fboltz Date: Sun, 17 Jan 2021 17:25:04 +0100 Subject: [PATCH 24/44] Progress: Debug and make working --- provider/godaddy/godaddy.go | 458 ++++++++++++++++++++----------- provider/godaddy/godaddy_test.go | 366 +++++------------------- 2 files changed, 368 insertions(+), 456 deletions(-) diff --git a/provider/godaddy/godaddy.go b/provider/godaddy/godaddy.go index df8e3f80e..08ae304da 100644 --- a/provider/godaddy/godaddy.go +++ b/provider/godaddy/godaddy.go @@ -31,7 +31,7 @@ import ( ) const ( - gdDefaultTTL = 600 + gdMinimalTTL = 600 gdCreate = iota gdUpdate gdDelete @@ -58,29 +58,31 @@ type GDProvider struct { domainFilter endpoint.DomainFilter client gdClient + ttl int64 DryRun bool } +type gdEndpoint struct { + endpoint *endpoint.Endpoint + action int +} + type gdRecordField struct { - Data string - Name string - Port int - Priority int - Protocol *string - Service *string - TTL int - Type string - Weight int + Data string `json:"data"` + Name string `json:"name"` + TTL int64 `json:"ttl"` + Type string `json:"type"` + Port *int `json:"port,omitempty"` + Priority *int `json:"priority,omitempty"` + Weight *int64 `json:"weight,omitempty"` + Protocol *string `json:"protocol,omitempty"` + Service *string `json:"service,omitempty"` } -type gdRecord struct { - gdRecordField - zone *string -} - -type gdChange struct { - gdRecord - Action int +type gdRecords struct { + records []gdRecordField + changed bool + zone string } type gdZone struct { @@ -101,9 +103,38 @@ type gdZone struct { TransferProtected bool } -// NewGoDaddyProvider initializes a new OVH DNS based Provider. -func NewGoDaddyProvider(ctx context.Context, domainFilter endpoint.DomainFilter, apiKey, apiSecret string, production, dryRun bool) (*GDProvider, error) { - client, err := NewClient(production, apiKey, apiSecret) +type gdZoneIDName map[string]*gdRecords + +func (z gdZoneIDName) add(zoneID string, zoneRecord *gdRecords) { + z[zoneID] = zoneRecord +} + +func (z gdZoneIDName) values() []*gdRecords { + values := make([]*gdRecords, 0, len(z)) + + for _, v := range z { + values = append(values, v) + } + + return values +} + +func (z gdZoneIDName) findZoneRecord(hostname string) (suitableZoneID string, suitableZoneRecord *gdRecords) { + for zoneID, zoneRecord := range z { + if hostname == zoneRecord.zone || strings.HasSuffix(hostname, "."+zoneRecord.zone) { + if suitableZoneRecord == nil || len(zoneRecord.zone) > len(suitableZoneRecord.zone) { + suitableZoneID = zoneID + suitableZoneRecord = zoneRecord + } + } + } + + return +} + +// NewGoDaddyProvider initializes a new GoDaddy DNS based Provider. +func NewGoDaddyProvider(ctx context.Context, domainFilter endpoint.DomainFilter, ttl int64, apiKey, apiSecret string, useOTE, dryRun bool) (*GDProvider, error) { + client, err := NewClient(useOTE, apiKey, apiSecret) if err != nil { return nil, err @@ -117,6 +148,7 @@ func NewGoDaddyProvider(ctx context.Context, domainFilter endpoint.DomainFilter, return &GDProvider{ client: client, domainFilter: domainFilter, + ttl: maxOf(gdMinimalTTL, ttl), DryRun: dryRun, }, nil } @@ -132,6 +164,7 @@ func (p *GDProvider) zones() ([]string, error) { for _, zone := range zones { if p.domainFilter.Match(zone.Domain) { filteredZones = append(filteredZones, zone.Domain) + log.Debugf("GoDaddy: %s zone found", zone.Domain) } } @@ -140,103 +173,140 @@ func (p *GDProvider) zones() ([]string, error) { return filteredZones, nil } -func (p *GDProvider) zonesRecords(ctx context.Context) ([]string, []gdRecord, error) { - var allRecords []gdRecord +func (p *GDProvider) zonesRecords(ctx context.Context, all bool) ([]string, []gdRecords, error) { + var allRecords []gdRecords zones, err := p.zones() if err != nil { return nil, nil, err } - chRecords := make(chan []gdRecord, len(zones)) + if len(zones) == 0 { + allRecords = []gdRecords{} + } else if len(zones) == 1 { + record, err := p.records(&ctx, zones[0], all) - eg, ctx := errgroup.WithContext(ctx) + if err != nil { + return nil, nil, err + } - for _, zone := range zones { - zone := zone - eg.Go(func() error { - return p.records(&ctx, &zone, chRecords) - }) - } + allRecords = append(allRecords, *record) + } else { + chRecords := make(chan gdRecords, len(zones)) - if err := eg.Wait(); err != nil { - return nil, nil, err - } + eg, ctx := errgroup.WithContext(ctx) - close(chRecords) + for _, zoneName := range zones { + zone := zoneName + eg.Go(func() error { + record, err := p.records(&ctx, zone, all) - for records := range chRecords { - allRecords = append(allRecords, records...) + if err != nil { + return err + } + + chRecords <- *record + + return nil + }) + } + + if err := eg.Wait(); err != nil { + return nil, nil, err + } + + close(chRecords) + + for records := range chRecords { + allRecords = append(allRecords, records) + } } return zones, allRecords, nil } -func (p *GDProvider) records(ctx *context.Context, zone *string, records chan<- []gdRecord) error { - var recordsIds []gdRecord +func (p *GDProvider) records(ctx *context.Context, zone string, all bool) (*gdRecords, error) { + var recordsIds []gdRecordField - log.Debugf("GoDaddy: Getting records for %s", *zone) + log.Debugf("GoDaddy: Getting records for %s", zone) - if err := p.client.Get(fmt.Sprintf("/v1/domains/%s/records", *zone), &recordsIds); err != nil { - return err + if err := p.client.Get(fmt.Sprintf("/v1/domains/%s/records", zone), &recordsIds); err != nil { + return nil, err } - results := make([]gdRecord, 0, len(recordsIds)) + if all { + return &gdRecords{ + zone: zone, + records: recordsIds, + }, nil + } + + results := &gdRecords{ + zone: zone, + records: make([]gdRecordField, 0, len(recordsIds)), + } for _, rec := range recordsIds { if provider.SupportedRecordType(rec.Type) { - log.Debugf("GoDaddy: Record %s for %s is %+v", rec.Name, *zone, rec) + log.Debugf("GoDaddy: Record %s for %s is %+v", rec.Name, zone, rec) - rec.zone = zone - results = append(results, rec) + results.records = append(results.records, rec) + } else { + log.Infof("GoDaddy: Discard record %s for %s is %+v", rec.Name, zone, rec) } } - records <- results - - return nil + return results, nil } -func (p *GDProvider) groupByNameAndType(records []gdRecord) []*endpoint.Endpoint { +func (p *GDProvider) groupByNameAndType(zoneRecords []gdRecords) []*endpoint.Endpoint { endpoints := []*endpoint.Endpoint{} // group supported records by name and type - groups := map[string][]gdRecord{} + groupsByZone := map[string]map[string][]gdRecordField{} - for _, r := range records { - groupBy := fmt.Sprintf("%s - %s.%s", r.Type, r.Name, *r.zone) + for _, zone := range zoneRecords { + groups := map[string][]gdRecordField{} - if _, ok := groups[groupBy]; !ok { - groups[groupBy] = []gdRecord{} + groupsByZone[zone.zone] = groups + + for _, r := range zone.records { + groupBy := fmt.Sprintf("%s - %s", r.Type, r.Name) + + if _, ok := groups[groupBy]; !ok { + groups[groupBy] = []gdRecordField{} + } + + groups[groupBy] = append(groups[groupBy], r) } - - groups[groupBy] = append(groups[groupBy], r) } // create single endpoint with all the targets for each name/type - for _, records := range groups { - targets := []string{} + for zoneName, groups := range groupsByZone { + for _, records := range groups { + targets := []string{} - for _, record := range records { - targets = append(targets, record.Data) + for _, record := range records { + targets = append(targets, record.Data) + } + + var recordName string + + if records[0].Name == "@" { + recordName = strings.TrimPrefix(zoneName, ".") + } else { + recordName = strings.TrimPrefix(fmt.Sprintf("%s.%s", records[0].Name, zoneName), ".") + } + + endpoint := endpoint.NewEndpointWithTTL( + recordName, + records[0].Type, + endpoint.TTL(records[0].TTL), + targets..., + ) + + endpoints = append(endpoints, endpoint) } - - var recordName string - - if records[0].Name == "@" { - recordName = strings.TrimPrefix(*records[0].zone, ".") - } else { - recordName = strings.TrimPrefix(fmt.Sprintf("%s.%s", records[0].Name, *records[0].zone), ".") - } - - endpoint := endpoint.NewEndpointWithTTL( - recordName, - records[0].Type, - endpoint.TTL(records[0].TTL), - targets..., - ) - - endpoints = append(endpoints, endpoint) } return endpoints @@ -244,7 +314,7 @@ func (p *GDProvider) groupByNameAndType(records []gdRecord) []*endpoint.Endpoint // Records returns the list of records in all relevant zones. func (p *GDProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) { - _, records, err := p.zonesRecords(ctx) + _, records, err := p.zonesRecords(ctx, false) if err != nil { return nil, err @@ -257,112 +327,194 @@ func (p *GDProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) return endpoints, nil } -func (p *GDProvider) change(change gdChange) error { - switch change.Action { - case gdCreate: - log.Debugf("GoDaddy: Add an entry to %s", change.String()) - return p.client.Patch(fmt.Sprintf("/v1/domains/%s/records", *change.zone), []gdRecordField{change.gdRecord.gdRecordField}, nil) - case gdUpdate: - log.Debugf("GoDaddy: Update an entry to %s", change.String()) - return p.client.Put(fmt.Sprintf("/v1/domains/%s/records/%s/%s", *change.zone, change.Type, change.Name), []gdRecordField{change.gdRecord.gdRecordField}, nil) - case gdDelete: - log.Debugf("GoDaddy: Delete an entry to %s", change.String()) - return p.client.Delete(fmt.Sprintf("/v1/domains/%s/records/%s/%s", *change.zone, change.Type, change.Name), nil) +func (p *GDProvider) flushRecords(patch bool, zoneRecord *gdRecords) error { + if patch { + return p.client.Patch(fmt.Sprintf("/v1/domains/%s/records", zoneRecord.zone), zoneRecord.records, nil) } + + return p.client.Put(fmt.Sprintf("/v1/domains/%s/records", zoneRecord.zone), zoneRecord.records, nil) +} + +func (p *GDProvider) appendChange(action int, endpoints []*endpoint.Endpoint, allChanges []gdEndpoint) []gdEndpoint { + for _, e := range endpoints { + allChanges = append(allChanges, gdEndpoint{ + action: action, + endpoint: e, + }) + } + + return allChanges +} + +func (p *GDProvider) changeAllRecords(patch bool, endpoints []gdEndpoint, zoneRecords []*gdRecords) error { + zoneNameIDMapper := gdZoneIDName{} + + for _, zoneRecord := range zoneRecords { + if patch { + zoneRecord.changed = false + zoneRecord.records = nil + } + + zoneNameIDMapper.add(zoneRecord.zone, zoneRecord) + } + + for _, e := range endpoints { + dnsName := e.endpoint.DNSName + zone, zoneRecord := zoneNameIDMapper.findZoneRecord(dnsName) + + if zone == "" { + log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", dnsName) + } else { + dnsName = strings.TrimSuffix(dnsName, "."+zone) + + if e.endpoint.RecordType == endpoint.RecordTypeA && (len(dnsName) == 0) { + dnsName = "@" + } + + for _, target := range e.endpoint.Targets { + + change := gdRecordField{ + Type: e.endpoint.RecordType, + Name: dnsName, + TTL: p.ttl, + Data: target, + } + + if e.endpoint.RecordTTL.IsConfigured() { + change.TTL = maxOf(gdMinimalTTL, int64(e.endpoint.RecordTTL)) + } + + zoneRecord.applyChange(e.action, change) + } + } + } + return nil } // ApplyChanges applies a given set of changes in a given zone. func (p *GDProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { - zones, records, err := p.zonesRecords(ctx) + + if countTargets(changes) == 0 { + return nil + } + + _, records, err := p.zonesRecords(ctx, true) if err != nil { return err } - allChanges := make([]gdChange, 0, countTargets(changes.Create, changes.UpdateNew, changes.UpdateOld, changes.Delete)) + changedZoneRecords := make([]*gdRecords, len(records)) - allChanges = append(allChanges, newGoDaddyChange(gdCreate, changes.Create, zones, records)...) - allChanges = append(allChanges, newGoDaddyChange(gdCreate, changes.UpdateNew, zones, records)...) + for i := range records { + changedZoneRecords[i] = &records[i] + } - allChanges = append(allChanges, newGoDaddyChange(gdDelete, changes.UpdateOld, zones, records)...) - allChanges = append(allChanges, newGoDaddyChange(gdDelete, changes.Delete, zones, records)...) + allChanges := make([]gdEndpoint, 0, countTargets(changes)) + + allChanges = p.appendChange(gdCreate, changes.Create, allChanges) + allChanges = p.appendChange(gdCreate, changes.UpdateNew, allChanges) + allChanges = p.appendChange(gdDelete, changes.UpdateOld, allChanges) + allChanges = p.appendChange(gdDelete, changes.Delete, allChanges) log.Infof("GoDaddy: %d changes will be done", len(allChanges)) - for _, change := range allChanges { - if err = p.change(change); err != nil { - return err + patch := len(changes.UpdateOld)+len(changes.Delete) == 0 + + if err = p.changeAllRecords(patch, allChanges, changedZoneRecords); err != nil { + return err + } + + for _, record := range changedZoneRecords { + if record.changed { + if err = p.flushRecords(patch, record); err != nil { + return err + } } } return nil } -func countTargets(allEndpoints ...[]*endpoint.Endpoint) int { +func (p *gdRecords) addRecord(change gdRecordField) { + log.Debugf("GoDaddy: Add an entry %s to zone %s", change.String(), p.zone) + + p.records = append(p.records, change) + p.changed = true +} + +func (p *gdRecords) updateRecord(change gdRecordField) { + log.Debugf("GoDaddy: Update an entry %s to zone %s", change.String(), p.zone) + + for index, record := range p.records { + if record.Type == change.Type && record.Name == change.Name { + p.records[index] = change + p.changed = true + break + } + } +} + +// Remove one record from the record list +func (p *gdRecords) deleteRecord(change gdRecordField) { + log.Debugf("GoDaddy: Delete an entry %s to zone %s", change.String(), p.zone) + + deleteIndex := -1 + + for index, record := range p.records { + if record.Type == change.Type && record.Name == change.Name && record.Data == change.Data { + deleteIndex = index + break + } + } + + if deleteIndex >= 0 { + p.records[deleteIndex] = p.records[len(p.records)-1] + + p.records = p.records[:len(p.records)-1] + p.changed = true + } else { + log.Warnf("GoDaddy: record in zone %s not found %s to delete", p.zone, change.String()) + } +} + +func (p *gdRecords) applyChange(action int, change gdRecordField) { + switch action { + case gdCreate: + p.addRecord(change) + case gdUpdate: + p.updateRecord(change) + case gdDelete: + p.deleteRecord(change) + } +} + +func (c gdRecordField) String() string { + return fmt.Sprintf("%s %d IN %s %s", c.Name, c.TTL, c.Type, c.Data) +} + +func countTargets(p *plan.Changes) int { + changes := [][]*endpoint.Endpoint{p.Create, p.UpdateNew, p.UpdateOld, p.Delete} count := 0 - for _, endpoints := range allEndpoints { + + for _, endpoints := range changes { for _, endpoint := range endpoints { count += len(endpoint.Targets) } } + return count } -func newGoDaddyChange(action int, endpoints []*endpoint.Endpoint, zones []string, records []gdRecord) []gdChange { - gdChanges := make([]gdChange, 0, countTargets(endpoints)) - zoneNameIDMapper := provider.ZoneIDName{} +func maxOf(vars ...int64) int64 { + max := vars[0] - for _, zone := range zones { - zoneNameIDMapper.Add(zone, zone) - } - - for _, e := range endpoints { - zone, _ := zoneNameIDMapper.FindZone(e.DNSName) - - if zone == "" { - log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", e.DNSName) - continue - } - - dnsName := strings.TrimSuffix(e.DNSName, "."+zone) - - if e.RecordType == endpoint.RecordTypeA && (len(dnsName) == 0) { - dnsName = "@" - } - - for _, target := range e.Targets { - - if e.RecordType == endpoint.RecordTypeCNAME { - target = target + "." - } else if e.RecordType == endpoint.RecordTypeA && (len(dnsName) == 0 || dnsName == ".") { - dnsName = "@" - } - - change := gdChange{ - Action: action, - gdRecord: gdRecord{ - zone: &zone, - gdRecordField: gdRecordField{ - Type: e.RecordType, - Name: dnsName, - TTL: gdDefaultTTL, - Data: target, - }, - }, - } - - if e.RecordTTL.IsConfigured() { - change.TTL = int(e.RecordTTL) - } - - gdChanges = append(gdChanges, change) + for _, i := range vars { + if max < i { + max = i } } - return gdChanges -} - -func (c *gdChange) String() string { - return fmt.Sprintf("%s zone : %s %d IN %s %s", *c.zone, c.Name, c.TTL, c.Type, c.Data) + return max } diff --git a/provider/godaddy/godaddy_test.go b/provider/godaddy/godaddy_test.go index 886d0c622..5cbd34416 100644 --- a/provider/godaddy/godaddy_test.go +++ b/provider/godaddy/godaddy_test.go @@ -22,7 +22,6 @@ import ( "sort" "testing" - "github.com/ovh/go-ovh/ovh" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -134,28 +133,22 @@ func TestGoDaddyZoneRecords(t *testing.T) { }, }, nil).Once() - client.On("Get", "/v1/domains/example.net/records").Return([]gdRecord{ + client.On("Get", "/v1/domains/example.net/records").Return([]gdRecordField{ { - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy", - Type: "NS", - TTL: 10, - Data: "203.0.113.42", - }, + Name: "godaddy", + Type: "NS", + TTL: 10, + Data: "203.0.113.42", }, { - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy", - Type: "A", - TTL: 10, - Data: "203.0.113.42", - }, + Name: "godaddy", + Type: "A", + TTL: 10, + Data: "203.0.113.42", }, }, nil).Once() - zones, records, err := provider.zonesRecords(context.TODO()) + zones, records, err := provider.zonesRecords(context.TODO(), true) assert.NoError(err) @@ -163,23 +156,22 @@ func TestGoDaddyZoneRecords(t *testing.T) { zoneNameExampleNet, }) - assert.ElementsMatch(records, []gdRecord{ + assert.ElementsMatch(records, []gdRecords{ { - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy", - Type: "NS", - TTL: 10, - Data: "203.0.113.42", - }, - }, - { - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy", - Type: "A", - TTL: 10, - Data: "203.0.113.42", + zone: zoneNameExampleNet, + records: []gdRecordField{ + { + Name: "godaddy", + Type: "NS", + TTL: 10, + Data: "203.0.113.42", + }, + { + Name: "godaddy", + Type: "A", + TTL: 10, + Data: "203.0.113.42", + }, }, }, }) @@ -188,7 +180,7 @@ func TestGoDaddyZoneRecords(t *testing.T) { // Error on getting zones list client.On("Get", "/v1/domains?statuses=ACTIVE").Return(nil, ErrAPIDown).Once() - zones, records, err = provider.zonesRecords(context.TODO()) + zones, records, err = provider.zonesRecords(context.TODO(), false) assert.Error(err) assert.Nil(zones) assert.Nil(records) @@ -203,7 +195,7 @@ func TestGoDaddyZoneRecords(t *testing.T) { client.On("Get", "/v1/domains/example.net/records").Return(nil, ErrAPIDown).Once() - zones, records, err = provider.zonesRecords(context.TODO()) + zones, records, err = provider.zonesRecords(context.TODO(), false) assert.Error(err) assert.Nil(zones) @@ -219,7 +211,7 @@ func TestGoDaddyZoneRecords(t *testing.T) { client.On("Get", "/v1/domains/example.net/records").Return(nil, ErrAPIDown).Once() - zones, records, err = provider.zonesRecords(context.TODO()) + zones, records, err = provider.zonesRecords(context.TODO(), false) assert.Error(err) assert.Nil(zones) assert.Nil(records) @@ -243,45 +235,33 @@ func TestGoDaddyRecords(t *testing.T) { }, }, nil).Once() - client.On("Get", "/v1/domains/example.org/records").Return([]gdRecord{ + client.On("Get", "/v1/domains/example.org/records").Return([]gdRecordField{ { - zone: &zoneNameExampleOrg, - gdRecordField: gdRecordField{ - Name: "@", - Type: "A", - TTL: 10, - Data: "203.0.113.42", - }, + Name: "@", + Type: "A", + TTL: 10, + Data: "203.0.113.42", }, { - zone: &zoneNameExampleOrg, - gdRecordField: gdRecordField{ - Name: "www", - Type: "CNAME", - TTL: 10, - Data: "example.org", - }, + Name: "www", + Type: "CNAME", + TTL: 10, + Data: "example.org", }, }, nil).Once() - client.On("Get", "/v1/domains/example.net/records").Return([]gdRecord{ + client.On("Get", "/v1/domains/example.net/records").Return([]gdRecordField{ { - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy", - Type: "A", - TTL: 10, - Data: "203.0.113.42", - }, + Name: "godaddy", + Type: "A", + TTL: 10, + Data: "203.0.113.42", }, { - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy", - Type: "A", - TTL: 10, - Data: "203.0.113.43", - }, + Name: "godaddy", + Type: "A", + TTL: 10, + Data: "203.0.113.43", }, }, nil).Once() @@ -327,129 +307,16 @@ func TestGoDaddyRecords(t *testing.T) { client.AssertExpectations(t) // Error getting zone - client.On("Get", "/v1/domains?statuses=ACTIVE").Return(nil, ovh.ErrAPIDown).Once() + client.On("Get", "/v1/domains?statuses=ACTIVE").Return(nil, ErrAPIDown).Once() endpoints, err = provider.Records(context.TODO()) assert.Error(err) assert.Nil(endpoints) client.AssertExpectations(t) } -func TestGoDaddyNewChange(t *testing.T) { - assert := assert.New(t) - endpoints := []*endpoint.Endpoint{ - { - DNSName: ".example.net", - RecordType: "A", - RecordTTL: 10, Targets: []string{ - "203.0.113.42", - }, - }, - { - DNSName: "godaddy.example.net", - RecordType: "A", - Targets: []string{ - "203.0.113.43", - }, - }, - { - DNSName: "godaddy2.example.net", - RecordType: "CNAME", - Targets: []string{ - "godaddy.example.net", - }, - }, - { - DNSName: "test.example.org", - }, - } - - // Create change - changes := newGoDaddyChange(gdCreate, endpoints, []string{"example.net"}, []gdRecord{}) - - assert.ElementsMatch(changes, []gdChange{ - { - Action: gdCreate, - gdRecord: gdRecord{ - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "@", - Type: "A", - TTL: 10, - Data: "203.0.113.42", - }, - }, - }, - { - Action: gdCreate, - gdRecord: gdRecord{ - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy", - Type: "A", - TTL: gdDefaultTTL, - Data: "203.0.113.43", - }, - }, - }, - { - Action: gdCreate, - gdRecord: gdRecord{ - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy2", - Type: "CNAME", - TTL: gdDefaultTTL, - Data: "godaddy.example.net.", - }, - }, - }}) - - // Delete change - endpoints = []*endpoint.Endpoint{ - { - DNSName: "godaddy.example.net", - RecordType: "A", - Targets: []string{ - "203.0.113.42", - }, - }, - } - - records := []gdRecord{ - { - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Type: "A", - Name: "godaddy", - Data: "203.0.113.42", - }, - }, - } - - changes = newGoDaddyChange(gdDelete, endpoints, []string{ - zoneNameExampleNet, - }, records) - - assert.ElementsMatch(changes, []gdChange{ - { - Action: gdDelete, - gdRecord: gdRecord{ - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy", - Type: "A", - TTL: gdDefaultTTL, - Data: "203.0.113.42", - }, - }, - }, - }) -} - -func TestGoDaddyApplyChanges(t *testing.T) { +func TestGoDaddyChange(t *testing.T) { assert := assert.New(t) client := newMockGoDaddyClient(t) - provider := &GDProvider{ client: client, } @@ -475,142 +342,35 @@ func TestGoDaddyApplyChanges(t *testing.T) { }, }, } + + // Fetch domains client.On("Get", "/v1/domains?statuses=ACTIVE").Return([]gdZone{ { Domain: zoneNameExampleNet, }, }, nil).Once() - client.On("Get", "/v1/domains/example.net/records").Return([]gdRecord{ - { - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy", - Type: "A", - TTL: 10, - Data: "203.0.113.43", - }, - }, - }, nil).Once() - - client.On("Patch", "/v1/domains/example.net/records", []gdRecordField{ - { - Name: "@", - Type: "A", - TTL: 10, - Data: "203.0.113.42", - }, - }).Return(nil, nil).Once() - - client.On("Delete", "/v1/domains/example.net/records/A/godaddy").Return(nil, nil).Once() - - // Basic changes - assert.NoError(provider.ApplyChanges(context.TODO(), &changes)) - - client.AssertExpectations(t) - - // Getting zones failed - client.On("Get", "/v1/domains?statuses=ACTIVE").Return(nil, ErrAPIDown).Once() - assert.Error(provider.ApplyChanges(context.TODO(), &changes)) - client.AssertExpectations(t) - - // Apply change failed - client.On("Get", "/v1/domains?statuses=ACTIVE").Return([]gdZone{ - { - Domain: zoneNameExampleNet, - }, - }, nil).Once() - - client.On("Get", "/v1/domains/example.net/records").Return([]gdRecord{}, nil).Once() - - client.On("Patch", "/v1/domains/example.net/records", []gdRecordField{ - { - Name: "@", - Type: "A", - TTL: 10, - Data: "203.0.113.42", - }, - }).Return(nil, ErrAPIDown).Once() - - assert.Error(provider.ApplyChanges(context.TODO(), &plan.Changes{ - Create: []*endpoint.Endpoint{ - { - DNSName: ".example.net", - RecordType: "A", - RecordTTL: 10, - Targets: []string{ - "203.0.113.42", - }, - }, - }, - })) - - client.AssertExpectations(t) -} - -func TestGoDaddyChange(t *testing.T) { - assert := assert.New(t) - client := newMockGoDaddyClient(t) - provider := &GDProvider{ - client: client, - } - - // Record creation - client.On("Patch", "/v1/domains/example.net/records", []gdRecordField{ + // Fetch record + client.On("Get", "/v1/domains/example.net/records").Return([]gdRecordField{ { Name: "godaddy", Type: "A", TTL: 10, + Data: "203.0.113.43", + }, + }, nil).Once() + + // Update domain + client.On("Put", "/v1/domains/example.net/records", []gdRecordField{ + { + Name: "@", + Type: "A", + TTL: 10, Data: "203.0.113.42", }, }).Return(nil, nil).Once() - assert.NoError(provider.change(gdChange{ - Action: gdCreate, - gdRecord: gdRecord{ - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy", - Type: "A", - TTL: 10, - Data: "203.0.113.42", - }, - }, - })) - - client.AssertExpectations(t) - - // Record deletion - client.On("Delete", "/v1/domains/example.net/records/A/godaddy").Return(nil, nil).Once() - - assert.NoError(provider.change(gdChange{ - Action: gdDelete, - gdRecord: gdRecord{ - zone: &zoneNameExampleNet, - gdRecordField: gdRecordField{ - Name: "godaddy", - Type: "A", - }, - }, - })) + assert.NoError(provider.ApplyChanges(context.TODO(), &changes)) client.AssertExpectations(t) } - -func TestOGoDaddyCountTargets(t *testing.T) { - cases := []struct { - endpoints [][]*endpoint.Endpoint - count int - }{ - {[][]*endpoint.Endpoint{{{DNSName: "godaddy.example.net", Targets: endpoint.Targets{"target"}}}}, 1}, - {[][]*endpoint.Endpoint{{{DNSName: "godaddy.example.net", Targets: endpoint.Targets{"target"}}, {DNSName: "godaddy.example.net", Targets: endpoint.Targets{"target"}}}}, 2}, - {[][]*endpoint.Endpoint{{{DNSName: "godaddy.example.net", Targets: endpoint.Targets{"target", "target", "target"}}}}, 3}, - {[][]*endpoint.Endpoint{{{DNSName: "godaddy.example.net", Targets: endpoint.Targets{"target", "target"}}}, {{DNSName: "godaddy.example.net", Targets: endpoint.Targets{"target", "target"}}}}, 4}, - } - for _, test := range cases { - count := countTargets(test.endpoints...) - if count != test.count { - t.Errorf("Wrong targets counts (Should be %d, get %d)", test.count, count) - } - } -} From d8a0bc4dc9d8b11e97b90cc8591c8f8bb5e6ca15 Mon Sep 17 00:00:00 2001 From: fboltz Date: Sun, 17 Jan 2021 17:25:33 +0100 Subject: [PATCH 25/44] Change arguments: OTE --- provider/godaddy/client.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/provider/godaddy/client.go b/provider/godaddy/client.go index 440c3ad27..6a0326f8b 100644 --- a/provider/godaddy/client.go +++ b/provider/godaddy/client.go @@ -77,13 +77,13 @@ type Client struct { } // NewClient represents a new client to call the API -func NewClient(production bool, apiKey, apiSecret string) (*Client, error) { +func NewClient(useOTE bool, apiKey, apiSecret string) (*Client, error) { var endpoint string - if production { - endpoint = "https://api.godaddy.com" - } else { + if useOTE { endpoint = " https://api.ote-godaddy.com" + } else { + endpoint = "https://api.godaddy.com" } client := Client{ From 9aa6fb291f45e5d317595c56e5356100ce4eaf02 Mon Sep 17 00:00:00 2001 From: fboltz Date: Sun, 17 Jan 2021 17:26:07 +0100 Subject: [PATCH 26/44] Update README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index d24bb47ed..8486d4603 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ ExternalDNS' current release is `v0.7`. This version allows you to keep selected * [Vultr](https://www.vultr.com) * [OVH](https://www.ovh.com) * [Scaleway](https://www.scaleway.com) +* [GoDaddy](https://www.godaddy.com) From this release, ExternalDNS can become aware of the records it is managing (enabled via `--registry=txt`), therefore ExternalDNS can safely manage non-empty hosted zones. We strongly encourage you to use `v0.5` (or greater) with `--registry=txt` enabled and `--txt-owner-id` set to a unique value that doesn't change for the lifetime of your cluster. You might also want to run ExternalDNS in a dry run mode (`--dry-run` flag) to see the changes to be submitted to your DNS Provider API. @@ -102,6 +103,7 @@ The following table clarifies the current status of the providers according to t | Scaleway DNS | Alpha | @Sh4d1 | | Vultr | Alpha | | | UltraDNS | Alpha | | +| GoDaddy | Alpha | | ## Running ExternalDNS: @@ -154,6 +156,7 @@ The following tutorials are provided: * [Scaleway](docs/tutorials/scaleway.md) * [Vultr](docs/tutorials/vultr.md) * [UltraDNS](docs/tutorials/ultradns.md) +* [GoDaddy](docs/tutorials/godaddy.md) ### Running Locally From 2b113c25fa159452385598b725efa83e73ae3b51 Mon Sep 17 00:00:00 2001 From: fboltz Date: Sun, 17 Jan 2021 17:26:31 +0100 Subject: [PATCH 27/44] Add tutorial --- docs/tutorials/godaddy.md | 197 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 docs/tutorials/godaddy.md diff --git a/docs/tutorials/godaddy.md b/docs/tutorials/godaddy.md new file mode 100644 index 000000000..782ac4dd0 --- /dev/null +++ b/docs/tutorials/godaddy.md @@ -0,0 +1,197 @@ +# Setting up ExternalDNS for Services on GoDaddy + +This tutorial describes how to setup ExternalDNS for use within a +Kubernetes cluster using GoDaddy DNS. + +Make sure to use **>=0.6** version of ExternalDNS for this tutorial. + +## Creating a zone with GoDaddy DNS + +If you are new to GoDaddy, we recommend you first read the following +instructions for creating a zone. + +[Creating a zone using the GoDaddy web console](https://www.godaddy.com/) + +[Creating a zone using the GoDaddy API](https://developer.godaddy.com/) + +## Creating GoDaddy API key + +You first need to create an API Key. + +Using the [GoDaddy documentation](https://developer.godaddy.com/getstarted) you will have your `API key` and `API secret` + +## Deploy ExternalDNS + +Connect your `kubectl` client to the cluster with which you want to test ExternalDNS, and then apply one of the following manifest files for deployment: + +### Manifest (for clusters without RBAC enabled) + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns +spec: + strategy: + type: Recreate + selector: + matchLabels: + app: external-dns + template: + metadata: + labels: + app: external-dns + spec: + containers: + - name: external-dns + image: k8s.gcr.io/external-dns/external-dns:v0.7.6 + args: + - --source=service # ingress is also possible + - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. + - --provider=godaddy + - --txt-prefix=external-dns. # In case of multiple k8s cluster + - --txt-owner-id=owner-id # In case of multiple k8s cluster + - --godaddy-api-key= + - --godaddy-api-secret= +``` + +### Manifest (for clusters with RBAC enabled) + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: external-dns +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: external-dns +rules: +- apiGroups: [""] + resources: ["services"] + verbs: ["get","watch","list"] +- apiGroups: [""] + resources: ["pods"] + verbs: ["get","watch","list"] +- apiGroups: ["extensions","networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get","watch","list"] +- apiGroups: [""] + resources: ["nodes"] + verbs: ["list"] +- apiGroups: [""] + resources: ["endpoints"] + verbs: ["get","watch","list"] +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: external-dns-viewer +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: external-dns +subjects: +- kind: ServiceAccount + name: external-dns + namespace: default +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns +spec: + strategy: + type: Recreate + selector: + matchLabels: + app: external-dns + template: + metadata: + labels: + app: external-dns + spec: + serviceAccountName: external-dns + containers: + - name: external-dns + image: k8s.gcr.io/external-dns/external-dns:v0.7.6 + args: + - --source=service # ingress is also possible + - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. + - --provider=godaddy + - --txt-prefix=external-dns. # In case of multiple k8s cluster + - --txt-owner-id=owner-id # In case of multiple k8s cluster + - --godaddy-api-key= + - --godaddy-api-secret= +``` + +## Deploying an Nginx Service + +Create a service file called 'nginx.yaml' with the following contents: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - image: nginx + name: nginx + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx + annotations: + external-dns.alpha.kubernetes.io/hostname: example.com + external-dns.alpha.kubernetes.io/ttl: "120" #optional +spec: + selector: + app: nginx + type: LoadBalancer + ports: + - protocol: TCP + port: 80 + targetPort: 80 +``` + +**A note about annotations** + +Verify that the annotation on the service uses the same hostname as the GoDaddy DNS zone created above. The annotation may also be a subdomain of the DNS zone (e.g. 'www.example.com'). + +The TTL annotation can be used to configure the TTL on DNS records managed by ExternalDNS and is optional. If this annotation is not set, the TTL on records managed by ExternalDNS will default to 10. + +ExternalDNS uses the hostname annotation to determine which services should be registered with DNS. Removing the hostname annotation will cause ExternalDNS to remove the corresponding DNS records. + +### Create the deployment and service + +``` +$ kubectl create -f nginx.yaml +``` + +Depending on where you run your service, it may take some time for your cloud provider to create an external IP for the service. Once an external IP is assigned, ExternalDNS detects the new service IP address and synchronizes the GoDaddy DNS records. + +## Verifying GoDaddy DNS records + +Use the GoDaddy web console or API to verify that the A record for your domain shows the external IP address of the services. + +## Cleanup + +Once you successfully configure and verify record management via ExternalDNS, you can delete the tutorial's example: + +``` +$ kubectl delete -f nginx.yaml +$ kubectl delete -f externaldns.yaml +``` From 2e3138e1322cd56ddf8dfb468def197c4fc33642 Mon Sep 17 00:00:00 2001 From: fboltz Date: Sun, 17 Jan 2021 17:26:46 +0100 Subject: [PATCH 28/44] Typo --- provider/godaddy/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/provider/godaddy/client.go b/provider/godaddy/client.go index 6a0326f8b..b4ca89c22 100644 --- a/provider/godaddy/client.go +++ b/provider/godaddy/client.go @@ -13,6 +13,7 @@ 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 godaddy import ( From 66dcd89e666525cf64fdbcddbd003d9929f15535 Mon Sep 17 00:00:00 2001 From: fboltz Date: Sun, 17 Jan 2021 17:27:01 +0100 Subject: [PATCH 29/44] Typo: Change message --- provider/godaddy/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider/godaddy/client.go b/provider/godaddy/client.go index b4ca89c22..ffed0ed2c 100644 --- a/provider/godaddy/client.go +++ b/provider/godaddy/client.go @@ -34,7 +34,7 @@ const DefaultTimeout = 180 * time.Second // Errors var ( - ErrAPIDown = errors.New("go-vh: the GoDaddy API is down, it does't respond to /time anymore") + ErrAPIDown = errors.New("godaddy: the GoDaddy API is down") ) // APIError error From 4509f727cdd97989d1674bc549d4e0179feede35 Mon Sep 17 00:00:00 2001 From: fboltz Date: Sun, 17 Jan 2021 17:53:25 +0100 Subject: [PATCH 30/44] FIX: Set TTL value --- provider/godaddy/godaddy_test.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/provider/godaddy/godaddy_test.go b/provider/godaddy/godaddy_test.go index 5cbd34416..2e9b07342 100644 --- a/provider/godaddy/godaddy_test.go +++ b/provider/godaddy/godaddy_test.go @@ -137,13 +137,13 @@ func TestGoDaddyZoneRecords(t *testing.T) { { Name: "godaddy", Type: "NS", - TTL: 10, + TTL: gdMinimalTTL, Data: "203.0.113.42", }, { Name: "godaddy", Type: "A", - TTL: 10, + TTL: gdMinimalTTL, Data: "203.0.113.42", }, }, nil).Once() @@ -163,13 +163,13 @@ func TestGoDaddyZoneRecords(t *testing.T) { { Name: "godaddy", Type: "NS", - TTL: 10, + TTL: gdMinimalTTL, Data: "203.0.113.42", }, { Name: "godaddy", Type: "A", - TTL: 10, + TTL: gdMinimalTTL, Data: "203.0.113.42", }, }, @@ -239,13 +239,13 @@ func TestGoDaddyRecords(t *testing.T) { { Name: "@", Type: "A", - TTL: 10, + TTL: gdMinimalTTL, Data: "203.0.113.42", }, { Name: "www", Type: "CNAME", - TTL: 10, + TTL: gdMinimalTTL, Data: "example.org", }, }, nil).Once() @@ -254,13 +254,13 @@ func TestGoDaddyRecords(t *testing.T) { { Name: "godaddy", Type: "A", - TTL: 10, + TTL: gdMinimalTTL, Data: "203.0.113.42", }, { Name: "godaddy", Type: "A", - TTL: 10, + TTL: gdMinimalTTL, Data: "203.0.113.43", }, }, nil).Once() @@ -277,7 +277,7 @@ func TestGoDaddyRecords(t *testing.T) { { DNSName: "godaddy.example.net", RecordType: "A", - RecordTTL: 10, + RecordTTL: gdMinimalTTL, Labels: endpoint.NewLabels(), Targets: []string{ "203.0.113.42", @@ -287,7 +287,7 @@ func TestGoDaddyRecords(t *testing.T) { { DNSName: "example.org", RecordType: "A", - RecordTTL: 10, + RecordTTL: gdMinimalTTL, Labels: endpoint.NewLabels(), Targets: []string{ "203.0.113.42", @@ -296,7 +296,7 @@ func TestGoDaddyRecords(t *testing.T) { { DNSName: "www.example.org", RecordType: "CNAME", - RecordTTL: 10, + RecordTTL: gdMinimalTTL, Labels: endpoint.NewLabels(), Targets: []string{ "example.org", @@ -326,7 +326,7 @@ func TestGoDaddyChange(t *testing.T) { { DNSName: ".example.net", RecordType: "A", - RecordTTL: 10, + RecordTTL: gdMinimalTTL, Targets: []string{ "203.0.113.42", }, @@ -355,7 +355,7 @@ func TestGoDaddyChange(t *testing.T) { { Name: "godaddy", Type: "A", - TTL: 10, + TTL: gdMinimalTTL, Data: "203.0.113.43", }, }, nil).Once() @@ -365,7 +365,7 @@ func TestGoDaddyChange(t *testing.T) { { Name: "@", Type: "A", - TTL: 10, + TTL: gdMinimalTTL, Data: "203.0.113.42", }, }).Return(nil, nil).Once() From 0805acf594dbcc6cdb0647d7655a7a63a48b4616 Mon Sep 17 00:00:00 2001 From: fboltz Date: Sun, 17 Jan 2021 17:53:34 +0100 Subject: [PATCH 31/44] Update go.mod --- go.mod | 1 + 1 file changed, 1 insertion(+) diff --git a/go.mod b/go.mod index 06b632e7a..5b5206b0d 100644 --- a/go.mod +++ b/go.mod @@ -56,6 +56,7 @@ require ( go.uber.org/ratelimit v0.1.0 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e google.golang.org/api v0.15.0 gopkg.in/ns1/ns1-go.v2 v2.0.0-20190322154155-0dafb5275fd1 gopkg.in/yaml.v2 v2.2.8 From 6fdfa0d2774d218c0cd6c90049c6ee5aae9196d1 Mon Sep 17 00:00:00 2001 From: fboltz Date: Sun, 17 Jan 2021 19:28:13 +0100 Subject: [PATCH 32/44] FIX: make golangci-lint happy --- provider/godaddy/godaddy.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/provider/godaddy/godaddy.go b/provider/godaddy/godaddy.go index 08ae304da..fcfc5ccce 100644 --- a/provider/godaddy/godaddy.go +++ b/provider/godaddy/godaddy.go @@ -109,16 +109,6 @@ func (z gdZoneIDName) add(zoneID string, zoneRecord *gdRecords) { z[zoneID] = zoneRecord } -func (z gdZoneIDName) values() []*gdRecords { - values := make([]*gdRecords, 0, len(z)) - - for _, v := range z { - values = append(values, v) - } - - return values -} - func (z gdZoneIDName) findZoneRecord(hostname string) (suitableZoneID string, suitableZoneRecord *gdRecords) { for zoneID, zoneRecord := range z { if hostname == zoneRecord.zone || strings.HasSuffix(hostname, "."+zoneRecord.zone) { @@ -372,7 +362,6 @@ func (p *GDProvider) changeAllRecords(patch bool, endpoints []gdEndpoint, zoneRe } for _, target := range e.endpoint.Targets { - change := gdRecordField{ Type: e.endpoint.RecordType, Name: dnsName, @@ -394,7 +383,6 @@ func (p *GDProvider) changeAllRecords(patch bool, endpoints []gdEndpoint, zoneRe // ApplyChanges applies a given set of changes in a given zone. func (p *GDProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { - if countTargets(changes) == 0 { return nil } From bc10232ad7520ee45c524ba33d40c3d2f507597a Mon Sep 17 00:00:00 2001 From: timyinshi Date: Mon, 18 Jan 2021 12:10:44 +0800 Subject: [PATCH 33/44] modify the error url of external-dns rbac Signed-off-by: timyinshi --- docs/tutorials/kube-ingress-aws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/kube-ingress-aws.md b/docs/tutorials/kube-ingress-aws.md index 27ea646f4..99cd07714 100644 --- a/docs/tutorials/kube-ingress-aws.md +++ b/docs/tutorials/kube-ingress-aws.md @@ -78,7 +78,7 @@ rules: See also current RBAC yaml files: - [kube-ingress-aws-controller](https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/cluster/manifests/ingress-controller/01-rbac.yaml) - [skipper](https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/cluster/manifests/skipper/rbac.yaml) -- [external-dns](https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/cluster/manifests/external-dns/rbac.yaml) +- [external-dns](https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/cluster/manifests/external-dns/01-rbac.yaml) [3]: https://opensource.zalando.com/skipper/kubernetes/routegroups/#routegroups [4]: https://opensource.zalando.com/skipper From bf6641d9734ea838414056eaa8c5f720295d60e8 Mon Sep 17 00:00:00 2001 From: Frederic BOLTZ Date: Tue, 19 Jan 2021 14:30:34 +0100 Subject: [PATCH 34/44] Update docs/tutorials/godaddy.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nick Jüttner --- docs/tutorials/godaddy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/godaddy.md b/docs/tutorials/godaddy.md index 782ac4dd0..7ce4c4edb 100644 --- a/docs/tutorials/godaddy.md +++ b/docs/tutorials/godaddy.md @@ -79,7 +79,7 @@ rules: verbs: ["get","watch","list"] - apiGroups: [""] resources: ["nodes"] - verbs: ["list"] + verbs: ["list","watch"] - apiGroups: [""] resources: ["endpoints"] verbs: ["get","watch","list"] From 85e048d4c94e2dda0e5c82711ca1781a3cd1bc20 Mon Sep 17 00:00:00 2001 From: Frederic BOLTZ Date: Tue, 19 Jan 2021 14:30:47 +0100 Subject: [PATCH 35/44] Update docs/tutorials/godaddy.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nick Jüttner --- docs/tutorials/godaddy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/godaddy.md b/docs/tutorials/godaddy.md index 7ce4c4edb..daf9a7ad7 100644 --- a/docs/tutorials/godaddy.md +++ b/docs/tutorials/godaddy.md @@ -44,7 +44,7 @@ spec: spec: containers: - name: external-dns - image: k8s.gcr.io/external-dns/external-dns:v0.7.6 + image: k8s.gcr.io/external-dns/external-dns:v0.7.7 args: - --source=service # ingress is also possible - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. From 02acef614d5dd36742f5aa19da39fbb37f1cdc92 Mon Sep 17 00:00:00 2001 From: Frederic BOLTZ Date: Tue, 19 Jan 2021 14:30:57 +0100 Subject: [PATCH 36/44] Update docs/tutorials/godaddy.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nick Jüttner --- docs/tutorials/godaddy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/godaddy.md b/docs/tutorials/godaddy.md index daf9a7ad7..c6d24b46f 100644 --- a/docs/tutorials/godaddy.md +++ b/docs/tutorials/godaddy.md @@ -115,7 +115,7 @@ spec: serviceAccountName: external-dns containers: - name: external-dns - image: k8s.gcr.io/external-dns/external-dns:v0.7.6 + image: k8s.gcr.io/external-dns/external-dns:v0.7.7 args: - --source=service # ingress is also possible - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. From 33e53edcd4cec3b8b679547d969d0b985df4ab91 Mon Sep 17 00:00:00 2001 From: Frederic BOLTZ Date: Tue, 19 Jan 2021 14:31:47 +0100 Subject: [PATCH 37/44] Update main.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nick Jüttner --- main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/main.go b/main.go index c61b28b26..c210a0745 100644 --- a/main.go +++ b/main.go @@ -299,7 +299,6 @@ func main() { p, err = transip.NewTransIPProvider(cfg.TransIPAccountName, cfg.TransIPPrivateKeyFile, domainFilter, cfg.DryRun) case "scaleway": p, err = scaleway.NewScalewayProvider(ctx, domainFilter, cfg.DryRun) - case "godaddy": p, err = godaddy.NewGoDaddyProvider(ctx, domainFilter, cfg.GoDaddyTTL, cfg.GoDaddyAPIKey, cfg.GoDaddySecretKey, cfg.GoDaddyOTE, cfg.DryRun) default: From 6e3c8ef1a4fd274b3c143c4647e0878436a57fa3 Mon Sep 17 00:00:00 2001 From: Frederic BOLTZ Date: Tue, 19 Jan 2021 14:32:05 +0100 Subject: [PATCH 38/44] Update pkg/apis/externaldns/types.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nick Jüttner --- pkg/apis/externaldns/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index b3225965a..f8b791ae8 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -346,7 +346,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("managed-record-types", "Comma separated list of record types to manage (default: A, CNAME) (supported records: CNAME, A, NS").Default("A", "CNAME").StringsVar(&cfg.ManagedDNSRecordTypes) // Flags related to providers - app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, hetzner, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "hetzner", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns", "godaddy") + app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, godaddy, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, hetzner, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "hetzner", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns", "godaddy") app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter) app.Flag("exclude-domains", "Exclude subdomains (optional)").Default("").StringsVar(&cfg.ExcludeDomains) app.Flag("zone-name-filter", "Filter target zones by zone domain (For now, only AzureDNS provider is using this flag); specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneNameFilter) From db648acfd3e5caeed9409adc07fe54058072e2b4 Mon Sep 17 00:00:00 2001 From: Frederic BOLTZ Date: Tue, 19 Jan 2021 14:32:20 +0100 Subject: [PATCH 39/44] Update pkg/apis/externaldns/types.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nick Jüttner --- pkg/apis/externaldns/types.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index f8b791ae8..c835e667e 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -402,7 +402,6 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("ns1-ignoressl", "When using the NS1 provider, specify whether to verify the SSL certificate (default: false)").Default(strconv.FormatBool(defaultConfig.NS1IgnoreSSL)).BoolVar(&cfg.NS1IgnoreSSL) app.Flag("ns1-min-ttl", "Minimal TTL (in seconds) for records. This value will be used if the provided TTL for a service/ingress is lower than this.").IntVar(&cfg.NS1MinTTLSeconds) app.Flag("digitalocean-api-page-size", "Configure the page size used when querying the DigitalOcean API.").Default(strconv.Itoa(defaultConfig.DigitalOceanAPIPageSize)).IntVar(&cfg.DigitalOceanAPIPageSize) - // GoDaddy flags app.Flag("godaddy-api-key", "When using the GoDaddy provider, specify the API Key (required when --provider=godaddy)").Default(defaultConfig.GoDaddyAPIKey).StringVar(&cfg.GoDaddyAPIKey) app.Flag("godaddy-api-secret", "When using the GoDaddy provider, specify the API secret (required when --provider=godaddy)").Default(defaultConfig.GoDaddySecretKey).StringVar(&cfg.GoDaddySecretKey) From ec8263dc11a817ae2fe98040c929f041de893804 Mon Sep 17 00:00:00 2001 From: Jonas-Taha El Sesiy Date: Thu, 21 Jan 2021 09:51:32 -0800 Subject: [PATCH 40/44] Address review comments --- provider/azure/{common.go => config.go} | 2 +- provider/azure/{common_test.go => config_test.go} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename provider/azure/{common.go => config.go} (98%) rename provider/azure/{common_test.go => config_test.go} (100%) diff --git a/provider/azure/common.go b/provider/azure/config.go similarity index 98% rename from provider/azure/common.go rename to provider/azure/config.go index 02386c889..34c13713e 100644 --- a/provider/azure/common.go +++ b/provider/azure/config.go @@ -27,7 +27,7 @@ import ( "gopkg.in/yaml.v2" ) -// Config represents common config items for Azure DNS and Azure Private DNS +// config represents common config items for Azure DNS and Azure Private DNS type config struct { Cloud string `json:"cloud" yaml:"cloud"` Environment azure.Environment `json:"-" yaml:"-"` diff --git a/provider/azure/common_test.go b/provider/azure/config_test.go similarity index 100% rename from provider/azure/common_test.go rename to provider/azure/config_test.go From 6eeef96b1474af472017c1a17c1fdf2ce5638a07 Mon Sep 17 00:00:00 2001 From: Alvaro Saurin Date: Tue, 23 Jun 2020 14:10:39 +0200 Subject: [PATCH 41/44] Support Ambassador Host resources as sources Ambassador can be configured with `Host` resources (based on the `Host` CRD) for defining the external DNS host name. This code adds a new source, `ambassador-host`, that looks for the `ambassador/ambassador` Service and and uses the `hostname` from the `Host` resource. Signed-off-by: Alvaro Saurin Signed-off-by: Flynn --- go.mod | 12 +- go.sum | 487 ++++++++++++++++++++++++++++++--- pkg/apis/externaldns/types.go | 2 +- source/ambassador_host.go | 283 +++++++++++++++++++ source/ambassador_host_test.go | 78 ++++++ source/store.go | 22 +- 6 files changed, 835 insertions(+), 49 deletions(-) create mode 100644 source/ambassador_host.go create mode 100644 source/ambassador_host_test.go diff --git a/go.mod b/go.mod index 06b632e7a..6cdc70b13 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/Azure/azure-sdk-for-go v45.1.0+incompatible github.com/Azure/go-autorest/autorest v0.11.10 github.com/Azure/go-autorest/autorest/adal v0.9.5 - github.com/Azure/go-autorest/autorest/azure/auth v0.5.3 github.com/Azure/go-autorest/autorest/to v0.4.0 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0 github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect @@ -19,6 +18,7 @@ require ( github.com/aws/aws-sdk-go v1.31.4 github.com/cloudflare/cloudflare-go v0.10.1 github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 + github.com/datawire/ambassador v1.6.0 github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba github.com/digitalocean/godo v1.36.0 github.com/dnsimple/dnsimple-go v0.60.0 @@ -46,7 +46,8 @@ require ( github.com/sanyu/dynectsoap v0.0.0-20181203081243-b83de5edc4e0 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200623155123-84df6c4b5301 github.com/sirupsen/logrus v1.6.0 - github.com/stretchr/testify v1.5.1 + github.com/smartystreets/gunit v1.3.4 // indirect + github.com/stretchr/testify v1.6.1 github.com/terra-farm/udnssdk v1.3.5 // indirect github.com/transip/gotransip v5.8.2+incompatible github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60 @@ -54,16 +55,19 @@ require ( github.com/vultr/govultr v0.4.2 go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875 go.uber.org/ratelimit v0.1.0 - golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e + golang.org/x/net v0.0.0-20200625001655-4c5254603344 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + golang.org/x/tools v0.0.0-20200708003708-134513de8882 // indirect google.golang.org/api v0.15.0 gopkg.in/ns1/ns1-go.v2 v2.0.0-20190322154155-0dafb5275fd1 - gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v2 v2.3.0 + honnef.co/go/tools v0.0.1-2020.1.4 // indirect istio.io/api v0.0.0-20200529165953-72dad51d4ffc istio.io/client-go v0.0.0-20200529172309-31c16ea3f751 k8s.io/api v0.18.8 k8s.io/apimachinery v0.18.8 k8s.io/client-go v0.18.8 + k8s.io/kubernetes v1.13.0 ) replace ( diff --git a/go.sum b/go.sum index 12e341d3b..8bd1d55b6 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= @@ -15,22 +16,20 @@ code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:s dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= git.blindage.org/21h/hcloud-dns v0.0.0-20200807003420-f768ffe03f8d h1:d6sdozgfqtgaOhjUn++lbo5siX3HELjcOUnbtrvVQi4= git.blindage.org/21h/hcloud-dns v0.0.0-20200807003420-f768ffe03f8d/go.mod h1:n26Twiii5jhkMC+Ocz/s8R73cBBcXRIwyTqQ+6bOZGo= +git.lukeshu.com/go/libsystemd v0.5.3/go.mod h1:FfDoP0i92r4p5Vn4NCLxvjkd7rCOe6otPa4L6hZg9WM= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v45.1.0+incompatible h1:kxtaPD8n2z5Za+9e3sKsYG2IX6PG2R6VXtgS7gAbh3A= github.com/Azure/azure-sdk-for-go v45.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.11.9/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= github.com/Azure/go-autorest/autorest v0.11.10 h1:j5sGbX7uj1ieYYkQ3Mpvewd4DCsEQ+ZeJpqnSM9pjnM= github.com/Azure/go-autorest/autorest v0.11.10/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.9.5 h1:Y3bBUV4rTuxenJJs41HU3qmqsb+auo+a3Lz+PlJPpL0= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/azure/auth v0.5.3 h1:lZifaPRAk1bqg5vGqreL6F8uLC5V0fDpY8nFvc3boFc= -github.com/Azure/go-autorest/autorest/azure/auth v0.5.3/go.mod h1:4bJZhUhcq8LB20TruwHbAQsmUs2Xh+QR7utuJpLXX3A= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= @@ -46,20 +45,37 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig v2.17.1+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= +github.com/Masterminds/squirrel v1.2.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA= +github.com/Masterminds/squirrel v1.4.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA= +github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/ahmetb/gen-crd-api-reference-docs v0.1.5/go.mod h1:P/XzJ+c2+khJKNKABcm2biRwk2QAuwbLf8DlXuaL7WM= -github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.11 h1:QGjNHMwoPYxE5NpOAc8kpd2KTY293/oFk5BWdjkza+k= -github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.11/go.mod h1:L+HB2uBoDgi3+r1pJEJcbGwyyHhd2QXaGsKLbDwtm8Q= github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0 h1:FJF58TWBaQnGqTIcziIP5/z3TTqWUn8fh26z03oZE2c= github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= @@ -79,31 +95,74 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/aliyun/alibaba-cloud-sdk-go v1.61.357 h1:3ynCSeUh9OtJLd/OzLapM1DLDv2g+0yyDdkLqSfZCaQ= github.com/aliyun/alibaba-cloud-sdk-go v1.61.357/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U= +github.com/aokoli/goutils v1.1.0/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.31.4 h1:YZ0uEYIWeanGuAomElHmRWMAbXVqrQixxgf2vtIjO6M= github.com/aws/aws-sdk-go v1.31.4/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.10.1 h1:d2CL6F9k2O0Ux0w27LgogJ5UOzZRj6a/hDPFqPP68d8= github.com/cloudflare/cloudflare-go v0.10.1/go.mod h1:C0Y6eWnTJPMK2ceuOxx2pjh78UUHihcXeTTHb8r7QjU= github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 h1:rdRS5BT13Iae9ssvcslol66gfOOXjaLYwqerEn/cl9s= github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381/go.mod h1:e5+USP2j8Le2M0Jo3qKPFnNhuo1wueU4nWHCXBOfQ14= +github.com/cncf/udpa v0.0.0-20200324003616-bae28a880fdb/go.mod h1:HNVadOiXCy7Jk3R2knJ+qm++zkncJxxBMpjdGgJ+UJc= github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20200324003616-bae28a880fdb/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -115,34 +174,59 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea h1:n2Ltr3SrfQlf/9nOna1DoGKxLx3qTSI8Ttl6Xrqp6mw= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/datawire/ambassador v1.6.0 h1:4KduhY/wqtv0jK8sMVQNtENHy9fmoXugsuFp/UrM0Ts= +github.com/datawire/ambassador v1.6.0/go.mod h1:mV5EhoG/NnHBsffmLnjrq+x4ZNkYDWFZXW9R+AueUiE= +github.com/datawire/pf v0.0.0-20180510150411-31a823f9495a/go.mod h1:H8uUmE8qqo7z9u30MYB9riLyRckPHOPBk9ZdCuH+dQQ= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC4G4As= +github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba h1:p6poVbjHDkKa+wtC8frBMwQtT3BmqGYBjzMwJ63tuR4= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/digitalocean/godo v1.36.0 h1:eRF8wNzHZyU7/wI3De/MQgiVSWdseDaf27bXj2gnOO0= github.com/digitalocean/godo v1.36.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU= -github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= -github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnsimple/dnsimple-go v0.60.0 h1:N+q+ML1CZGf+5r4udu9Opy7WJNtOaFT9aM86Af9gLhk= github.com/dnsimple/dnsimple-go v0.60.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg= +github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ecodia/golang-awaitility v0.0.0-20180710094957-fb55e59708c7/go.mod h1:etn7NbLy5UviLk20XMZbSn/0AigF3Zfx7wwaEZ3fyIk= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -150,12 +234,15 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.5/go.mod h1:OXl5to++W0ctG+EHWTFUjiypVxC/Y4VLc/KFU+al13s= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.3.0-java.0.20200609174644-bd816e4522c1/go.mod h1:bjmEhrMDubXDd0uKxnWwRmgSsiEv2CkJliIHnj6ETm8= github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exoscale/egoscale v0.18.1 h1:1FNZVk8jHUx0AvWhOZxLEDNlacTU0chMXUUNkm9EZaI= github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= @@ -163,18 +250,26 @@ github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99 h1:jmwW6QWvUO2O github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99/go.mod h1:4mP9w9+vYGw2jUx2+2v03IA+phyQQjNRR4AL3uxlNrs= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk= @@ -225,25 +320,41 @@ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2K github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY= github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= +github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= +github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -260,8 +371,13 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f h1:kSqKc8ouCLIBHqdj9a9xxhtxlZhNqbePClixA4HoM44= github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:YCHYtYb9c8Q7XgYVYjmJBPtFPKx5QvOcPxHZWjldabE= +github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= +github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= +github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -281,6 +397,7 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -291,50 +408,90 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsC github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= +github.com/gookit/color v1.2.3/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/infobloxopen/infoblox-go-client v0.0.0-20180606155407-61dc5f9b0a65 h1:FP5rOFP4ifbtFIjFHJmwhFrsbDyONILK/FNntl/Pou8= github.com/infobloxopen/infoblox-go-client v0.0.0-20180606155407-61dc5f9b0a65/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -342,6 +499,7 @@ github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -349,9 +507,14 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= @@ -365,10 +528,21 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kubermatic/glog-logrus v0.0.0-20180829085450-3fa5b9870d1d h1:JV46OtdhH2vVt8mJ1EWUE94k99vbN9fZs1WQ8kcEapU= github.com/kubermatic/glog-logrus v0.0.0-20180829085450-3fa5b9870d1d/go.mod h1:CHQ3o5KBH1PIS2Fb1mRLTIWO5YzP9kSUB3KoCICwlvA= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linki/instrumented_http v0.2.0 h1:zLhcB3Q/McQQqml3qd5kzdZ0cGnL3vquPFIW2338f5Y= github.com/linki/instrumented_http v0.2.0/go.mod h1:pjYbItoegfuVi2GUOMhEqzvm/SJKuEL3H0tc8QRLRFk= github.com/linode/linodego v0.19.0 h1:JxYBTxUcXcOlCwLMuugc7Il0RMtJ7riaddqz6gG/ACA= github.com/linode/linodego v0.19.0/go.mod h1:XOWXRHjqeU2uPS84tKLgfWIfTlv3TYzCS0io4GOQzEI= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/lyft/protoc-gen-star v0.4.10/go.mod h1:mE8fbna26u7aEA2QCVvvfBU/ZrPgocG1206xAFPcs94= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -376,27 +550,47 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw= github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/maxatome/go-testdeep v1.4.0 h1:vKQh3/lHKAMsxggya/fXB6fLbf70c7k6wlLveuS9sKE= github.com/maxatome/go-testdeep v1.4.0/go.mod h1:011SgQ6efzZYAen6fDn4BqQ+lUR72ysdyKe7Dyogw70= +github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.6/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo= github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mikkeloscar/knolog v0.0.0-20190326191552-80742771eb6b h1:5f5B1kp+QerGOF91q1qVJcUWWvXsVEN3OKiyEzAAjIM= github.com/mikkeloscar/knolog v0.0.0-20190326191552-80742771eb6b/go.mod h1:PizLs/1ddmVrXpFgWOGNmTJ2YHSWUkpUXMYuUkTo3Go= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/mitchellh/protoc-gen-go-json v0.0.0-20190813154521-ece073100ced/go.mod h1:VhkV06JZBMrulb3fNiUvM5Lmz3yc7ei3omzocl9UtCw= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -404,93 +598,176 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/nesv/go-dynect v0.6.0 h1:Ow/DiSm4LAISwnFku/FITSQHnU6pBvhQMsUE5Gu6Oq4= github.com/nesv/go-dynect v0.6.0/go.mod h1:GHRBRKzTwjAMhosHJQq/KrZaFkXIFyJ5zRE7thGXXrs= github.com/nic-at/rc0go v1.1.1 h1:bf2gTwYecJEh7qmnOEuarXKueZn4A8N08U1Uop3K8+s= github.com/nic-at/rc0go v1.1.1/go.mod h1:KEa3H5fmDNXCaXSqOeAZxkKnG/8ggr1OHIG25Ve7fjU= +github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/openshift/api v0.0.0-20200605231317-fb2a6ca106ae h1:cRqNH6AtRQwEqCpymMkaR2ePp08FBIYLkU7YusJeZJ8= github.com/openshift/api v0.0.0-20200605231317-fb2a6ca106ae/go.mod h1:l6TGeqJ92DrZBuWMNKcot1iZUHfbYSJyBWHGgg6Dn6s= github.com/openshift/build-machinery-go v0.0.0-20200424080330-082bf86082cc/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc= github.com/openshift/client-go v0.0.0-20200608144219-584632b8fc73 h1:JePLt9EpNLF/30KsSsArrzxGWPaUIvYUt8Fwnw9wlgM= github.com/openshift/client-go v0.0.0-20200608144219-584632b8fc73/go.mod h1:+66gk3dEqw9e+WoiXjJFzWlS1KGhj9ZRHi/RI/YG/ZM= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/oracle/oci-go-sdk v21.4.0+incompatible h1:ORX+RXBuG/INBs+rgx6S3qoShEZ5+rwEEyRn2s6bPiw= github.com/oracle/oci-go-sdk v21.4.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 h1:37VE5TYj2m/FLA9SNr4z0+A0JefvTmR60Zwf8XSEV7c= github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ= github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso= github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/projectcontour/contour v1.5.0 h1:4zz4XWKKb1Nk2zXVQ27ZpoTivjG2DQvYLwrStcK8Fqc= github.com/projectcontour/contour v1.5.0/go.mod h1:y1MEsorL/Q8lBG5BZz8Gzryi9L5ryVALOuHicmAdfW8= +github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc= +github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sanyu/dynectsoap v0.0.0-20181203081243-b83de5edc4e0 h1:vOcHdR1nu7DO4BAx1rwzdHV7jQTzW3gqcBT5qxHSc6A= github.com/sanyu/dynectsoap v0.0.0-20181203081243-b83de5edc4e0/go.mod h1:FeplEtXXejBYC4NPAFTrs5L7KuK+5RL9bf5nB2vZe9o= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200623155123-84df6c4b5301 h1:qj0du14RIOnmePII/eTlw1aHKDYL6zxDIk/Dq7Tef9k= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200623155123-84df6c4b5301/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -504,23 +781,34 @@ github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:X github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA= github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= +github.com/smartystreets/gunit v1.3.4 h1:iHc8Rfhb/uCOc9a3KGuD3ut22L+hLIVaqR1o5fS6zC4= +github.com/smartystreets/gunit v1.3.4/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak= github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= @@ -529,20 +817,27 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/terra-farm/udnssdk v1.3.5 h1:MNR3adfuuEK/l04+jzo8WW/0fnorY+nW515qb3vEr6I= github.com/terra-farm/udnssdk v1.3.5/go.mod h1:8RnM56yZTR7mYyUIvrDgXzdRaEyFIzqdEi7+um26Sv8= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/transip/gotransip v5.8.2+incompatible h1:aNJhw/w/3QBqFcHAIPz1ytoK5FexeMzbUCGrrhWr3H0= github.com/transip/gotransip v5.8.2+incompatible/go.mod h1:uacMoJVmrfOcscM4Bi5NVg708b7c6rz2oDTWqa7i2Ic= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60 h1:n7unetnX8WWTc0U85h/0+dJoLWLqoaJwowXB9RkBdxU= github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60/go.mod h1:43vmy6GEvRuVMpGEWfJ/JoEM6RIqUQI1/tb8JqZR1zI= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556 h1:UbVjBjgJUYGD8MlobEdOR+yTeNqaNa2Gf1/nskVNCSE= github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg= @@ -550,12 +845,23 @@ github.com/vultr/govultr v0.4.2 h1:9i8xKZ+xp6vwZ9raqHoBLzhB4wCnMj7nOQTj5YIRLWY= github.com/vultr/govultr v0.4.2/go.mod h1:TUuUizMOFc7z+PNMssb6iGjKjQfpw5arIaOLfocVudQ= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= @@ -564,33 +870,50 @@ go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875/go.mod h1:dnLIgRNXw go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw= go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -606,17 +929,24 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -628,8 +958,10 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -640,8 +972,11 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -653,14 +988,19 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180425194835-bb9c189858d9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -671,29 +1011,40 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -717,11 +1068,21 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190929041059-e7abfedfabcf/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200115044656-831fdb1e1868/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200708003708-134513de8882 h1:x4Two2lSwHxTqR+eal4lB4ydUnTvmDDpPQeL92ZHDgA= +golang.org/x/tools v0.0.0-20200708003708-134513de8882/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= @@ -730,6 +1091,8 @@ gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3m gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -737,21 +1100,25 @@ google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1 h1:aQktFqmDE2yjveXJlVIfslDFmFnUXSqG0i6KRcJAeMc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba h1:pRj9OXZbwNtbtZtOB4dLwfK4u+EVRMvP+e9zKkg2grM= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -761,15 +1128,20 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= +gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0= gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -785,6 +1157,7 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -792,78 +1165,114 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +helm.sh/helm/v3 v3.2.4/go.mod h1:ZaXz/vzktgwjyGGFbUWtIQkscfE7WYoRGP2szqAFHR0= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= istio.io/api v0.0.0-20200529165953-72dad51d4ffc h1:cR9GmbIBAz3FnY3tgs1SRn/uiznhtvG+mZBfD1p2vIA= istio.io/api v0.0.0-20200529165953-72dad51d4ffc/go.mod h1:kyq3g5w42zl/AKlbzDGppYpGMQYMYMyZKeq0/eexML8= istio.io/client-go v0.0.0-20200529172309-31c16ea3f751 h1:yH62fTmV+5l1XVTWcomsc1jjH/oH9u/tTgn5NVmdIac= istio.io/client-go v0.0.0-20200529172309-31c16ea3f751/go.mod h1:4SGvmmus5HNFdqQsIL+uQO1PbAhjQKtSjMTqwsvYHlg= +istio.io/gogo-genproto v0.0.0-20190904133402-ee07f2785480/go.mod h1:uKtbae4K9k2rjjX4ToV0l6etglbc1i7gqQ94XdkshzY= istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a h1:w7zILua2dnYo9CxImhpNW4NE/8ZxEoc/wfBfHrhUhrE= istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a/go.mod h1:OzpAts7jljZceG4Vqi5/zXy/pOg1b209T3jb7Nv5wIs= k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48= k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= +k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= k8s.io/api v0.18.1/go.mod h1:3My4jorQWzSs5a+l7Ge6JBbIxChLnY8HnuT58ZWolss= k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= -k8s.io/api v0.18.3 h1:2AJaUQdgUZLoDZHrun21PW2Nx9+ll6cUzvn3IKhSIn0= k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA= +k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4= k8s.io/api v0.18.8 h1:aIKUzJPb96f3fKec2lxtY7acZC9gQNDLVhfSGpxBAC4= k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY= k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY= k8s.io/apiextensions-apiserver v0.17.0/go.mod h1:XiIFUakZywkUl54fVXa7QTEHcqQz9HG55nHd1DCoHj8= +k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo= +k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY= +k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio= k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.18.1/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= -k8s.io/apimachinery v0.18.3 h1:pOGcbVAhxADgUYnjS08EFXs9QMl8qaH5U4fr5LGUrSk= k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apimachinery v0.18.8 h1:jimPrycCqgx2QPearX3to1JePz7wSbVLq+7PdBTTwQ0= k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg= k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg= +k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw= +k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw= +k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8= +k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ= +k8s.io/cli-runtime v0.18.4/go.mod h1:9/hS/Cuf7NVzWR5F/5tyS6xsnclxoPLVtwhnkJG1Y4g= k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk= k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= +k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8= k8s.io/client-go v0.18.1/go.mod h1:iCikYRiXOj/yRRFE/aWqrpPtDt4P2JVWhtHkmESTcfY= k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= -k8s.io/client-go v0.18.3 h1:QaJzz92tsN67oorwzmoB0a9r9ZVHuD5ryjbCKP0U22k= -k8s.io/client-go v0.18.3/go.mod h1:4a/dpQEvzAhT1BbuWW09qvIaGw6Gbu1gZYiQZIi1DMw= k8s.io/client-go v0.18.3/go.mod h1:4a/dpQEvzAhT1BbuWW09qvIaGw6Gbu1gZYiQZIi1DMw= +k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g= k8s.io/client-go v0.18.8 h1:SdbLpIxk5j5YbFr1b7fq8S7mDgDjYmUxSbszyoesoDM= k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU= -k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269 h1:d8Fm55A+7HOczX58+x9x+nJnJ1Devt1aCrWVIPaw/Vg= k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE= k8s.io/code-generator v0.17.0/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= +k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= +k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= k8s.io/code-generator v0.18.3/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= +k8s.io/code-generator v0.18.4/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA= k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= +k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= +k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM= +k8s.io/component-base v0.18.4/go.mod h1:7jr/Ef5PGmKwQhyAz/pjByxJbC58mhKAhiaDu0vXfPk= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20191120174120-e74f70b9b27e/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/helm v2.16.9+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= +k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kubectl v0.18.0/go.mod h1:LOkWx9Z5DXMEg5KtOjHhRiC1fqJPLyCr3KtQgEolCkU= +k8s.io/kubernetes v1.13.0 h1:qTfB+u5M92k2fCCCVP2iuhgwwSOv1EkAkvQY1tQODD8= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/metrics v0.18.0/go.mod h1:8aYTW18koXqjLVKL7Ds05RPMX9ipJZI3mywYvBOxXd4= k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE= +k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns= +sigs.k8s.io/controller-runtime v0.6.1 h1:LcK2+nk0kmaOnKGN+vBcWHqY5WDJNJNB/c5pW+sU8fc= +sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gEORz0efEja7A= sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA= sigs.k8s.io/controller-tools v0.2.9/go.mod h1:ArP7w60JQKkZf7UU2oWTVnEhoNGA+sOMyuSuS+JFNDQ= +sigs.k8s.io/controller-tools v0.3.1-0.20200517180335-820a4a27ea84/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI= +sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/kustomize/kyaml v0.1.1/go.mod h1:/NdPPfrperSCGjm55cwEro1loBVtbtVIXSb7FguK6uk= sigs.k8s.io/service-apis v0.0.0-20200213014236-51691dd89266/go.mod h1:s6Cwc0sg5w4xxZ/HEr88qPkQ4CITr80lnMpYZUzL9VY= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= @@ -877,3 +1286,5 @@ sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UA sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index ece99189d..c436b61f3 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -318,7 +318,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(source.DefaultRoutegroupVersion).StringVar(&cfg.SkipperRouteGroupVersion) // Flags related to processing sources - app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, crd, empty, skipper-routegroup,openshift-route)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route") + app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host") app.Flag("namespace", "Limit sources of endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace) app.Flag("annotation-filter", "Filter sources managed by external-dns via annotation using label selector semantics (default: all sources)").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter) diff --git a/source/ambassador_host.go b/source/ambassador_host.go new file mode 100644 index 000000000..2f4579e5d --- /dev/null +++ b/source/ambassador_host.go @@ -0,0 +1,283 @@ +/* +Copyright 2020 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 source + +import ( + "context" + "fmt" + "sort" + "strings" + "time" + + ambassador "github.com/datawire/ambassador/pkg/api/getambassador.io/v2" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/dynamic/dynamicinformer" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/cache" + api "k8s.io/kubernetes/pkg/apis/core" + + "sigs.k8s.io/external-dns/endpoint" +) + +// ambHostAnnotation is the annotation in the Host that maps to a Service +const ambHostAnnotation = "external-dns.ambassador-service" + +// groupName is the group name for the Ambassador API +const groupName = "getambassador.io" + +var schemeGroupVersion = schema.GroupVersion{Group: groupName, Version: "v2"} + +var ambHostGVR = schemeGroupVersion.WithResource("hosts") + +// ambassadorHostSource is an implementation of Source for Ambassador Host objects. +// The IngressRoute implementation uses the spec.virtualHost.fqdn value for the hostname. +// Use targetAnnotationKey to explicitly set Endpoint. +type ambassadorHostSource struct { + dynamicKubeClient dynamic.Interface + kubeClient kubernetes.Interface + namespace string + ambassadorHostInformer informers.GenericInformer + unstructuredConverter *unstructuredConverter +} + +// NewAmbassadorHostSource creates a new ambassadorHostSource with the given config. +func NewAmbassadorHostSource( + dynamicKubeClient dynamic.Interface, + kubeClient kubernetes.Interface, + namespace string) (Source, error) { + var err error + + // Use shared informer to listen for add/update/delete of Host in the specified namespace. + // Set resync period to 0, to prevent processing when nothing has changed. + informerFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynamicKubeClient, 0, namespace, nil) + ambassadorHostInformer := informerFactory.ForResource(ambHostGVR) + + // Add default resource event handlers to properly initialize informer. + ambassadorHostInformer.Informer().AddEventHandler( + cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + }, + }, + ) + + // TODO informer is not explicitly stopped since controller is not passing in its channel. + informerFactory.Start(wait.NeverStop) + + // wait for the local cache to be populated. + err = poll(time.Second, 60*time.Second, func() (bool, error) { + return ambassadorHostInformer.Informer().HasSynced(), nil + }) + if err != nil { + return nil, errors.Wrapf(err, "failed to sync cache") + } + + uc, err := newUnstructuredConverter() + if err != nil { + return nil, errors.Wrapf(err, "failed to setup Unstructured Converter") + } + + return &ambassadorHostSource{ + dynamicKubeClient: dynamicKubeClient, + kubeClient: kubeClient, + namespace: namespace, + ambassadorHostInformer: ambassadorHostInformer, + unstructuredConverter: uc, + }, nil +} + +// Endpoints returns endpoint objects for each host-target combination that should be processed. +// Retrieves all Hosts in the source's namespace(s). +func (sc *ambassadorHostSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) { + hosts, err := sc.ambassadorHostInformer.Lister().ByNamespace(sc.namespace).List(labels.Everything()) + if err != nil { + return nil, err + } + + endpoints := []*endpoint.Endpoint{} + for _, hostObj := range hosts { + unstructuredHost, ok := hostObj.(*unstructured.Unstructured) + if !ok { + return nil, errors.New("could not convert") + } + + host := &ambassador.Host{} + err := sc.unstructuredConverter.scheme.Convert(unstructuredHost, host, nil) + if err != nil { + return nil, err + } + + fullname := fmt.Sprintf("%s/%s", host.Namespace, host.Name) + + // look for the "exernal-dns.ambassador-service" annotation. If it is not there then just ignore this `Host` + service, found := host.Annotations[ambHostAnnotation] + if !found { + log.Debugf("Host %s ignored: no annotation %q found", fullname, ambHostAnnotation) + continue + } + + targets, err := sc.targetsFromAmbassadorLoadBalancer(ctx, service) + if err != nil { + return nil, err + } + + hostEndpoints, err := sc.endpointsFromHost(ctx, host, targets) + if err != nil { + return nil, err + } + if len(hostEndpoints) == 0 { + log.Debugf("No endpoints could be generated from Host %s", fullname) + continue + } + + log.Debugf("Endpoints generated from Host: %s: %v", fullname, hostEndpoints) + endpoints = append(endpoints, hostEndpoints...) + } + + for _, ep := range endpoints { + sort.Sort(ep.Targets) + } + + return endpoints, nil +} + +// endpointsFromHost extracts the endpoints from a Host object +func (sc *ambassadorHostSource) endpointsFromHost(ctx context.Context, host *ambassador.Host, targets endpoint.Targets) ([]*endpoint.Endpoint, error) { + var endpoints []*endpoint.Endpoint + + providerSpecific := endpoint.ProviderSpecific{} + setIdentifier := "" + + annotations := host.Annotations + ttl, err := getTTLFromAnnotations(annotations) + if err != nil { + return nil, err + } + + if host.Spec != nil { + hostname := host.Spec.Hostname + if hostname != "" { + endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier)...) + } + } + + return endpoints, nil +} + +func (sc *ambassadorHostSource) targetsFromAmbassadorLoadBalancer(ctx context.Context, service string) (targets endpoint.Targets, err error) { + lbNamespace, lbName, err := parseAmbLoadBalancerService(service) + if err != nil { + return nil, err + } + + svc, err := sc.kubeClient.CoreV1().Services(lbNamespace).Get(ctx, lbName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + for _, lb := range svc.Status.LoadBalancer.Ingress { + if lb.IP != "" { + targets = append(targets, lb.IP) + } + if lb.Hostname != "" { + targets = append(targets, lb.Hostname) + } + } + + return +} + +// parseAmbLoadBalancerService returns a name/namespace tuple from the annotation in +// an Ambassador Host CRD +// +// This is a thing because Ambassador has historically supported cross-namespace +// references using a name.namespace syntax, but here we want to also support +// namespace/name. +// +// Returns namespace, name, error. + +func parseAmbLoadBalancerService(service string) (namespace, name string, err error) { + // Start by assuming that we have namespace/name. + parts := strings.Split(service, "/") + + if len(parts) == 1 { + // No "/" at all, so let's try for name.namespace. To be consistent with the + // rest of Ambassador, use SplitN to limit this to one split, so that e.g. + // svc.foo.bar uses service "svc" in namespace "foo.bar". + parts = strings.SplitN(service, ".", 2) + + if len(parts) == 2 { + // We got a namespace, great. + name := parts[0] + namespace := parts[1] + + return namespace, name, nil + } + + // If here, we have no separator, so the whole string is the service, and + // we can assume the default namespace. + name := service + namespace := api.NamespaceDefault + + return namespace, name, nil + } else if len(parts) == 2 { + // This is "namespace/name". Note that the name could be qualified, + // which is fine. + namespace := parts[0] + name := parts[1] + + return namespace, name, nil + } + + // If we got here, this string is simply ill-formatted. Return an error. + return "", "", errors.New(fmt.Sprintf("invalid external-dns service: %s", service)) +} + +func (sc *ambassadorHostSource) AddEventHandler(ctx context.Context, handler func()) { +} + +// unstructuredConverter handles conversions between unstructured.Unstructured and Ambassador types +type unstructuredConverter struct { + // scheme holds an initializer for converting Unstructured to a type + scheme *runtime.Scheme +} + +// newUnstructuredConverter returns a new unstructuredConverter initialized +func newUnstructuredConverter() (*unstructuredConverter, error) { + uc := &unstructuredConverter{ + scheme: runtime.NewScheme(), + } + + // Setup converter to understand custom CRD types + ambassador.AddToScheme(uc.scheme) + + // Add the core types we need + if err := scheme.AddToScheme(uc.scheme); err != nil { + return nil, err + } + + return uc, nil +} diff --git a/source/ambassador_host_test.go b/source/ambassador_host_test.go new file mode 100644 index 000000000..bb960600e --- /dev/null +++ b/source/ambassador_host_test.go @@ -0,0 +1,78 @@ +/* +Copyright 2019 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 source + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type AmbassadorSuite struct { + suite.Suite +} + +func TestAmbassadorSource(t *testing.T) { + suite.Run(t, new(AmbassadorSuite)) + t.Run("Interface", testAmbassadorSourceImplementsSource) +} + +// testAmbassadorSourceImplementsSource tests that ambassadorHostSource is a valid Source. +func testAmbassadorSourceImplementsSource(t *testing.T) { + require.Implements(t, (*Source)(nil), new(ambassadorHostSource)) +} + +// TestParseAmbLoadBalancerService tests our parsing of Ambassador service info. +func TestParseAmbLoadBalancerService(t *testing.T) { + vectors := []struct { + input string + ns string + svc string + errstr string + }{ + {"svc", "default", "svc", ""}, + {"ns/svc", "ns", "svc", ""}, + {"svc.ns", "ns", "svc", ""}, + {"svc.ns.foo.bar", "ns.foo.bar", "svc", ""}, + {"ns/svc/foo/bar", "", "", "invalid external-dns service: ns/svc/foo/bar"}, + {"ns/svc/foo.bar", "", "", "invalid external-dns service: ns/svc/foo.bar"}, + {"ns.foo/svc/bar", "", "", "invalid external-dns service: ns.foo/svc/bar"}, + } + + for _, v := range vectors { + ns, svc, err := parseAmbLoadBalancerService(v.input) + + errstr := "" + + if err != nil { + errstr = err.Error() + } + + if v.ns != ns { + t.Errorf("%s: got ns \"%s\", wanted \"%s\"", v.input, ns, v.ns) + } + + if v.svc != svc { + t.Errorf("%s: got svc \"%s\", wanted \"%s\"", v.input, svc, v.svc) + } + + if v.errstr != errstr { + t.Errorf("%s: got err \"%s\", wanted \"%s\"", v.input, errstr, v.errstr) + } + } +} diff --git a/source/store.go b/source/store.go index 3f3a1321e..0cb6641fb 100644 --- a/source/store.go +++ b/source/store.go @@ -83,12 +83,12 @@ type SingletonClientGenerator struct { kubeClient kubernetes.Interface istioClient *istioclient.Clientset cfClient *cfclient.Client - contourClient dynamic.Interface + dynKubeClient dynamic.Interface openshiftClient openshift.Interface kubeOnce sync.Once istioOnce sync.Once cfOnce sync.Once - contourOnce sync.Once + dynCliOnce sync.Once openshiftOnce sync.Once } @@ -134,13 +134,13 @@ func NewCFClient(cfAPIEndpoint string, cfUsername string, cfPassword string) (*c return client, nil } -// DynamicKubernetesClient generates a contour client if it was not created before +// DynamicKubernetesClient generates a dynamic client if it was not created before func (p *SingletonClientGenerator) DynamicKubernetesClient() (dynamic.Interface, error) { var err error - p.contourOnce.Do(func() { - p.contourClient, err = NewDynamicKubernetesClient(p.KubeConfig, p.APIServerURL, p.RequestTimeout) + p.dynCliOnce.Do(func() { + p.dynKubeClient, err = NewDynamicKubernetesClient(p.KubeConfig, p.APIServerURL, p.RequestTimeout) }) - return p.contourClient, err + return p.dynKubeClient, err } // OpenShiftClient generates an openshift client if it was not created before @@ -213,6 +213,16 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err return nil, err } return NewCloudFoundrySource(cfClient) + case "ambassador-host": + kubernetesClient, err := p.KubeClient() + if err != nil { + return nil, err + } + dynamicClient, err := p.DynamicKubernetesClient() + if err != nil { + return nil, err + } + return NewAmbassadorHostSource(dynamicClient, kubernetesClient, cfg.Namespace) case "contour-ingressroute": kubernetesClient, err := p.KubeClient() if err != nil { From 94be444f5d914a89d44fa3cff85cb8bdf2d7a695 Mon Sep 17 00:00:00 2001 From: Patrik Cyvoct Date: Mon, 9 Nov 2020 10:28:44 +0100 Subject: [PATCH 42/44] fix(scaleway): only use absolute CNAMEs Signed-off-by: Patrik Cyvoct --- CHANGELOG.md | 1 + provider/scaleway/scaleway.go | 14 ++++++++++++-- provider/scaleway/scaleway_test.go | 4 ++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b52a37ad..74bee768a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Update contributing section in README (#1760) @seanmalloy - Option to cache AWS zones list @bpineau - Refactor, enhance and test Akamai provider and documentation (#1846) @edglynes +- Fix: only use absolute CNAMEs in Scaleway provider (#1859) @Sh4d1 ## v0.7.3 - 2020-08-05 diff --git a/provider/scaleway/scaleway.go b/provider/scaleway/scaleway.go index 3ee4f8303..df4677a79 100644 --- a/provider/scaleway/scaleway.go +++ b/provider/scaleway/scaleway.go @@ -269,8 +269,13 @@ func endpointToScalewayRecords(zoneName string, ep *endpoint.Endpoint) []*domain records := []*domain.Record{} for _, target := range ep.Targets { + finalTargetName := target + if domain.RecordType(ep.RecordType) == domain.RecordTypeCNAME { + finalTargetName = provider.EnsureTrailingDot(target) + } + records = append(records, &domain.Record{ - Data: target, + Data: finalTargetName, Name: strings.Trim(strings.TrimSuffix(ep.DNSName, zoneName), ". "), Priority: priority, TTL: ttl, @@ -285,9 +290,14 @@ func endpointToScalewayRecordsChangeDelete(zoneName string, ep *endpoint.Endpoin records := []*domain.RecordChange{} for _, target := range ep.Targets { + finalTargetName := target + if domain.RecordType(ep.RecordType) == domain.RecordTypeCNAME { + finalTargetName = provider.EnsureTrailingDot(target) + } + records = append(records, &domain.RecordChange{ Delete: &domain.RecordChangeDelete{ - Data: target, + Data: finalTargetName, Name: strings.Trim(strings.TrimSuffix(ep.DNSName, zoneName), ". "), Type: domain.RecordType(ep.RecordType), }, diff --git a/provider/scaleway/scaleway_test.go b/provider/scaleway/scaleway_test.go index 56b5f45d7..a0a67461e 100644 --- a/provider/scaleway/scaleway_test.go +++ b/provider/scaleway/scaleway_test.go @@ -93,7 +93,7 @@ func (m *mockScalewayDomain) ListDNSZoneRecords(req *domain.ListDNSZoneRecordsRe Type: domain.RecordTypeA, }, { - Data: "test.example.com", + Data: "test.example.com.", Name: "two", TTL: 600, Priority: 30, @@ -330,7 +330,7 @@ func TestScalewayProvider_generateApplyRequests(t *testing.T) { Add: &domain.RecordChangeAdd{ Records: []*domain.Record{ { - Data: "example.com", + Data: "example.com.", Name: "", TTL: 600, Type: domain.RecordTypeCNAME, From b65b945e27050ced09a21ddf27a4b88806b708bb Mon Sep 17 00:00:00 2001 From: Raffaele Di Fazio Date: Thu, 28 Jan 2021 09:17:37 +0100 Subject: [PATCH 43/44] corrects broken links in alb-ingress tutorial --- docs/tutorials/alb-ingress.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/alb-ingress.md b/docs/tutorials/alb-ingress.md index 08c40c3bb..eb7a19139 100644 --- a/docs/tutorials/alb-ingress.md +++ b/docs/tutorials/alb-ingress.md @@ -2,7 +2,7 @@ This tutorial describes how to use ExternalDNS with the [aws-alb-ingress-controller][1]. -[1]: https://kubernetes-sigs.github.io/aws-alb-ingress-controller/ +[1]: https://kubernetes-sigs.github.io/aws-load-balancer-controller ## Setting up ExternalDNS and aws-alb-ingress-controller @@ -14,12 +14,12 @@ this is not required. For help setting up the ALB Ingress Controller, follow the [Setup Guide][2]. -[2]: https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/controller/setup/ +[2]: https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/deploy/installation/ Note that the ALB ingress controller uses the same tags for [subnet auto-discovery][3] as Kubernetes does with the AWS cloud provider. -[3]: https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/controller/config/#subnet-auto-discovery +[3]: https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/deploy/subnet_discovery/ In the examples that follow, it is assumed that you configured the ALB Ingress Controller with the `ingress-class=alb` argument (not to be confused with the From 1e4c1e299da5d6e0109a26e34b57ecffdbbd7a53 Mon Sep 17 00:00:00 2001 From: Dustin Scott Date: Thu, 4 Feb 2021 15:14:48 -0600 Subject: [PATCH 44/44] Add RFC3645 support for secure updates with GSS-TSIG --- docs/tutorials/rfc2136.md | 94 ++++++++++++++++++- go.mod | 5 +- go.sum | 45 +++++++++ main.go | 2 +- pkg/apis/externaldns/types.go | 9 ++ pkg/apis/externaldns/validation/validation.go | 10 ++ .../externaldns/validation/validation_test.go | 60 ++++++++++++ provider/rfc2136/rfc2136.go | 75 +++++++++++++-- provider/rfc2136/rfc2136_test.go | 2 +- 9 files changed, 284 insertions(+), 18 deletions(-) diff --git a/docs/tutorials/rfc2136.md b/docs/tutorials/rfc2136.md index 7a9082661..d8977609a 100644 --- a/docs/tutorials/rfc2136.md +++ b/docs/tutorials/rfc2136.md @@ -220,6 +220,8 @@ spec: - name: external-dns image: k8s.gcr.io/external-dns/external-dns:v0.7.3 args: + - --registry=txt + - --txt-prefix=external-dns- - --txt-owner-id=k8s - --provider=rfc2136 - --rfc2136-host=192.168.0.1 @@ -260,6 +262,8 @@ spec: - name: external-dns image: k8s.gcr.io/external-dns/external-dns:v0.7.3 args: + - --registry=txt + - --txt-prefix=external-dns- - --txt-owner-id=k8s - --provider=rfc2136 - --rfc2136-host=192.168.0.1 @@ -273,17 +277,19 @@ spec: - --domain-filter=k8s.example.org ``` -## Microsoft DNS +## Microsoft DNS (Insecure Updates) While `external-dns` was not developed or tested against Microsoft DNS, it can be configured to work against it. YMMV. -### DNS-side configuration +### Insecure Updates + +#### DNS-side configuration 1. Create a DNS zone 2. Enable insecure dynamic updates for the zone 3. Enable Zone Transfers from all servers -### `external-dns` configuration +#### `external-dns` configuration You'll want to configure `external-dns` similarly to the following: @@ -298,4 +304,84 @@ You'll want to configure `external-dns` similarly to the following: ... ``` -Since Microsoft DNS does not support secure updates via TSIG, this will let `external-dns` make insecure updates. Do this at your own risk. +### Secure Updates Using RFC3645 (GSS-TSIG) + +### DNS-side configuration + +1. Create a DNS zone +2. Enable secure dynamic updates for the zone +3. Enable Zone Transfers from all servers + + +#### Kerberos Configuration + +DNS with secure updates relies upon a valid Kerberos configuration running within the `external-dns` container. At this time, you will need to create a ConfigMap for the `external-dns` container to use and mount it in your deployment. Below is an example of a working Kerberos configuration inside a ConfigMap definition. This may be different depending on many factors in your environment: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + creationTimestamp: null + name: krb5.conf +data: + krb5.conf: | + [logging] + default = FILE:/var/log/krb5libs.log + kdc = FILE:/var/log/krb5kdc.log + admin_server = FILE:/var/log/kadmind.log + + [libdefaults] + dns_lookup_realm = false + ticket_lifetime = 24h + renew_lifetime = 7d + forwardable = true + rdns = false + pkinit_anchors = /etc/pki/tls/certs/ca-bundle.crt + default_ccache_name = KEYRING:persistent:%{uid} + + default_realm = YOURDOMAIN.COM + + [realms] + YOURDOMAIN.COM = { + kdc = dc1.yourdomain.com + admin_server = dc1.yourdomain.com + } + + [domain_realm] + yourdomain.com = YOURDOMAIN.COM + .yourdomain.com = YOURDOMAIN.COM +``` + +Once the ConfigMap is created, the container `external-dns` container needs to be told to mount that ConfigMap as a volume at the default Kerberos configuration location. The pod spec should include a similar configuration to the following: + +```yaml +... + volumeMounts: + - mountPath: /etc/krb5.conf + name: kerberos-config-volume + subPath: krb5.conf +... + volumes: + - configMap: + defaultMode: 420 + name: krb5.conf + name: kerberos-config-volume +... +``` + +#### `external-dns` configuration + +You'll want to configure `external-dns` similarly to the following: + +```text +... + - --provider=rfc2136 + - --rfc2136-gss-tsig + - --rfc2136-host=123.123.123.123 + - --rfc2136-port=53 + - --rfc2136-zone=your-domain.com + - --rfc2136-kerberos-username=your-domain-account + - --rfc2136-kerberos-password=your-domain-password + - --rfc2136-tsig-axfr # needed to enable zone transfers, which is required for deletion of records. +... +``` \ No newline at end of file diff --git a/go.mod b/go.mod index 6cdc70b13..a4ec1c2c2 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c // indirect github.com/aliyun/alibaba-cloud-sdk-go v1.61.357 github.com/aws/aws-sdk-go v1.31.4 + github.com/bodgit/tsig v0.0.2 github.com/cloudflare/cloudflare-go v0.10.1 github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 github.com/datawire/ambassador v1.6.0 @@ -33,7 +34,7 @@ require ( github.com/linki/instrumented_http v0.2.0 github.com/linode/linodego v0.19.0 github.com/maxatome/go-testdeep v1.4.0 - github.com/miekg/dns v1.1.30 + github.com/miekg/dns v1.1.36-0.20210109083720-731b191cabd1 github.com/nesv/go-dynect v0.6.0 github.com/nic-at/rc0go v1.1.1 github.com/openshift/api v0.0.0-20200605231317-fb2a6ca106ae @@ -55,7 +56,7 @@ require ( github.com/vultr/govultr v0.4.2 go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875 go.uber.org/ratelimit v0.1.0 - golang.org/x/net v0.0.0-20200625001655-4c5254603344 + golang.org/x/net v0.0.0-20201224014010-6772e930b67b golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/tools v0.0.0-20200708003708-134513de8882 // indirect google.golang.org/api v0.15.0 diff --git a/go.sum b/go.sum index 8bd1d55b6..d9e31a4a0 100644 --- a/go.sum +++ b/go.sum @@ -92,6 +92,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro= github.com/aliyun/alibaba-cloud-sdk-go v1.61.357 h1:3ynCSeUh9OtJLd/OzLapM1DLDv2g+0yyDdkLqSfZCaQ= github.com/aliyun/alibaba-cloud-sdk-go v1.61.357/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= @@ -124,6 +125,10 @@ github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngE github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bodgit/tsig v0.0.2 h1:seNt23SrPW8dkWoyRYzdeuqFEzr+lDc0dAJvo94xB8U= +github.com/bodgit/tsig v0.0.2/go.mod h1:0mYe0t9it36SOvDQyeFekc7bLtvljFz7H9vHS/nYbgc= +github.com/bodgit/tsig v1.1.1 h1:SViReRa8KyaweqdJ3ojdYqIE3xDyJlR3G+6wAsSbLCo= +github.com/bodgit/tsig v1.1.1/go.mod h1:8LZ3Mn7AVZHH8GN2ArvzB7msHfLjoptWsdPEJRSw/uo= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= @@ -231,6 +236,7 @@ github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkg github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815/go.mod h1:wYFFK4LYXbX7j+76mOq7aiC/EAw2S22CrzPHqgsisPw= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.5/go.mod h1:OXl5to++W0ctG+EHWTFUjiypVxC/Y4VLc/KFU+al13s= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -421,6 +427,8 @@ github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= @@ -443,6 +451,7 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplb github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -452,6 +461,8 @@ github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjh github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= @@ -459,6 +470,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -484,7 +497,21 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/infobloxopen/infoblox-go-client v0.0.0-20180606155407-61dc5f9b0a65 h1:FP5rOFP4ifbtFIjFHJmwhFrsbDyONILK/FNntl/Pou8= github.com/infobloxopen/infoblox-go-client v0.0.0-20180606155407-61dc5f9b0a65/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.1 h1:IGSJfqBzMS6TA0oJ7DxXdyzPK563QHa8T2IqER2ggyQ= +github.com/jcmturner/gokrb5/v8 v8.4.1/go.mod h1:T1hnNppQsBtxW0tCHMHTkAt8n/sABdzZgZdoFrZaZNM= +github.com/jcmturner/rpc/v2 v2.0.2 h1:gMB4IwRXYsWw4Bc6o/az2HJgFUA1ffSh90i26ZJ6Xl0= +github.com/jcmturner/rpc/v2 v2.0.2/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/copier v0.1.0 h1:Vh8xALtH3rrKGB/XIRe5d0yCTHPZFauWPLvdpDAbi88= +github.com/jinzhu/copier v0.1.0/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -574,6 +601,9 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.6/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo= github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.36-0.20210109083720-731b191cabd1 h1:kZZmnTeY2r+88mDNCVV/uCXL2gG3rkVPTN9jcYfGQcI= +github.com/miekg/dns v1.1.36-0.20210109083720-731b191cabd1/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mikkeloscar/knolog v0.0.0-20190326191552-80742771eb6b h1:5f5B1kp+QerGOF91q1qVJcUWWvXsVEN3OKiyEzAAjIM= github.com/mikkeloscar/knolog v0.0.0-20190326191552-80742771eb6b/go.mod h1:PizLs/1ddmVrXpFgWOGNmTJ2YHSWUkpUXMYuUkTo3Go= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -660,6 +690,7 @@ github.com/openshift/api v0.0.0-20200605231317-fb2a6ca106ae/go.mod h1:l6TGeqJ92D github.com/openshift/build-machinery-go v0.0.0-20200424080330-082bf86082cc/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc= github.com/openshift/client-go v0.0.0-20200608144219-584632b8fc73 h1:JePLt9EpNLF/30KsSsArrzxGWPaUIvYUt8Fwnw9wlgM= github.com/openshift/client-go v0.0.0-20200608144219-584632b8fc73/go.mod h1:+66gk3dEqw9e+WoiXjJFzWlS1KGhj9ZRHi/RI/YG/ZM= +github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b/go.mod h1:tNrEB5k8SI+g5kOlsCmL2ELASfpqEofI0+FLBgBdN08= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -910,12 +941,15 @@ golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -971,12 +1005,15 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1025,6 +1062,7 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1033,6 +1071,13 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY= +golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/main.go b/main.go index 8a0db91bc..8135b1c54 100644 --- a/main.go +++ b/main.go @@ -282,7 +282,7 @@ func main() { p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun) } case "rfc2136": - p, err = rfc2136.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, cfg.RFC2136MinTTL, nil) + p, err = rfc2136.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, cfg.RFC2136MinTTL, cfg.RFC2136GSSTSIG, cfg.RFC2136KerberosUsername, cfg.RFC2136KerberosPassword, nil) case "ns1": p, err = ns1.NewNS1Provider( ns1.NS1Config{ diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index c436b61f3..95453991a 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -141,6 +141,9 @@ type Config struct { RFC2136Port int RFC2136Zone string RFC2136Insecure bool + RFC2136GSSTSIG bool + RFC2136KerberosUsername string + RFC2136KerberosPassword string RFC2136TSIGKeyName string RFC2136TSIGSecret string `secure:"yes"` RFC2136TSIGSecretAlg string @@ -247,6 +250,9 @@ var defaultConfig = &Config{ RFC2136Port: 0, RFC2136Zone: "", RFC2136Insecure: false, + RFC2136GSSTSIG: false, + RFC2136KerberosUsername: "", + RFC2136KerberosPassword: "", RFC2136TSIGKeyName: "", RFC2136TSIGSecret: "", RFC2136TSIGSecretAlg: "", @@ -414,6 +420,9 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("rfc2136-tsig-secret-alg", "When using the RFC2136 provider, specify the TSIG (base64) value to attached to DNS messages (required when --rfc2136-insecure=false)").Default(defaultConfig.RFC2136TSIGSecretAlg).StringVar(&cfg.RFC2136TSIGSecretAlg) app.Flag("rfc2136-tsig-axfr", "When using the RFC2136 provider, specify the TSIG (base64) value to attached to DNS messages (required when --rfc2136-insecure=false)").BoolVar(&cfg.RFC2136TAXFR) app.Flag("rfc2136-min-ttl", "When using the RFC2136 provider, specify minimal TTL (in duration format) for records. This value will be used if the provided TTL for a service/ingress is lower than this").Default(defaultConfig.RFC2136MinTTL.String()).DurationVar(&cfg.RFC2136MinTTL) + app.Flag("rfc2136-gss-tsig", "When using the RFC2136 provider, specify whether to use secure updates with GSS-TSIG using Kerberos (default: false, requires --rfc2136-kerberos-username and rfc2136-kerberos-password)").Default(strconv.FormatBool(defaultConfig.RFC2136GSSTSIG)).BoolVar(&cfg.RFC2136GSSTSIG) + app.Flag("rfc2136-kerberos-username", "When using the RFC2136 provider with GSS-TSIG, specify the username of the user with permissions to update DNS records (required when --rfc2136-gss-tsig=true)").Default(defaultConfig.RFC2136KerberosUsername).StringVar(&cfg.RFC2136KerberosUsername) + app.Flag("rfc2136-kerberos-password", "When using the RFC2136 provider with GSS-TSIG, specify the password of the user with permissions to update DNS records (required when --rfc2136-gss-tsig=true)").Default(defaultConfig.RFC2136KerberosPassword).StringVar(&cfg.RFC2136KerberosPassword) // Flags related to TransIP provider app.Flag("transip-account", "When using the TransIP provider, specify the account name (required when --provider=transip)").Default(defaultConfig.TransIPAccountName).StringVar(&cfg.TransIPAccountName) diff --git a/pkg/apis/externaldns/validation/validation.go b/pkg/apis/externaldns/validation/validation.go index 3ec950311..81f27a34a 100644 --- a/pkg/apis/externaldns/validation/validation.go +++ b/pkg/apis/externaldns/validation/validation.go @@ -86,6 +86,16 @@ func ValidateConfig(cfg *externaldns.Config) error { if cfg.RFC2136MinTTL < 0 { return errors.New("TTL specified for rfc2136 is negative") } + + if cfg.RFC2136Insecure && cfg.RFC2136GSSTSIG { + return errors.New("--rfc2136-insecure and --rfc2136-gss-tsig are mutually exclusive arguments") + } + + if cfg.RFC2136GSSTSIG { + if cfg.RFC2136KerberosPassword == "" || cfg.RFC2136KerberosUsername == "" { + return errors.New("--rfc2136-kerberos-username and --rfc2136-kerberos-password both required when specifying --rfc2136-gss-tsig option") + } + } } if cfg.IgnoreHostnameAnnotation && cfg.FQDNTemplate == "" { diff --git a/pkg/apis/externaldns/validation/validation_test.go b/pkg/apis/externaldns/validation/validation_test.go index c62b2664a..98c5a5068 100644 --- a/pkg/apis/externaldns/validation/validation_test.go +++ b/pkg/apis/externaldns/validation/validation_test.go @@ -150,3 +150,63 @@ func TestValidateGoodRfc2136Config(t *testing.T) { assert.Nil(t, err) } + +func TestValidateBadRfc2136GssTsigConfig(t *testing.T) { + var invalidRfc2136GssTsigConfigs = []*externaldns.Config{ + { + LogFormat: "json", + Sources: []string{"test-source"}, + Provider: "rfc2136", + RFC2136GSSTSIG: true, + RFC2136KerberosUsername: "test-user", + RFC2136KerberosPassword: "", + RFC2136MinTTL: 3600, + }, + { + LogFormat: "json", + Sources: []string{"test-source"}, + Provider: "rfc2136", + RFC2136GSSTSIG: true, + RFC2136KerberosUsername: "", + RFC2136KerberosPassword: "test-pass", + RFC2136MinTTL: 3600, + }, + { + LogFormat: "json", + Sources: []string{"test-source"}, + Provider: "rfc2136", + RFC2136GSSTSIG: true, + RFC2136Insecure: true, + RFC2136KerberosUsername: "test-user", + RFC2136KerberosPassword: "test-pass", + RFC2136MinTTL: 3600, + }, + } + + for _, cfg := range invalidRfc2136GssTsigConfigs { + err := ValidateConfig(cfg) + + assert.NotNil(t, err) + } +} + +func TestValidateGoodRfc2136GssTsigConfig(t *testing.T) { + var validRfc2136GssTsigConfigs = []*externaldns.Config{ + { + LogFormat: "json", + Sources: []string{"test-source"}, + Provider: "rfc2136", + RFC2136GSSTSIG: true, + RFC2136Insecure: false, + RFC2136KerberosUsername: "test-user", + RFC2136KerberosPassword: "test-pass", + RFC2136MinTTL: 3600, + }, + } + + for _, cfg := range validRfc2136GssTsigConfigs { + err := ValidateConfig(cfg) + + assert.Nil(t, err) + } +} diff --git a/provider/rfc2136/rfc2136.go b/provider/rfc2136/rfc2136.go index 8874b9821..611261160 100644 --- a/provider/rfc2136/rfc2136.go +++ b/provider/rfc2136/rfc2136.go @@ -24,7 +24,11 @@ import ( "strings" "time" + "github.com/bodgit/tsig" + extendedClient "github.com/bodgit/tsig/client" + "github.com/bodgit/tsig/gss" "github.com/miekg/dns" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" @@ -36,6 +40,9 @@ import ( const ( // maximum size of a UDP transport message in DNS protocol udpMaxMsgSize = 512 + + // maximum time DNS client can be off from server for an update to succeed + clockSkew = 300 ) // rfc2136 provider type @@ -50,6 +57,12 @@ type rfc2136Provider struct { axfr bool minTTL time.Duration + // options specific to rfc3645 gss-tsig support + gssTsig bool + krb5Username string + krb5Password string + krb5Realm string + // only consider hosted zones managing domains ending in this suffix domainFilter endpoint.DomainFilter dryRun bool @@ -72,9 +85,9 @@ type rfc2136Actions interface { } // NewRfc2136Provider is a factory function for OpenStack rfc2136 providers -func NewRfc2136Provider(host string, port int, zoneName string, insecure bool, keyName string, secret string, secretAlg string, axfr bool, domainFilter endpoint.DomainFilter, dryRun bool, minTTL time.Duration, actions rfc2136Actions) (provider.Provider, error) { +func NewRfc2136Provider(host string, port int, zoneName string, insecure bool, keyName string, secret string, secretAlg string, axfr bool, domainFilter endpoint.DomainFilter, dryRun bool, minTTL time.Duration, gssTsig bool, krb5Username string, krb5Password string, actions rfc2136Actions) (provider.Provider, error) { secretAlgChecked, ok := tsigAlgs[secretAlg] - if !ok && !insecure { + if !ok && !insecure && !gssTsig { return nil, errors.Errorf("%s is not supported TSIG algorithm", secretAlg) } @@ -82,6 +95,10 @@ func NewRfc2136Provider(host string, port int, zoneName string, insecure bool, k nameserver: net.JoinHostPort(host, strconv.Itoa(port)), zoneName: dns.Fqdn(zoneName), insecure: insecure, + gssTsig: gssTsig, + krb5Username: krb5Username, + krb5Password: krb5Password, + krb5Realm: strings.ToUpper(zoneName), domainFilter: domainFilter, dryRun: dryRun, axfr: axfr, @@ -103,6 +120,22 @@ func NewRfc2136Provider(host string, port int, zoneName string, insecure bool, k return r, nil } +// KeyName will return TKEY name and TSIG handle to use for followon actions with a secure connection +func (r rfc2136Provider) KeyData() (keyName *string, handle *gss.GSS, err error) { + handle, err = gss.New() + if err != nil { + return keyName, handle, err + } + + rawHost, _, err := net.SplitHostPort(r.nameserver) + if err != nil { + return keyName, handle, err + } + + keyName, _, err = handle.NegotiateContextWithCredentials(rawHost, r.krb5Realm, r.krb5Username, r.krb5Password) + return keyName, handle, err +} + // Records returns the list of records. func (r rfc2136Provider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) { rrs, err := r.List() @@ -163,7 +196,7 @@ OuterLoop: func (r rfc2136Provider) IncomeTransfer(m *dns.Msg, a string) (env chan *dns.Envelope, err error) { t := new(dns.Transfer) - if !r.insecure { + if !r.insecure && !r.gssTsig { t.TsigSecret = map[string]string{r.tsigKeyName: r.tsigSecret} } @@ -180,8 +213,8 @@ func (r rfc2136Provider) List() ([]dns.RR, error) { m := new(dns.Msg) m.SetAxfr(r.zoneName) - if !r.insecure { - m.SetTsig(r.tsigKeyName, r.tsigSecretAlg, 300, time.Now().Unix()) + if !r.insecure && !r.gssTsig { + m.SetTsig(r.tsigKeyName, r.tsigSecretAlg, clockSkew, time.Now().Unix()) } env, err := r.actions.IncomeTransfer(m, r.nameserver) @@ -304,12 +337,31 @@ func (r rfc2136Provider) SendMessage(msg *dns.Msg) error { } log.Debugf("SendMessage") - c := new(dns.Client) + c := new(extendedClient.Client) c.SingleInflight = true if !r.insecure { - c.TsigSecret = map[string]string{r.tsigKeyName: r.tsigSecret} - msg.SetTsig(r.tsigKeyName, r.tsigSecretAlg, 300, time.Now().Unix()) + if r.gssTsig { + keyName, handle, err := r.KeyData() + if err != nil { + return err + } + defer handle.Close() + defer handle.DeleteContext(keyName) + + c.TsigAlgorithm = map[string]*extendedClient.TsigAlgorithm{ + tsig.GSS: { + Generate: handle.GenerateGSS, + Verify: handle.VerifyGSS, + }, + } + c.TsigSecret = map[string]string{*keyName: ""} + + msg.SetTsig(*keyName, tsig.GSS, clockSkew, time.Now().Unix()) + } else { + c.TsigSecret = map[string]string{r.tsigKeyName: r.tsigSecret} + msg.SetTsig(r.tsigKeyName, r.tsigSecretAlg, clockSkew, time.Now().Unix()) + } } if msg.Len() > udpMaxMsgSize { @@ -318,8 +370,11 @@ func (r rfc2136Provider) SendMessage(msg *dns.Msg) error { resp, _, err := c.Exchange(msg, r.nameserver) if err != nil { - log.Infof("error in dns.Client.Exchange: %s", err) - return err + if resp != nil && resp.Rcode != dns.RcodeSuccess { + log.Infof("error in dns.Client.Exchange: %s", err) + return err + } + log.Warnf("warn in dns.Client.Exchange: %s", err) } if resp != nil && resp.Rcode != dns.RcodeSuccess { log.Infof("Bad dns.Client.Exchange response: %s", resp) diff --git a/provider/rfc2136/rfc2136_test.go b/provider/rfc2136/rfc2136_test.go index 163414158..1df3ea1c6 100644 --- a/provider/rfc2136/rfc2136_test.go +++ b/provider/rfc2136/rfc2136_test.go @@ -95,7 +95,7 @@ func (r *rfc2136Stub) IncomeTransfer(m *dns.Msg, a string) (env chan *dns.Envelo } func createRfc2136StubProvider(stub *rfc2136Stub) (provider.Provider, error) { - return NewRfc2136Provider("", 0, "", false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{}, false, 300*time.Second, stub) + return NewRfc2136Provider("", 0, "", false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{}, false, 300*time.Second, false, "", "", stub) } func extractAuthoritySectionFromMessage(msg fmt.Stringer) []string {