func NewAlibabaCloudProvider(configFile string, domainFilter DomainFilter, zoneIDFileter ZoneIDFilter, zoneType string, dryRun bool) (*AlibabaCloudProvider, error) {
	cfg := alibabaCloudConfig{}
	if configFile != "" {
		contents, err := ioutil.ReadFile(configFile)
		if err != nil {
			return nil, fmt.Errorf("Failed to read Alibaba Cloud config file '%s': %v", configFile, err)
		}
		err = yaml.Unmarshal(contents, &cfg)
		if err != nil {
			return nil, fmt.Errorf("Failed to parse Alibaba Cloud config file '%s': %v", configFile, err)
		}
	} else {
		var tmpError error
		cfg, tmpError = getCloudConfigFromStsToken()
		if tmpError != nil {
			return nil, fmt.Errorf("Failed to getCloudConfigFromStsToken: %v", tmpError)
		}
	}

	// Public DNS service
	var dnsClient AlibabaCloudDNSAPI
	var err error

	if cfg.RoleName == "" {
		dnsClient, err = alidns.NewClientWithAccessKey(
			cfg.RegionID,
			cfg.AccessKeyID,
			cfg.AccessKeySecret,
		)
	} else {
		dnsClient, err = alidns.NewClientWithStsToken(
			cfg.RegionID,
			cfg.AccessKeyID,
			cfg.AccessKeySecret,
			cfg.StsToken,
		)
	}

	if err != nil {
		return nil, fmt.Errorf("failed to create Alibaba Cloud DNS client: %v", err)
	}

	// Private DNS service
	var pvtzClient AlibabaCloudPrivateZoneAPI
	if cfg.RoleName == "" {
		pvtzClient, err = pvtz.NewClientWithAccessKey(
			"cn-hangzhou", // The Private Zone location is fixed
			cfg.AccessKeyID,
			cfg.AccessKeySecret,
		)
	} else {
		pvtzClient, err = pvtz.NewClientWithStsToken(
			cfg.RegionID,
			cfg.AccessKeyID,
			cfg.AccessKeySecret,
			cfg.StsToken,
		)
	}

	if err != nil {
		return nil, err
	}

	provider := &AlibabaCloudProvider{
		domainFilter: domainFilter,
		zoneIDFilter: zoneIDFileter,
		vpcID:        cfg.VPCID,
		dryRun:       dryRun,
		dnsClient:    dnsClient,
		pvtzClient:   pvtzClient,
		privateZone:  zoneType == "private",
	}

	if cfg.RoleName != "" {
		provider.setNextExpire(cfg.ExpireTime)
		go provider.refreshStsToken(1 * time.Second)
	}
	return provider, nil
}

func
NewAlibabaCloudProvider
(
configFile
string
,
domainFilter
DomainFilter
,
zoneIDFileter
ZoneIDFilter
,
zoneType
string
,
dryRun
bool
)
(
*
AlibabaCloudProvider
,
error
)
{
cfg
:=
alibabaCloudConfig
{
}
if
configFile
!=
"
"
{
contents
,
err
:=
ioutil
.
ReadFile
(
configFile
)
if
err
!=
{
return
,
fmt
.
Errorf
(
"
"
,
configFile
,
err
)
}
err
=
yaml
.
Unmarshal
(
contents
,
&
cfg
)
if
err
!=
{
return
,
fmt
.
Errorf
(
"
"
,
configFile
,
err
)
}
}
else
{
var
tmpError
error
cfg
,
tmpError
=
getCloudConfigFromStsToken
(
)
if
tmpError
!=
{
return
,
fmt
.
Errorf
(
"
"
,
tmpError
)
}
}
// Public DNS service
var
dnsClient
AlibabaCloudDNSAPI
var
err
error
if
cfg
.
RoleName
==
"
"
{
dnsClient
,
err
=
alidns
.
NewClientWithAccessKey
(
cfg
.
RegionID
,
cfg
.
AccessKeyID
,
cfg
.
AccessKeySecret
,
)
}
else
{
dnsClient
,
err
=
alidns
.
NewClientWithStsToken
(
cfg
.
RegionID
,
cfg
.
AccessKeyID
,
cfg
.
AccessKeySecret
,
cfg
.
StsToken
,
)
}
if
err
!=
{
return
,
fmt
.
Errorf
(
"
"
,
err
)
}
// Private DNS service
var
pvtzClient
AlibabaCloudPrivateZoneAPI
if
cfg
.
RoleName
==
"
"
{
pvtzClient
,
err
=
pvtz
.
NewClientWithAccessKey
(
"
"
,
// The Private Zone location is fixed
cfg
.
AccessKeyID
,
cfg
.
AccessKeySecret
,
)
}
else
{
pvtzClient
,
err
=
pvtz
.
NewClientWithStsToken
(
cfg
.
RegionID
,
cfg
.
AccessKeyID
,
cfg
.
AccessKeySecret
,
cfg
.
StsToken
,
)
}
if
err
!=
{
return
,
err
}
provider
:=
&
AlibabaCloudProvider
{
domainFilter
:
domainFilter
,
zoneIDFilter
:
zoneIDFileter
,
vpcID
:
cfg
.
VPCID
,
dryRun
:
dryRun
,
dnsClient
:
dnsClient
,
pvtzClient
:
pvtzClient
,
privateZone
:
zoneType
==
"
"
,
}
if
cfg
.
RoleName
!=
"
"
{
provider
.
setNextExpire
(
cfg
.
ExpireTime
)
go
provider
.
refreshStsToken
(
1
*
time
.
Second
)
}
return
provider
,
}

// NewAlibabaCloudProvider creates a new Alibaba Cloud provider.
//
// Returns the provider or an error if a provider could not be created.

NewAlibabaCloudProvider creates a new Alibaba Cloud provider . Returns the provider or an error if a provider could not be created .


func (p *AlibabaCloudProvider) recordsForDNS() (endpoints []*endpoint.Endpoint, _ error) {

	records, err := p.records()
	if err != nil {
		return nil, err
	}
	for _, recordList := range p.groupRecords(records) {
		name := p.getDNSName(recordList[0].RR, recordList[0].DomainName)
		recordType := recordList[0].Type
		ttl := recordList[0].TTL

		if ttl == defaultAlibabaCloudRecordTTL {
			ttl = 0
		}

		var targets []string
		for _, record := range recordList {
			target := record.Value
			if recordType == "TXT" {
				target = p.unescapeTXTRecordValue(target)
			}
			targets = append(targets, target)
		}
		ep := endpoint.NewEndpointWithTTL(name, recordType, endpoint.TTL(ttl), targets...)
		endpoints = append(endpoints, ep)
	}
	return endpoints, nil
}

func ( p * AlibabaCloudProvider ) recordsForDNS ( ) ( endpoints [ ] * endpoint . Endpoint , _ error ) { records , err := p . records ( ) if err != nil { return nil , err } for _ , recordList := range p . groupRecords ( records ) { name := p . getDNSName ( recordList [ 0 ] . RR , recordList [ 0 ] . DomainName ) recordType := recordList [ 0 ] . Type ttl := recordList [ 0 ] . TTL if ttl == defaultAlibabaCloudRecordTTL { ttl = 0 } var targets [ ] string for _ , record := range recordList { target := record . Value if recordType == " " { target = p . unescapeTXTRecordValue ( target ) } targets = append ( targets , target ) } ep := endpoint . NewEndpointWithTTL ( name , recordType , endpoint . TTL ( ttl ) , targets ... ) endpoints = append ( endpoints , ep ) } return endpoints , nil }

// recordsForDNS gets the current records.
//
// Returns the current records or an error if the operation failed.

recordsForDNS gets the current records . Returns the current records or an error if the operation failed .


func (p *AlibabaCloudProvider) privateZoneRecords() (endpoints []*endpoint.Endpoint, _ error) {

	zones, err := p.getPrivateZones()
	if err != nil {
		return nil, err
	}

	for _, zone := range zones {
		recordMap := p.groupPrivateZoneRecords(zone)
		for _, recordList := range recordMap {
			name := p.getDNSName(recordList[0].Rr, zone.ZoneName)
			recordType := recordList[0].Type
			ttl := recordList[0].Ttl
			if ttl == defaultAlibabaCloudPrivateZoneRecordTTL {
				ttl = 0
			}
			var targets []string
			for _, record := range recordList {
				target := record.Value
				if recordType == "TXT" {
					target = p.unescapeTXTRecordValue(target)
				}
				targets = append(targets, target)
			}
			ep := endpoint.NewEndpointWithTTL(name, recordType, endpoint.TTL(ttl), targets...)
			endpoints = append(endpoints, ep)
		}
	}
	return endpoints, nil
}

func ( p * AlibabaCloudProvider ) privateZoneRecords ( ) ( endpoints [ ] * endpoint . Endpoint , _ error ) { zones , err := p . getPrivateZones ( ) if err != nil { return nil , err } for _ , zone := range zones { recordMap := p . groupPrivateZoneRecords ( zone ) for _ , recordList := range recordMap { name := p . getDNSName ( recordList [ 0 ] . Rr , zone . ZoneName ) recordType := recordList [ 0 ] . Type ttl := recordList [ 0 ] . Ttl if ttl == defaultAlibabaCloudPrivateZoneRecordTTL { ttl = 0 } var targets [ ] string for _ , record := range recordList { target := record . Value if recordType == " " { target = p . unescapeTXTRecordValue ( target ) } targets = append ( targets , target ) } ep := endpoint . NewEndpointWithTTL ( name , recordType , endpoint . TTL ( ttl ) , targets ... ) endpoints = append ( endpoints , ep ) } } return endpoints , nil }

// recordsForPrivateZone gets the current records.
//
// Returns the current records or an error if the operation failed.

recordsForPrivateZone gets the current records . Returns the current records or an error if the operation failed .


func (c etcdClient) SaveService(service *Service) error {
	ctx, cancel := context.WithTimeout(c.ctx, etcdTimeout)
	defer cancel()

	value, err := json.Marshal(&service)
	if err != nil {
		return err
	}
	_, err = c.client.Put(ctx, service.Key, string(value))
	if err != nil {
		return err
	}
	return nil
}

func ( c etcdClient ) SaveService ( service * Service ) error { ctx , cancel := context . WithTimeout ( c . ctx , etcdTimeout ) defer cancel ( ) value , err := json . Marshal ( & service ) if err != nil { return err } _ , err = c . client . Put ( ctx , service . Key , string ( value ) ) if err != nil { return err } return nil }

// SaveService persists service data into etcd


func (c etcdClient) DeleteService(key string) error {
	ctx, cancel := context.WithTimeout(c.ctx, etcdTimeout)
	defer cancel()

	_, err := c.client.Delete(ctx, key, etcdcv3.WithPrefix())
	return err
}

func ( c etcdClient ) DeleteService ( key string ) error { ctx , cancel := context . WithTimeout ( c . ctx , etcdTimeout ) defer cancel ( ) _ , err := c . client . Delete ( ctx , key , etcdcv3 . WithPrefix ( ) ) return err }

// DeleteService deletes service record from etcd


func newTLSConfig(certPath, keyPath, caPath, serverName string, insecure bool) (*tls.Config, error) {
	if certPath != "" && keyPath == "" || certPath == "" && keyPath != "" {
		return nil, errors.New("either both cert and key or none must be provided")
	}
	var certificates []tls.Certificate
	if certPath != "" {
		cert, err := tls.LoadX509KeyPair(certPath, keyPath)
		if err != nil {
			return nil, fmt.Errorf("could not load TLS cert: %s", err)
		}
		certificates = append(certificates, cert)
	}
	roots, err := loadRoots(caPath)
	if err != nil {
		return nil, err
	}

	return &tls.Config{
		Certificates:       certificates,
		RootCAs:            roots,
		InsecureSkipVerify: insecure,
		ServerName:         serverName,
	}, nil
}

func newTLSConfig ( certPath , keyPath , caPath , serverName string , insecure bool ) ( * tls . Config , error ) { if certPath != " " && keyPath == " " || certPath == " " && keyPath != " " { return nil , errors . New ( " " ) } var certificates [ ] tls . Certificate if certPath != " " { cert , err := tls . LoadX509KeyPair ( certPath , keyPath ) if err != nil { return nil , fmt . Errorf ( " " , err ) } certificates = append ( certificates , cert ) } roots , err := loadRoots ( caPath ) if err != nil { return nil , err } return & tls . Config { Certificates : certificates , RootCAs : roots , InsecureSkipVerify : insecure , ServerName : serverName , } , nil }

// loads TLS artifacts and builds tls.Clonfig object

loads TLS artifacts and builds tls . Clonfig object


func getETCDConfig() (*etcdcv3.Config, error) {
	etcdURLsStr := os.Getenv("ETCD_URLS")
	if etcdURLsStr == "" {
		etcdURLsStr = "http://localhost:2379"
	}
	etcdURLs := strings.Split(etcdURLsStr, ",")
	firstURL := strings.ToLower(etcdURLs[0])
	if strings.HasPrefix(firstURL, "http://") {
		return &etcdcv3.Config{Endpoints: etcdURLs}, nil
	} else if strings.HasPrefix(firstURL, "https://") {
		caFile := os.Getenv("ETCD_CA_FILE")
		certFile := os.Getenv("ETCD_CERT_FILE")
		keyFile := os.Getenv("ETCD_KEY_FILE")
		serverName := os.Getenv("ETCD_TLS_SERVER_NAME")
		isInsecureStr := strings.ToLower(os.Getenv("ETCD_TLS_INSECURE"))
		isInsecure := isInsecureStr == "true" || isInsecureStr == "yes" || isInsecureStr == "1"
		tlsConfig, err := newTLSConfig(certFile, keyFile, caFile, serverName, isInsecure)
		if err != nil {
			return nil, err
		}
		return &etcdcv3.Config{
			Endpoints: etcdURLs,
			TLS:       tlsConfig,
		}, nil
	} else {
		return nil, errors.New("etcd URLs must start with either http:// or https://")
	}
}

func getETCDConfig ( ) ( * etcdcv3 . Config , error ) { etcdURLsStr := os . Getenv ( " " ) if etcdURLsStr == " " { etcdURLsStr = " " } etcdURLs := strings . Split ( etcdURLsStr , " " ) firstURL := strings . ToLower ( etcdURLs [ 0 ] ) if strings . HasPrefix ( firstURL , " " ) { return & etcdcv3 . Config { Endpoints : etcdURLs } , nil } else if strings . HasPrefix ( firstURL , " " ) { caFile := os . Getenv ( " " ) certFile := os . Getenv ( " " ) keyFile := os . Getenv ( " " ) serverName := os . Getenv ( " " ) isInsecureStr := strings . ToLower ( os . Getenv ( " " ) ) isInsecure := isInsecureStr == " " || isInsecureStr == " " || isInsecureStr == " " tlsConfig , err := newTLSConfig ( certFile , keyFile , caFile , serverName , isInsecure ) if err != nil { return nil , err } return & etcdcv3 . Config { Endpoints : etcdURLs , TLS : tlsConfig , } , nil } else { return nil , errors . New ( " " ) } }

// builds etcd client config depending on connection scheme and TLS parameters


func newETCDClient() (coreDNSClient, error) {
	cfg, err := getETCDConfig()
	if err != nil {
		return nil, err
	}
	c, err := etcdcv3.New(*cfg)
	if err != nil {
		return nil, err
	}
	return etcdClient{c, context.Background()}, nil
}

func newETCDClient ( ) ( coreDNSClient , error ) { cfg , err := getETCDConfig ( ) if err != nil { return nil , err } c , err := etcdcv3 . New ( * cfg ) if err != nil { return nil , err } return etcdClient { c , context . Background ( ) } , nil }

//newETCDClient is an etcd client constructor


func NewCoreDNSProvider(domainFilter DomainFilter, dryRun bool) (Provider, error) {
	client, err := newETCDClient()
	if err != nil {
		return nil, err
	}
	return coreDNSProvider{
		client:       client,
		dryRun:       dryRun,
		domainFilter: domainFilter,
	}, nil
}

func NewCoreDNSProvider ( domainFilter DomainFilter , dryRun bool ) ( Provider , error ) { client , err := newETCDClient ( ) if err != nil { return nil , err } return coreDNSProvider { client : client , dryRun : dryRun , domainFilter : domainFilter , } , nil }

// NewCoreDNSProvider is a CoreDNS provider constructor


func (p coreDNSProvider) Records() ([]*endpoint.Endpoint, error) {
	var result []*endpoint.Endpoint
	services, err := p.client.GetServices(coreDNSPrefix)
	if err != nil {
		return nil, err
	}
	for _, service := range services {
		domains := strings.Split(strings.TrimPrefix(service.Key, coreDNSPrefix), "/")
		reverse(domains)
		dnsName := strings.Join(domains[service.TargetStrip:], ".")
		if !p.domainFilter.Match(dnsName) {
			continue
		}
		prefix := strings.Join(domains[:service.TargetStrip], ".")
		if service.Host != "" {
			ep := endpoint.NewEndpointWithTTL(
				dnsName,
				guessRecordType(service.Host),
				endpoint.TTL(service.TTL),
				service.Host,
			)
			ep.Labels["originalText"] = service.Text
			ep.Labels[randomPrefixLabel] = prefix
			result = append(result, ep)
		}
		if service.Text != "" {
			ep := endpoint.NewEndpoint(
				dnsName,
				endpoint.RecordTypeTXT,
				service.Text,
			)
			ep.Labels[randomPrefixLabel] = prefix
			result = append(result, ep)
		}
	}
	return result, nil
}

func ( p coreDNSProvider ) Records ( ) ( [ ] * endpoint . Endpoint , error ) { var result [ ] * endpoint . Endpoint services , err := p . client . GetServices ( coreDNSPrefix ) if err != nil { return nil , err } for _ , service := range services { domains := strings . Split ( strings . TrimPrefix ( service . Key , coreDNSPrefix ) , " " ) reverse ( domains ) dnsName := strings . Join ( domains [ service . TargetStrip : ] , " " ) if ! p . domainFilter . Match ( dnsName ) { continue } prefix := strings . Join ( domains [ : service . TargetStrip ] , " " ) if service . Host != " " { ep := endpoint . NewEndpointWithTTL ( dnsName , guessRecordType ( service . Host ) , endpoint . TTL ( service . TTL ) , service . Host , ) ep . Labels [ " " ] = service . Text ep . Labels [ randomPrefixLabel ] = prefix result = append ( result , ep ) } if service . Text != " " { ep := endpoint . NewEndpoint ( dnsName , endpoint . RecordTypeTXT , service . Text , ) ep . Labels [ randomPrefixLabel ] = prefix result = append ( result , ep ) } } return result , nil }

// Records returns all DNS records found in CoreDNS etcd backend. Depending on the record fields
// it may be mapped to one or two records of type A, CNAME, TXT, A+TXT, CNAME+TXT

Records returns all DNS records found in CoreDNS etcd backend . Depending on the record fields it may be mapped to one or two records of type A CNAME TXT A + TXT CNAME + TXT


func NewRfc2136Provider(host string, port int, zoneName string, insecure bool, keyName string, secret string, secretAlg string, axfr bool, domainFilter DomainFilter, dryRun bool, actions rfc2136Actions) (Provider, error) {
	secretAlgChecked, ok := tsigAlgs[secretAlg]
	if !ok && !insecure {
		return nil, errors.Errorf("%s is not supported TSIG algorithm", secretAlg)
	}

	r := &rfc2136Provider{
		nameserver:   net.JoinHostPort(host, strconv.Itoa(port)),
		zoneName:     dns.Fqdn(zoneName),
		insecure:     insecure,
		domainFilter: domainFilter,
		dryRun:       dryRun,
		axfr:         axfr,
	}
	if actions != nil {
		r.actions = actions
	} else {
		r.actions = r
	}

	if !insecure {
		r.tsigKeyName = dns.Fqdn(keyName)
		r.tsigSecret = secret
		r.tsigSecretAlg = secretAlgChecked
	}

	log.Infof("Configured RFC2136 with zone '%s' and nameserver '%s'", r.zoneName, r.nameserver)
	return r, nil
}

func NewRfc2136Provider ( host string , port int , zoneName string , insecure bool , keyName string , secret string , secretAlg string , axfr bool , domainFilter DomainFilter , dryRun bool , actions rfc2136Actions ) ( Provider , error ) { secretAlgChecked , ok := tsigAlgs [ secretAlg ] if ! ok && ! insecure { return nil , errors . Errorf ( " " , secretAlg ) } r := & rfc2136Provider { nameserver : net . JoinHostPort ( host , strconv . Itoa ( port ) ) , zoneName : dns . Fqdn ( zoneName ) , insecure : insecure , domainFilter : domainFilter , dryRun : dryRun , axfr : axfr , } if actions != nil { r . actions = actions } else { r . actions = r } if ! insecure { r . tsigKeyName = dns . Fqdn ( keyName ) r . tsigSecret = secret r . tsigSecretAlg = secretAlgChecked } log . Infof ( " " , r . zoneName , r . nameserver ) return r , nil }

// NewRfc2136Provider is a factory function for OpenStack rfc2136 providers


func stringifyHTTPResponseBody(r *http.Response) (body string) {

	if r == nil {
		return ""
	}

	buf := new(bytes.Buffer)
	buf.ReadFrom(r.Body)
	body = buf.String()
	return body

}

func stringifyHTTPResponseBody ( r * http . Response ) ( body string ) { if r == nil { return " " } buf := new ( bytes . Buffer ) buf . ReadFrom ( r . Body ) body = buf . String ( ) return body }


func NewPDNSProvider(config PDNSConfig) (*PDNSProvider, error) {

	// Do some input validation

	if config.APIKey == "" {
		return nil, errors.New("Missing API Key for PDNS. Specify using --pdns-api-key=")
	}

	// We do not support dry running, exit safely instead of surprising the user
	// TODO: Add Dry Run support
	if config.DryRun {
		return nil, errors.New("PDNS Provider does not currently support dry-run")
	}

	if config.Server == "localhost" {
		log.Warnf("PDNS Server is set to localhost, this may not be what you want. Specify using --pdns-server=")
	}

	pdnsClientConfig := pgo.NewConfiguration()
	pdnsClientConfig.BasePath = config.Server + apiBase
	if err := config.TLSConfig.setHTTPClient(pdnsClientConfig); err != nil {
		return nil, err
	}

	provider := &PDNSProvider{
		client: &PDNSAPIClient{
			dryRun:       config.DryRun,
			authCtx:      context.WithValue(context.TODO(), pgo.ContextAPIKey, pgo.APIKey{Key: config.APIKey}),
			client:       pgo.NewAPIClient(pdnsClientConfig),
			domainFilter: config.DomainFilter,
		},
	}

	return provider, nil
}

// NewPDNSProvider initializes a new PowerDNS based Provider.

NewPDNSProvider initializes a new PowerDNS based Provider .


func (p *PDNSProvider) mutateRecords(endpoints []*endpoint.Endpoint, changetype pdnsChangeType) error {
	zonelist, err := p.ConvertEndpointsToZones(endpoints, changetype)
	if err != nil {
		return err
	}
	for _, zone := range zonelist {
		jso, err := json.Marshal(zone)
		if err != nil {
			log.Errorf("JSON Marshal for zone struct failed!")
		} else {
			log.Debugf("Struct for PatchZone:\n%s", string(jso))
		}

		resp, err := p.client.PatchZone(zone.Id, zone)
		if err != nil {
			log.Debugf("PDNS API response: %s", stringifyHTTPResponseBody(resp))
			return err
		}

	}
	return nil
}

func ( p * PDNSProvider ) mutateRecords ( endpoints [ ] * endpoint . Endpoint , changetype pdnsChangeType ) error { zonelist , err := p . ConvertEndpointsToZones ( endpoints , changetype ) if err != nil { return err } for _ , zone := range zonelist { jso , err := json . Marshal ( zone ) if err != nil { log . Errorf ( " " ) } else { log . Debugf ( " \n " , string ( jso ) ) } resp , err := p . client . PatchZone ( zone . Id , zone ) if err != nil { log . Debugf ( " " , stringifyHTTPResponseBody ( resp ) ) return err } } return nil }

// mutateRecords takes a list of endpoints and creates, replaces or deletes them based on the changetype

mutateRecords takes a list of endpoints and creates replaces or deletes them based on the changetype


func NewAWSSDProvider(domainFilter DomainFilter, namespaceType string, assumeRole string, dryRun bool) (*AWSSDProvider, error) {
	config := aws.NewConfig()

	config = config.WithHTTPClient(
		instrumented_http.NewClient(config.HTTPClient, &instrumented_http.Callbacks{
			PathProcessor: func(path string) string {
				parts := strings.Split(path, "/")
				return parts[len(parts)-1]
			},
		}),
	)

	sess, err := session.NewSessionWithOptions(session.Options{
		Config:            *config,
		SharedConfigState: session.SharedConfigEnable,
	})
	if err != nil {
		return nil, err
	}

	if assumeRole != "" {
		log.Infof("Assuming role: %s", assumeRole)
		sess.Config.WithCredentials(stscreds.NewCredentials(sess, assumeRole))
	}

	sess.Handlers.Build.PushBack(request.MakeAddToUserAgentHandler("ExternalDNS", externaldns.Version))

	provider := &AWSSDProvider{
		client:              sd.New(sess),
		namespaceFilter:     domainFilter,
		namespaceTypeFilter: newSdNamespaceFilter(namespaceType),
		dryRun:              dryRun,
	}

	return provider, nil
}

func NewAWSSDProvider ( domainFilter DomainFilter , namespaceType string , assumeRole string , dryRun bool ) ( * AWSSDProvider , error ) { config := aws . NewConfig ( ) config = config . WithHTTPClient ( instrumented_http . NewClient ( config . HTTPClient , & instrumented_http . Callbacks { PathProcessor : func ( path string ) string { parts := strings . Split ( path , " " ) return parts [ len ( parts ) - 1 ] } , } ) , ) sess , err := session . NewSessionWithOptions ( session . Options { Config : * config , SharedConfigState : session . SharedConfigEnable , } ) if err != nil { return nil , err } if assumeRole != " " { log . Infof ( " " , assumeRole ) sess . Config . WithCredentials ( stscreds . NewCredentials ( sess , assumeRole ) ) } sess . Handlers . Build . PushBack ( request . MakeAddToUserAgentHandler ( " " , externaldns . Version ) ) provider := & AWSSDProvider { client : sd . New ( sess ) , namespaceFilter : domainFilter , namespaceTypeFilter : newSdNamespaceFilter ( namespaceType ) , dryRun : dryRun , } return provider , nil }

// NewAWSSDProvider initializes a new AWS Route53 Auto Naming based Provider.

NewAWSSDProvider initializes a new AWS Route53 Auto Naming based Provider .


func newSdNamespaceFilter(namespaceTypeConfig string) *sd.NamespaceFilter {
	switch namespaceTypeConfig {
	case sdNamespaceTypePublic:
		return &sd.NamespaceFilter{
			Name:   aws.String(sd.NamespaceFilterNameType),
			Values: []*string{aws.String(sd.NamespaceTypeDnsPublic)},
		}
	case sdNamespaceTypePrivate:
		return &sd.NamespaceFilter{
			Name:   aws.String(sd.NamespaceFilterNameType),
			Values: []*string{aws.String(sd.NamespaceTypeDnsPrivate)},
		}
	default:
		return nil
	}
}

func newSdNamespaceFilter ( namespaceTypeConfig string ) * sd . NamespaceFilter { switch namespaceTypeConfig { case sdNamespaceTypePublic : return & sd . NamespaceFilter { Name : aws . String ( sd . NamespaceFilterNameType ) , Values : [ ] * string { aws . String ( sd . NamespaceTypeDnsPublic ) } , } case sdNamespaceTypePrivate : return & sd . NamespaceFilter { Name : aws . String ( sd . NamespaceFilterNameType ) , Values : [ ] * string { aws . String ( sd . NamespaceTypeDnsPrivate ) } , } default : return nil } }

// newSdNamespaceFilter initialized AWS SD Namespace Filter based on given string config


func (p *AWSSDProvider) Records() (endpoints []*endpoint.Endpoint, err error) {
	namespaces, err := p.ListNamespaces()
	if err != nil {
		return nil, err
	}

	for _, ns := range namespaces {
		services, err := p.ListServicesByNamespaceID(ns.Id)
		if err != nil {
			return nil, err
		}

		for _, srv := range services {
			instances, err := p.ListInstancesByServiceID(srv.Id)
			if err != nil {
				return nil, err
			}

			if len(instances) > 0 {
				ep := p.instancesToEndpoint(ns, srv, instances)
				endpoints = append(endpoints, ep)
			}
		}
	}

	return endpoints, nil
}

func ( p * AWSSDProvider ) Records ( ) ( endpoints [ ] * endpoint . Endpoint , err error ) { namespaces , err := p . ListNamespaces ( ) if err != nil { return nil , err } for _ , ns := range namespaces { services , err := p . ListServicesByNamespaceID ( ns . Id ) if err != nil { return nil , err } for _ , srv := range services { instances , err := p . ListInstancesByServiceID ( srv . Id ) if err != nil { return nil , err } if len ( instances ) > 0 { ep := p . instancesToEndpoint ( ns , srv , instances ) endpoints = append ( endpoints , ep ) } } } return endpoints , nil }

// Records returns list of all endpoints.


func (p *AWSSDProvider) ApplyChanges(changes *plan.Changes) error {
	// return early if there is nothing to change
	if len(changes.Create) == 0 && len(changes.Delete) == 0 && len(changes.UpdateNew) == 0 {
		log.Info("All records are already up to date")
		return nil
	}

	// convert updates to delete and create operation if applicable (updates not supported)
	creates, deletes := p.updatesToCreates(changes)
	changes.Delete = append(changes.Delete, deletes...)
	changes.Create = append(changes.Create, creates...)

	namespaces, err := p.ListNamespaces()
	if err != nil {
		return err
	}

	// Deletes must be executed first to support update case.
	// When just list of targets is updated `[1.2.3.4] -> [1.2.3.4, 1.2.3.5]` it is translated to:
	// ```
	// deletes = [1.2.3.4]
	// creates = [1.2.3.4, 1.2.3.5]
	// ```
	// then when deletes are executed after creates it will miss the `1.2.3.4` instance.
	err = p.submitDeletes(namespaces, changes.Delete)
	if err != nil {
		return err
	}

	err = p.submitCreates(namespaces, changes.Create)
	if err != nil {
		return err
	}

	return nil
}

// ApplyChanges applies Kubernetes changes in endpoints to AWS API


func (p *AWSSDProvider) ListNamespaces() ([]*sd.NamespaceSummary, error) {
	namespaces := make([]*sd.NamespaceSummary, 0)

	f := func(resp *sd.ListNamespacesOutput, lastPage bool) bool {
		for _, ns := range resp.Namespaces {
			if !p.namespaceFilter.Match(aws.StringValue(ns.Name)) {
				continue
			}
			namespaces = append(namespaces, ns)
		}

		return true
	}

	err := p.client.ListNamespacesPages(&sd.ListNamespacesInput{
		Filters: []*sd.NamespaceFilter{p.namespaceTypeFilter},
	}, f)
	if err != nil {
		return nil, err
	}

	return namespaces, nil
}

func ( p * AWSSDProvider ) ListNamespaces ( ) ( [ ] * sd . NamespaceSummary , error ) { namespaces := make ( [ ] * sd . NamespaceSummary , 0 ) f := func ( resp * sd . ListNamespacesOutput , lastPage bool ) bool { for _ , ns := range resp . Namespaces { if ! p . namespaceFilter . Match ( aws . StringValue ( ns . Name ) ) { continue } namespaces = append ( namespaces , ns ) } return true } err := p . client . ListNamespacesPages ( & sd . ListNamespacesInput { Filters : [ ] * sd . NamespaceFilter { p . namespaceTypeFilter } , } , f ) if err != nil { return nil , err } return namespaces , nil }

// ListNamespaces returns all namespaces matching defined namespace filter


func (p *AWSSDProvider) GetServiceDetail(serviceID *string) (*sd.Service, error) {
	output, err := p.client.GetService(&sd.GetServiceInput{
		Id: serviceID,
	})
	if err != nil {
		return nil, err
	}

	return output.Service, nil
}

func ( p * AWSSDProvider ) GetServiceDetail ( serviceID * string ) ( * sd . Service , error ) { output , err := p . client . GetService ( & sd . GetServiceInput { Id : serviceID , } ) if err != nil { return nil , err } return output . Service , nil }

// GetServiceDetail returns detail of given service


func (p *AWSSDProvider) ListInstancesByServiceID(serviceID *string) ([]*sd.InstanceSummary, error) {
	instances := make([]*sd.InstanceSummary, 0)

	f := func(resp *sd.ListInstancesOutput, lastPage bool) bool {
		instances = append(instances, resp.Instances...)

		return true
	}

	err := p.client.ListInstancesPages(&sd.ListInstancesInput{
		ServiceId: serviceID,
	}, f)
	if err != nil {
		return nil, err
	}

	return instances, nil
}

func ( p * AWSSDProvider ) ListInstancesByServiceID ( serviceID * string ) ( [ ] * sd . InstanceSummary , error ) { instances := make ( [ ] * sd . InstanceSummary , 0 ) f := func ( resp * sd . ListInstancesOutput , lastPage bool ) bool { instances = append ( instances , resp . Instances ... ) return true } err := p . client . ListInstancesPages ( & sd . ListInstancesInput { ServiceId : serviceID , } , f ) if err != nil { return nil , err } return instances , nil }

// ListInstancesByServiceID returns list of instances registered in given service.

ListInstancesByServiceID returns list of instances registered in given service .


func (p *AWSSDProvider) CreateService(namespaceID *string, srvName *string, ep *endpoint.Endpoint) (*sd.Service, error) {
	log.Infof("Creating a new service \"%s\" in \"%s\" namespace", *srvName, *namespaceID)

	srvType := p.serviceTypeFromEndpoint(ep)
	routingPolicy := p.routingPolicyFromEndpoint(ep)

	ttl := int64(sdDefaultRecordTTL)
	if ep.RecordTTL.IsConfigured() {
		ttl = int64(ep.RecordTTL)
	}

	if !p.dryRun {
		out, err := p.client.CreateService(&sd.CreateServiceInput{
			Name:        srvName,
			Description: aws.String(ep.Labels[endpoint.AWSSDDescriptionLabel]),
			DnsConfig: &sd.DnsConfig{
				NamespaceId:   namespaceID,
				RoutingPolicy: aws.String(routingPolicy),
				DnsRecords: []*sd.DnsRecord{{
					Type: aws.String(srvType),
					TTL:  aws.Int64(ttl),
				}},
			},
		})
		if err != nil {
			return nil, err
		}

		return out.Service, nil
	}

	// return mock service summary in case of dry run
	return &sd.Service{Id: aws.String("dry-run-service"), Name: aws.String("dry-run-service")}, nil
}

// CreateService creates a new service in AWS API. Returns the created service.

CreateService creates a new service in AWS API . Returns the created service .


func (p *AWSSDProvider) UpdateService(service *sd.Service, ep *endpoint.Endpoint) error {
	log.Infof("Updating service \"%s\"", *service.Name)

	srvType := p.serviceTypeFromEndpoint(ep)

	ttl := int64(sdDefaultRecordTTL)
	if ep.RecordTTL.IsConfigured() {
		ttl = int64(ep.RecordTTL)
	}

	if !p.dryRun {
		_, err := p.client.UpdateService(&sd.UpdateServiceInput{
			Id: service.Id,
			Service: &sd.ServiceChange{
				Description: aws.String(ep.Labels[endpoint.AWSSDDescriptionLabel]),
				DnsConfig: &sd.DnsConfigChange{
					DnsRecords: []*sd.DnsRecord{{
						Type: aws.String(srvType),
						TTL:  aws.Int64(ttl),
					}},
				}}})
		if err != nil {
			return err
		}
	}

	return nil
}

func ( p * AWSSDProvider ) UpdateService ( service * sd . Service , ep * endpoint . Endpoint ) error { log . Infof ( " \" \" " , * service . Name ) srvType := p . serviceTypeFromEndpoint ( ep ) ttl := int64 ( sdDefaultRecordTTL ) if ep . RecordTTL . IsConfigured ( ) { ttl = int64 ( ep . RecordTTL ) } if ! p . dryRun { _ , err := p . client . UpdateService ( & sd . UpdateServiceInput { Id : service . Id , Service : & sd . ServiceChange { Description : aws . String ( ep . Labels [ endpoint . AWSSDDescriptionLabel ] ) , DnsConfig : & sd . DnsConfigChange { DnsRecords : [ ] * sd . DnsRecord { { Type : aws . String ( srvType ) , TTL : aws . Int64 ( ttl ) , } } , } } } ) if err != nil { return err } } return nil }

// UpdateService updates the specified service with information from provided endpoint.

UpdateService updates the specified service with information from provided endpoint .


func (p *AWSSDProvider) RegisterInstance(service *sd.Service, ep *endpoint.Endpoint) error {
	for _, target := range ep.Targets {
		log.Infof("Registering a new instance \"%s\" for service \"%s\" (%s)", target, *service.Name, *service.Id)

		attr := make(map[string]*string)

		if ep.RecordType == endpoint.RecordTypeCNAME {
			if p.isAWSLoadBalancer(target) {
				attr[sdInstanceAttrAlias] = aws.String(target)
			} else {
				attr[sdInstanceAttrCname] = aws.String(target)
			}
		} else if ep.RecordType == endpoint.RecordTypeA {
			attr[sdInstanceAttrIPV4] = aws.String(target)
		} else {
			return fmt.Errorf("invalid endpoint type (%v)", ep)
		}

		if !p.dryRun {
			_, err := p.client.RegisterInstance(&sd.RegisterInstanceInput{
				ServiceId:  service.Id,
				Attributes: attr,
				InstanceId: aws.String(p.targetToInstanceID(target)),
			})
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func ( p * AWSSDProvider ) RegisterInstance ( service * sd . Service , ep * endpoint . Endpoint ) error { for _ , target := range ep . Targets { log . Infof ( " \" \" \" \" " , target , * service . Name , * service . Id ) attr := make ( map [ string ] * string ) if ep . RecordType == endpoint . RecordTypeCNAME { if p . isAWSLoadBalancer ( target ) { attr [ sdInstanceAttrAlias ] = aws . String ( target ) } else { attr [ sdInstanceAttrCname ] = aws . String ( target ) } } else if ep . RecordType == endpoint . RecordTypeA { attr [ sdInstanceAttrIPV4 ] = aws . String ( target ) } else { return fmt . Errorf ( " " , ep ) } if ! p . dryRun { _ , err := p . client . RegisterInstance ( & sd . RegisterInstanceInput { ServiceId : service . Id , Attributes : attr , InstanceId : aws . String ( p . targetToInstanceID ( target ) ) , } ) if err != nil { return err } } } return nil }

// RegisterInstance creates a new instance in given service.

RegisterInstance creates a new instance in given service .


func (p *AWSSDProvider) DeregisterInstance(service *sd.Service, ep *endpoint.Endpoint) error {
	for _, target := range ep.Targets {
		log.Infof("De-registering an instance \"%s\" for service \"%s\" (%s)", target, *service.Name, *service.Id)

		if !p.dryRun {
			_, err := p.client.DeregisterInstance(&sd.DeregisterInstanceInput{
				InstanceId: aws.String(p.targetToInstanceID(target)),
				ServiceId:  service.Id,
			})
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func ( p * AWSSDProvider ) DeregisterInstance ( service * sd . Service , ep * endpoint . Endpoint ) error { for _ , target := range ep . Targets { log . Infof ( " \" \" \" \" " , target , * service . Name , * service . Id ) if ! p . dryRun { _ , err := p . client . DeregisterInstance ( & sd . DeregisterInstanceInput { InstanceId : aws . String ( p . targetToInstanceID ( target ) ) , ServiceId : service . Id , } ) if err != nil { return err } } } return nil }

// DeregisterInstance removes an instance from given service.

DeregisterInstance removes an instance from given service .


func (p *AWSSDProvider) targetToInstanceID(target string) string {
	if len(target) > 64 {
		hash := sha256.Sum256([]byte(strings.ToLower(target)))
		return hex.EncodeToString(hash[:])
	}

	return strings.ToLower(target)
}

func ( p * AWSSDProvider ) targetToInstanceID ( target string ) string { if len ( target ) > 64 { hash := sha256 . Sum256 ( [ ] byte ( strings . ToLower ( target ) ) ) return hex . EncodeToString ( hash [ : ] ) } return strings . ToLower ( target ) }

// Instance ID length is limited by AWS API to 64 characters. For longer strings SHA-256 hash will be used instead of
// the verbatim target to limit the length.

Instance ID length is limited by AWS API to 64 characters . For longer strings SHA - 256 hash will be used instead of the verbatim target to limit the length .


func matchingNamespaces(hostname string, namespaces []*sd.NamespaceSummary) []*sd.NamespaceSummary {
	matchingNamespaces := make([]*sd.NamespaceSummary, 0)

	for _, ns := range namespaces {
		if *ns.Name == hostname {
			matchingNamespaces = append(matchingNamespaces, ns)
		}
	}

	return matchingNamespaces
}

func matchingNamespaces ( hostname string , namespaces [ ] * sd . NamespaceSummary ) [ ] * sd . NamespaceSummary { matchingNamespaces := make ( [ ] * sd . NamespaceSummary , 0 ) for _ , ns := range namespaces { if * ns . Name == hostname { matchingNamespaces = append ( matchingNamespaces , ns ) } } return matchingNamespaces }

// returns list of all namespaces matching given hostname


func (p *AWSSDProvider) routingPolicyFromEndpoint(ep *endpoint.Endpoint) string {
	if ep.RecordType == endpoint.RecordTypeA {
		return sd.RoutingPolicyMultivalue
	}

	return sd.RoutingPolicyWeighted
}

func ( p * AWSSDProvider ) routingPolicyFromEndpoint ( ep * endpoint . Endpoint ) string { if ep . RecordType == endpoint . RecordTypeA { return sd . RoutingPolicyMultivalue } return sd . RoutingPolicyWeighted }

// determine service routing policy based on endpoint type


func (p *AWSSDProvider) isAWSLoadBalancer(hostname string) bool {
	return strings.HasSuffix(hostname, sdElbHostnameSuffix)
}

func ( p * AWSSDProvider ) isAWSLoadBalancer ( hostname string ) bool { return strings . HasSuffix ( hostname , sdElbHostnameSuffix ) }

// determine if a given hostname belongs to an AWS load balancer


func (s PerResource) ResolveUpdate(current *endpoint.Endpoint, candidates []*endpoint.Endpoint) *endpoint.Endpoint {
	currentResource := current.Labels[endpoint.ResourceLabelKey] // resource which has already acquired the DNS
	// TODO: sort candidates only needed because we can still have two endpoints from same resource here. We sort for consistency
	// TODO: remove once single endpoint can have multiple targets
	sort.SliceStable(candidates, func(i, j int) bool {
		return s.less(candidates[i], candidates[j])
	})
	for _, ep := range candidates {
		if ep.Labels[endpoint.ResourceLabelKey] == currentResource {
			return ep
		}
	}
	return s.ResolveCreate(candidates)
}

// ResolveUpdate is invoked when dns name is already owned by "current" endpoint
// ResolveUpdate uses "current" record as base and updates it accordingly with new version of same resource
// if it doesn't exist then pick min

ResolveUpdate is invoked when dns name is already owned by current endpoint ResolveUpdate uses current record as base and updates it accordingly with new version of same resource if it doesn t exist then pick min


func (s PerResource) less(x, y *endpoint.Endpoint) bool {
	return x.Targets.IsLess(y.Targets)
}

func ( s PerResource ) less ( x , y * endpoint . Endpoint ) bool { return x . Targets . IsLess ( y . Targets ) }

// less returns true if endpoint x is less than y


func NewDigitalOceanProvider(domainFilter DomainFilter, dryRun bool) (*DigitalOceanProvider, error) {
	token, ok := os.LookupEnv("DO_TOKEN")
	if !ok {
		return nil, fmt.Errorf("No token found")
	}
	oauthClient := oauth2.NewClient(oauth2.NoContext, oauth2.StaticTokenSource(&oauth2.Token{
		AccessToken: token,
	}))
	client := godo.NewClient(oauthClient)

	provider := &DigitalOceanProvider{
		Client:       client.Domains,
		domainFilter: domainFilter,
		DryRun:       dryRun,
	}
	return provider, nil
}

func NewDigitalOceanProvider ( domainFilter DomainFilter , dryRun bool ) ( * DigitalOceanProvider , error ) { token , ok := os . LookupEnv ( " " ) if ! ok { return nil , fmt . Errorf ( " " ) } oauthClient := oauth2 . NewClient ( oauth2 . NoContext , oauth2 . StaticTokenSource ( & oauth2 . Token { AccessToken : token , } ) ) client := godo . NewClient ( oauthClient ) provider := & DigitalOceanProvider { Client : client . Domains , domainFilter : domainFilter , DryRun : dryRun , } return provider , nil }

// NewDigitalOceanProvider initializes a new DigitalOcean DNS based Provider.

NewDigitalOceanProvider initializes a new DigitalOcean DNS based Provider .


func newDigitalOceanChanges(action string, endpoints []*endpoint.Endpoint) []*DigitalOceanChange {
	changes := make([]*DigitalOceanChange, 0, len(endpoints))

	for _, endpoint := range endpoints {
		changes = append(changes, newDigitalOceanChange(action, endpoint))
	}

	return changes
}

func newDigitalOceanChanges ( action string , endpoints [ ] * endpoint . Endpoint ) [ ] * DigitalOceanChange { changes := make ( [ ] * DigitalOceanChange , 0 , len ( endpoints ) ) for _ , endpoint := range endpoints { changes = append ( changes , newDigitalOceanChange ( action , endpoint ) ) } return changes }

// newDigitalOceanChanges returns a collection of Changes based on the given records and action.

newDigitalOceanChanges returns a collection of Changes based on the given records and action .


func (p *DigitalOceanProvider) getRecordID(records []godo.DomainRecord, record godo.DomainRecord) int {
	for _, zoneRecord := range records {
		if zoneRecord.Name == record.Name && zoneRecord.Type == record.Type {
			return zoneRecord.ID
		}
	}
	return 0
}

func ( p * DigitalOceanProvider ) getRecordID ( records [ ] godo . DomainRecord , record godo . DomainRecord ) int { for _ , zoneRecord := range records { if zoneRecord . Name == record . Name && zoneRecord . Type == record . Type { return zoneRecord . ID } } return 0 }

// getRecordID returns the ID from a record.
// the ID is mandatory to update and delete records

getRecordID returns the ID from a record . the ID is mandatory to update and delete records


func digitalOceanChangesByZone(zones []godo.Domain, changeSet []*DigitalOceanChange) map[string][]*DigitalOceanChange {
	changes := make(map[string][]*DigitalOceanChange)
	zoneNameIDMapper := zoneIDName{}
	for _, z := range zones {
		zoneNameIDMapper.Add(z.Name, z.Name)
		changes[z.Name] = []*DigitalOceanChange{}
	}

	for _, c := range changeSet {
		zone, _ := zoneNameIDMapper.FindZone(c.ResourceRecordSet.Name)
		if zone == "" {
			log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected ", c.ResourceRecordSet.Name)
			continue
		}
		changes[zone] = append(changes[zone], c)
	}

	return changes
}

func digitalOceanChangesByZone ( zones [ ] godo . Domain , changeSet [ ] * DigitalOceanChange ) map [ string ] [ ] * DigitalOceanChange { changes := make ( map [ string ] [ ] * DigitalOceanChange ) zoneNameIDMapper := zoneIDName { } for _ , z := range zones { zoneNameIDMapper . Add ( z . Name , z . Name ) changes [ z . Name ] = [ ] * DigitalOceanChange { } } for _ , c := range changeSet { zone , _ := zoneNameIDMapper . FindZone ( c . ResourceRecordSet . Name ) if zone == " " { log . Debugf ( " " , c . ResourceRecordSet . Name ) continue } changes [ zone ] = append ( changes [ zone ] , c ) } return changes }

// digitalOceanchangesByZone separates a multi-zone change into a single change per zone.

digitalOceanchangesByZone separates a multi - zone change into a single change per zone .


func legacyEndpointsFromService(svc *v1.Service, compatibility string) []*endpoint.Endpoint {
	switch compatibility {
	case "mate":
		return legacyEndpointsFromMateService(svc)
	case "molecule":
		return legacyEndpointsFromMoleculeService(svc)
	}

	return []*endpoint.Endpoint{}
}

func legacyEndpointsFromService ( svc * v1 . Service , compatibility string ) [ ] * endpoint . Endpoint { switch compatibility { case " " : return legacyEndpointsFromMateService ( svc ) case " " : return legacyEndpointsFromMoleculeService ( svc ) } return [ ] * endpoint . Endpoint { } }

// legacyEndpointsFromService tries to retrieve Endpoints from Services
// annotated with legacy annotations.

legacyEndpointsFromService tries to retrieve Endpoints from Services annotated with legacy annotations .


func legacyEndpointsFromMateService(svc *v1.Service) []*endpoint.Endpoint {
	var endpoints []*endpoint.Endpoint

	// Get the desired hostname of the service from the annotation.
	hostname, exists := svc.Annotations[mateAnnotationKey]
	if !exists {
		return nil
	}

	// Create a corresponding endpoint for each configured external entrypoint.
	for _, lb := range svc.Status.LoadBalancer.Ingress {
		if lb.IP != "" {
			endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeA, lb.IP))
		}
		if lb.Hostname != "" {
			endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeCNAME, lb.Hostname))
		}
	}

	return endpoints
}

// legacyEndpointsFromMateService tries to retrieve Endpoints from Services
// annotated with Mate's annotation semantics.

legacyEndpointsFromMateService tries to retrieve Endpoints from Services annotated with Mate s annotation semantics .


func legacyEndpointsFromMoleculeService(svc *v1.Service) []*endpoint.Endpoint {
	var endpoints []*endpoint.Endpoint

	// Check that the Service opted-in to being processed.
	if svc.Labels["dns"] != "route53" {
		return nil
	}

	// Get the desired hostname of the service from the annotation.
	hostnameAnnotation, exists := svc.Annotations[moleculeAnnotationKey]
	if !exists {
		return nil
	}

	hostnameList := strings.Split(strings.Replace(hostnameAnnotation, " ", "", -1), ",")

	for _, hostname := range hostnameList {
		// Create a corresponding endpoint for each configured external entrypoint.
		for _, lb := range svc.Status.LoadBalancer.Ingress {
			if lb.IP != "" {
				endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeA, lb.IP))
			}
			if lb.Hostname != "" {
				endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeCNAME, lb.Hostname))
			}
		}
	}

	return endpoints
}

// legacyEndpointsFromMoleculeService tries to retrieve Endpoints from Services
// annotated with Molecule Software's annotation semantics.

legacyEndpointsFromMoleculeService tries to retrieve Endpoints from Services annotated with Molecule Software s annotation semantics .


func getTargetsFromTargetAnnotation(annotations map[string]string) endpoint.Targets {
	var targets endpoint.Targets

	// Get the desired hostname of the ingress from the annotation.
	targetAnnotation, exists := annotations[targetAnnotationKey]
	if exists && targetAnnotation != "" {
		// splits the hostname annotation and removes the trailing periods
		targetsList := strings.Split(strings.Replace(targetAnnotation, " ", "", -1), ",")
		for _, targetHostname := range targetsList {
			targetHostname = strings.TrimSuffix(targetHostname, ".")
			targets = append(targets, targetHostname)
		}
	}
	return targets
}

// getTargetsFromTargetAnnotation gets endpoints from optional "target" annotation.
// Returns empty endpoints array if none are found.

getTargetsFromTargetAnnotation gets endpoints from optional target annotation . Returns empty endpoints array if none are found .


func suitableType(target string) string {
	if net.ParseIP(target) != nil {
		return endpoint.RecordTypeA
	}
	return endpoint.RecordTypeCNAME
}

func suitableType ( target string ) string { if net . ParseIP ( target ) != nil { return endpoint . RecordTypeA } return endpoint . RecordTypeCNAME }

// suitableType returns the DNS resource record type suitable for the target.
// In this case type A for IPs and type CNAME for everything else.

suitableType returns the DNS resource record type suitable for the target . In this case type A for IPs and type CNAME for everything else .


func endpointsForHostname(hostname string, targets endpoint.Targets, ttl endpoint.TTL, providerSpecific endpoint.ProviderSpecific) []*endpoint.Endpoint {
	var endpoints []*endpoint.Endpoint

	var aTargets endpoint.Targets
	var cnameTargets endpoint.Targets

	for _, t := range targets {
		switch suitableType(t) {
		case endpoint.RecordTypeA:
			aTargets = append(aTargets, t)
		default:
			cnameTargets = append(cnameTargets, t)
		}
	}

	if len(aTargets) > 0 {
		epA := &endpoint.Endpoint{
			DNSName:          strings.TrimSuffix(hostname, "."),
			Targets:          aTargets,
			RecordTTL:        ttl,
			RecordType:       endpoint.RecordTypeA,
			Labels:           endpoint.NewLabels(),
			ProviderSpecific: providerSpecific,
		}
		endpoints = append(endpoints, epA)
	}

	if len(cnameTargets) > 0 {
		epCNAME := &endpoint.Endpoint{
			DNSName:          strings.TrimSuffix(hostname, "."),
			Targets:          cnameTargets,
			RecordTTL:        ttl,
			RecordType:       endpoint.RecordTypeCNAME,
			Labels:           endpoint.NewLabels(),
			ProviderSpecific: providerSpecific,
		}
		endpoints = append(endpoints, epCNAME)
	}

	return endpoints
}

func endpointsForHostname ( hostname string , targets endpoint . Targets , ttl endpoint . TTL , providerSpecific endpoint . ProviderSpecific ) [ ] * endpoint . Endpoint { var endpoints [ ] * endpoint . Endpoint var aTargets endpoint . Targets var cnameTargets endpoint . Targets for _ , t := range targets { switch suitableType ( t ) { case endpoint . RecordTypeA : aTargets = append ( aTargets , t ) default : cnameTargets = append ( cnameTargets , t ) } } if len ( aTargets ) > 0 { epA := & endpoint . Endpoint { DNSName : strings . TrimSuffix ( hostname , " " ) , Targets : aTargets , RecordTTL : ttl , RecordType : endpoint . RecordTypeA , Labels : endpoint . NewLabels ( ) , ProviderSpecific : providerSpecific , } endpoints = append ( endpoints , epA ) } if len ( cnameTargets ) > 0 { epCNAME := & endpoint . Endpoint { DNSName : strings . TrimSuffix ( hostname , " " ) , Targets : cnameTargets , RecordTTL : ttl , RecordType : endpoint . RecordTypeCNAME , Labels : endpoint . NewLabels ( ) , ProviderSpecific : providerSpecific , } endpoints = append ( endpoints , epCNAME ) } return endpoints }

// endpointsForHostname returns the endpoint objects for each host-target combination.

endpointsForHostname returns the endpoint objects for each host - target combination .


func NewDynProvider(config DynConfig) (Provider, error) {
	return &dynProviderState{
		DynConfig: config,
		ZoneSnapshot: &ZoneSnapshot{
			endpoints: map[string][]*endpoint.Endpoint{},
			serials:   map[string]int{},
		},
	}, nil
}

func NewDynProvider ( config DynConfig ) ( Provider , error ) { return & dynProviderState { DynConfig : config , ZoneSnapshot : & ZoneSnapshot { endpoints : map [ string ] [ ] * endpoint . Endpoint { } , serials : map [ string ] int { } , } , } , nil }

// NewDynProvider initializes a new Dyn Provider.

NewDynProvider initializes a new Dyn Provider .


func endpointToRecord(ep *endpoint.Endpoint) *dynect.DataBlock {
	result := dynect.DataBlock{}

	if ep.RecordType == endpoint.RecordTypeA {
		result.Address = ep.Targets[0]
	} else if ep.RecordType == endpoint.RecordTypeCNAME {
		result.CName = ep.Targets[0]
	} else if ep.RecordType == endpoint.RecordTypeTXT {
		result.TxtData = ep.Targets[0]
	}

	return &result
}

func endpointToRecord ( ep * endpoint . Endpoint ) * dynect . DataBlock { result := dynect . DataBlock { } if ep . RecordType == endpoint . RecordTypeA { result . Address = ep . Targets [ 0 ] } else if ep . RecordType == endpoint . RecordTypeCNAME { result . CName = ep . Targets [ 0 ] } else if ep . RecordType == endpoint . RecordTypeTXT { result . TxtData = ep . Targets [ 0 ] } return & result }

// endpointToRecord puts the Target of an Endpoint in the correct field of DataBlock.
// See DataBlock comments for more info

endpointToRecord puts the Target of an Endpoint in the correct field of DataBlock . See DataBlock comments for more info


func (d *dynProviderState) fetchAllRecordsInZone(zone string) (*dynectsoap.GetAllRecordsResponseType, error) {
	var err error
	client := dynectsoap.NewClient("https://api2.dynect.net/SOAP/")
	service := dynectsoap.NewDynect(client)

	sessionRequest := dynectsoap.SessionLoginRequestType{
		Customer_name:  d.CustomerName,
		User_name:      d.Username,
		Password:       d.Password,
		Fault_incompat: 0,
	}
	resp := dynectsoap.SessionLoginResponseType{}
	err = apiRetryLoop(func() error {
		return service.Do(&sessionRequest, &resp)
	})

	if err != nil {
		return nil, err
	}

	token := resp.Data.Token

	logoutRequest := dynectsoap.SessionLogoutRequestType{
		Token:          token,
		Fault_incompat: 0,
	}
	logoutResponse := dynectsoap.SessionLogoutResponseType{}
	defer service.Do(&logoutRequest, &logoutResponse)

	req := dynectsoap.GetAllRecordsRequestType{
		Token:          token,
		Zone:           zone,
		Fault_incompat: 0,
	}
	records := dynectsoap.GetAllRecordsResponseType{}
	err = apiRetryLoop(func() error {
		return service.Do(&req, &records)
	})

	if err != nil {
		return nil, err
	}
	log.Debugf("Got all Records, status is %s", records.Status)

	if strings.ToLower(records.Status) == "incomplete" {
		jobRequest := dynectsoap.GetJobRequestType{
			Token:          token,
			Job_id:         records.Job_id,
			Fault_incompat: 0,
		}

		jobResults := dynectsoap.GetJobResponseType{}
		err = apiRetryLoop(func() error {
			return service.GetJobRetry(&jobRequest, &jobResults)
		})
		if err != nil {
			return nil, err
		}
		return jobResults.Data.(*dynectsoap.GetAllRecordsResponseType), nil
	}

	return &records, nil

}

func ( d * dynProviderState ) fetchAllRecordsInZone ( zone string ) ( * dynectsoap . GetAllRecordsResponseType , error ) { var err error client := dynectsoap . NewClient ( " " ) service := dynectsoap . NewDynect ( client ) sessionRequest := dynectsoap . SessionLoginRequestType { Customer_name : d . CustomerName , User_name : d . Username , Password : d . Password , Fault_incompat : 0 , } resp := dynectsoap . SessionLoginResponseType { } err = apiRetryLoop ( func ( ) error { return service . Do ( & sessionRequest , & resp ) } ) if err != nil { return nil , err } token := resp . Data . Token logoutRequest := dynectsoap . SessionLogoutRequestType { Token : token , Fault_incompat : 0 , } logoutResponse := dynectsoap . SessionLogoutResponseType { } defer service . Do ( & logoutRequest , & logoutResponse ) req := dynectsoap . GetAllRecordsRequestType { Token : token , Zone : zone , Fault_incompat : 0 , } records := dynectsoap . GetAllRecordsResponseType { } err = apiRetryLoop ( func ( ) error { return service . Do ( & req , & records ) } ) if err != nil { return nil , err } log . Debugf ( " " , records . Status ) if strings . ToLower ( records . Status ) == " " { jobRequest := dynectsoap . GetJobRequestType { Token : token , Job_id : records . Job_id , Fault_incompat : 0 , } jobResults := dynectsoap . GetJobResponseType { } err = apiRetryLoop ( func ( ) error { return service . GetJobRetry ( & jobRequest , & jobResults ) } ) if err != nil { return nil , err } return jobResults . Data . ( * dynectsoap . GetAllRecordsResponseType ) , nil } return & records , nil }

//Use SOAP to fetch all records with a single call


func (d *dynProviderState) zones(client *dynect.Client) []string {
	return d.ZoneIDFilter.zoneIDs
}

func ( d * dynProviderState ) zones ( client * dynect . Client ) [ ] string { return d . ZoneIDFilter . zoneIDs }

// the zones we are allowed to touch. Currently only exact matches are considered, not all
// zones with the given suffix

the zones we are allowed to touch . Currently only exact matches are considered not all zones with the given suffix


func (d *dynProviderState) replaceRecord(client *dynect.Client, ep *endpoint.Endpoint) error {
	link, record := d.buildRecordRequest(ep)
	if link == "" {
		return nil
	}

	response := dynect.RecordResponse{}
	err := apiRetryLoop(func() error {
		return client.Do("PUT", link, record, &response)
	})

	log.Debugf("Replacing record %s: %+v,", link, errorOrValue(err, &response))
	return err
}

func ( d * dynProviderState ) replaceRecord ( client * dynect . Client , ep * endpoint . Endpoint ) error { link , record := d . buildRecordRequest ( ep ) if link == " " { return nil } response := dynect . RecordResponse { } err := apiRetryLoop ( func ( ) error { return client . Do ( " " , link , record , & response ) } ) log . Debugf ( " " , link , errorOrValue ( err , & response ) ) return err }

// replaceRecord replaces all existing records pf the given type for the Endpoint.DNSName with 1 API call

replaceRecord replaces all existing records pf the given type for the Endpoint . DNSName with 1 API call


func (d *dynProviderState) commit(client *dynect.Client) error {
	errs := []error{}

	for _, zone := range d.zones(client) {
		// extra call if in debug mode to fetch pending changes
		if log.GetLevel() >= log.DebugLevel {
			response := ZoneChangesResponse{}
			err := client.Do("GET", fmt.Sprintf("ZoneChanges/%s/", zone), nil, &response)
			log.Debugf("Pending changes for zone %s: %+v", zone, errorOrValue(err, &response))
		}

		h, err := os.Hostname()
		if err != nil {
			h = "unknown-host"
		}
		notes := fmt.Sprintf("Change by external-dns@%s, DynAPI@%s, %s on %s",
			d.AppVersion,
			d.DynVersion,
			time.Now().Format(time.RFC3339),
			h,
		)

		zonePublish := ZonePublishRequest{
			Publish: true,
			Notes:   notes,
		}

		response := ZonePublishResponse{}

		// always retry the commit: don't waste the good work so far
		err = apiRetryLoop(func() error {
			return client.Do("PUT", fmt.Sprintf("Zone/%s/", zone), &zonePublish, &response)
		})
		log.Infof("Committing changes for zone %s: %+v", zone, errorOrValue(err, &response))
	}

	switch len(errs) {
	case 0:
		return nil
	case 1:
		return errs[0]
	default:
		return fmt.Errorf("Multiple errors committing: %+v", errs)
	}
}

// commit commits all pending changes. It will always attempt to commit, if there are no

commit commits all pending changes . It will always attempt to commit if there are no


func (ms *multiSource) Endpoints() ([]*endpoint.Endpoint, error) {
	result := []*endpoint.Endpoint{}

	for _, s := range ms.children {
		endpoints, err := s.Endpoints()
		if err != nil {
			return nil, err
		}

		result = append(result, endpoints...)
	}

	return result, nil
}

func ( ms * multiSource ) Endpoints ( ) ( [ ] * endpoint . Endpoint , error ) { result := [ ] * endpoint . Endpoint { } for _ , s := range ms . children { endpoints , err := s . Endpoints ( ) if err != nil { return nil , err } result = append ( result , endpoints ... ) } return result , nil }

// Endpoints collects endpoints of all nested Sources and returns them in a single slice.

Endpoints collects endpoints of all nested Sources and returns them in a single slice .


func newDesignateClient() (designateClientInterface, error) {
	serviceClient, err := createDesignateServiceClient()
	if err != nil {
		return nil, err
	}
	return &designateClient{serviceClient}, nil
}

func newDesignateClient ( ) ( designateClientInterface , error ) { serviceClient , err := createDesignateServiceClient ( ) if err != nil { return nil , err } return & designateClient { serviceClient } , nil }

// factory function for the designateClientInterface


func remapEnv(mapping map[string]string) {
	for k, v := range mapping {
		currentVal := os.Getenv(k)
		newVal := os.Getenv(v)
		if currentVal == "" && newVal != "" {
			os.Setenv(k, newVal)
		}
	}
}

func remapEnv ( mapping map [ string ] string ) { for k , v := range mapping { currentVal := os . Getenv ( k ) newVal := os . Getenv ( v ) if currentVal == " " && newVal != " " { os . Setenv ( k , newVal ) } } }

// copies environment variables to new names without overwriting existing values


func createDesignateServiceClient() (*gophercloud.ServiceClient, error) {
	opts, err := getAuthSettings()
	if err != nil {
		return nil, err
	}
	log.Infof("Using OpenStack Keystone at %s", opts.IdentityEndpoint)
	authProvider, err := openstack.NewClient(opts.IdentityEndpoint)
	if err != nil {
		return nil, err
	}

	tlsConfig, err := tlsutils.CreateTLSConfig("OPENSTACK")
	if err != nil {
		return nil, err
	}

	transport := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext,
		MaxIdleConns:          100,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
		TLSClientConfig:       tlsConfig,
	}
	authProvider.HTTPClient.Transport = transport

	if err = openstack.Authenticate(authProvider, opts); err != nil {
		return nil, err
	}

	eo := gophercloud.EndpointOpts{
		Region: os.Getenv("OS_REGION_NAME"),
	}

	client, err := openstack.NewDNSV2(authProvider, eo)
	if err != nil {
		return nil, err
	}
	log.Infof("Found OpenStack Designate service at %s", client.Endpoint)
	return client, nil
}

func createDesignateServiceClient ( ) ( * gophercloud . ServiceClient , error ) { opts , err := getAuthSettings ( ) if err != nil { return nil , err } log . Infof ( " " , opts . IdentityEndpoint ) authProvider , err := openstack . NewClient ( opts . IdentityEndpoint ) if err != nil { return nil , err } tlsConfig , err := tlsutils . CreateTLSConfig ( " " ) if err != nil { return nil , err } transport := & http . Transport { Proxy : http . ProxyFromEnvironment , DialContext : ( & net . Dialer { Timeout : 30 * time . Second , KeepAlive : 30 * time . Second , } ) . DialContext , MaxIdleConns : 100 , IdleConnTimeout : 90 * time . Second , TLSHandshakeTimeout : 10 * time . Second , ExpectContinueTimeout : 1 * time . Second , TLSClientConfig : tlsConfig , } authProvider . HTTPClient . Transport = transport if err = openstack . Authenticate ( authProvider , opts ) ; err != nil { return nil , err } eo := gophercloud . EndpointOpts { Region : os . Getenv ( " " ) , } client , err := openstack . NewDNSV2 ( authProvider , eo ) if err != nil { return nil , err } log . Infof ( " " , client . Endpoint ) return client , nil }

// authenticate in OpenStack and obtain Designate service endpoint


func (c designateClient) ForEachZone(handler func(zone *zones.Zone) error) error {
	pager := zones.List(c.serviceClient, zones.ListOpts{})
	return pager.EachPage(
		func(page pagination.Page) (bool, error) {
			list, err := zones.ExtractZones(page)
			if err != nil {
				return false, err
			}
			for _, zone := range list {
				err := handler(&zone)
				if err != nil {
					return false, err
				}
			}
			return true, nil
		},
	)
}

func ( c designateClient ) ForEachZone ( handler func ( zone * zones . Zone ) error ) error { pager := zones . List ( c . serviceClient , zones . ListOpts { } ) return pager . EachPage ( func ( page pagination . Page ) ( bool , error ) { list , err := zones . ExtractZones ( page ) if err != nil { return false , err } for _ , zone := range list { err := handler ( & zone ) if err != nil { return false , err } } return true , nil } , ) }

// ForEachZone calls handler for each zone managed by the Designate


func (c designateClient) ForEachRecordSet(zoneID string, handler func(recordSet *recordsets.RecordSet) error) error {
	pager := recordsets.ListByZone(c.serviceClient, zoneID, recordsets.ListOpts{})
	return pager.EachPage(
		func(page pagination.Page) (bool, error) {
			list, err := recordsets.ExtractRecordSets(page)
			if err != nil {
				return false, err
			}
			for _, recordSet := range list {
				err := handler(&recordSet)
				if err != nil {
					return false, err
				}
			}
			return true, nil
		},
	)
}

func ( c designateClient ) ForEachRecordSet ( zoneID string , handler func ( recordSet * recordsets . RecordSet ) error ) error { pager := recordsets . ListByZone ( c . serviceClient , zoneID , recordsets . ListOpts { } ) return pager . EachPage ( func ( page pagination . Page ) ( bool , error ) { list , err := recordsets . ExtractRecordSets ( page ) if err != nil { return false , err } for _ , recordSet := range list { err := handler ( & recordSet ) if err != nil { return false , err } } return true , nil } , ) }

// ForEachRecordSet calls handler for each recordset in the given DNS zone


func (c designateClient) CreateRecordSet(zoneID string, opts recordsets.CreateOpts) (string, error) {
	r, err := recordsets.Create(c.serviceClient, zoneID, opts).Extract()
	if err != nil {
		return "", err
	}
	return r.ID, nil
}

func ( c designateClient ) CreateRecordSet ( zoneID string , opts recordsets . CreateOpts ) ( string , error ) { r , err := recordsets . Create ( c . serviceClient , zoneID , opts ) . Extract ( ) if err != nil { return " " , err } return r . ID , nil }

// CreateRecordSet creates recordset in the given DNS zone


func (c designateClient) UpdateRecordSet(zoneID, recordSetID string, opts recordsets.UpdateOpts) error {
	_, err := recordsets.Update(c.serviceClient, zoneID, recordSetID, opts).Extract()
	return err
}

func ( c designateClient ) UpdateRecordSet ( zoneID , recordSetID string , opts recordsets . UpdateOpts ) error { _ , err := recordsets . Update ( c . serviceClient , zoneID , recordSetID , opts ) . Extract ( ) return err }

// UpdateRecordSet updates recordset in the given DNS zone


func (c designateClient) DeleteRecordSet(zoneID, recordSetID string) error {
	return recordsets.Delete(c.serviceClient, zoneID, recordSetID).ExtractErr()
}

func ( c designateClient ) DeleteRecordSet ( zoneID , recordSetID string ) error { return recordsets . Delete ( c . serviceClient , zoneID , recordSetID ) . ExtractErr ( ) }

// DeleteRecordSet deletes recordset in the given DNS zone


func NewDesignateProvider(domainFilter DomainFilter, dryRun bool) (Provider, error) {
	client, err := newDesignateClient()
	if err != nil {
		return nil, err
	}
	return &designateProvider{
		client:       client,
		domainFilter: domainFilter,
		dryRun:       dryRun,
	}, nil
}

func NewDesignateProvider ( domainFilter DomainFilter , dryRun bool ) ( Provider , error ) { client , err := newDesignateClient ( ) if err != nil { return nil , err } return & designateProvider { client : client , domainFilter : domainFilter , dryRun : dryRun , } , nil }

// NewDesignateProvider is a factory function for OpenStack designate providers


func canonicalizeDomainNames(domains []string) []string {
	var cDomains []string
	for _, d := range domains {
		if !strings.HasSuffix(d, ".") {
			d += "."
			cDomains = append(cDomains, strings.ToLower(d))
		}
	}
	return cDomains
}

func canonicalizeDomainNames ( domains [ ] string ) [ ] string { var cDomains [ ] string for _ , d := range domains { if ! strings . HasSuffix ( d , " " ) { d += " " cDomains = append ( cDomains , strings . ToLower ( d ) ) } } return cDomains }


func canonicalizeDomainName(d string) string {
	if !strings.HasSuffix(d, ".") {
		d += "."
	}
	return strings.ToLower(d)
}

func canonicalizeDomainName ( d string ) string { if ! strings . HasSuffix ( d , " " ) { d += " " } return strings . ToLower ( d ) }


func (p designateProvider) getZones() (map[string]string, error) {
	result := map[string]string{}

	err := p.client.ForEachZone(
		func(zone *zones.Zone) error {
			if zone.Type != "" && strings.ToUpper(zone.Type) != "PRIMARY" || zone.Status != "ACTIVE" {
				return nil
			}

			zoneName := canonicalizeDomainName(zone.Name)
			if !p.domainFilter.Match(zoneName) {
				return nil
			}
			result[zone.ID] = zoneName
			return nil
		},
	)

	return result, err
}

func ( p designateProvider ) getZones ( ) ( map [ string ] string , error ) { result := map [ string ] string { } err := p . client . ForEachZone ( func ( zone * zones . Zone ) error { if zone . Type != " " && strings . ToUpper ( zone . Type ) != " " || zone . Status != " " { return nil } zoneName := canonicalizeDomainName ( zone . Name ) if ! p . domainFilter . Match ( zoneName ) { return nil } result [ zone . ID ] = zoneName return nil } , ) return result , err }

// returns ZoneID -> ZoneName mapping for zones that are managed by the Designate and match domain filter

returns ZoneID - > ZoneName mapping for zones that are managed by the Designate and match domain filter


func (p designateProvider) getHostZoneID(hostname string, managedZones map[string]string) (string, error) {
	longestZoneLength := 0
	resultID := ""

	for zoneID, zoneName := range managedZones {
		if !strings.HasSuffix(hostname, zoneName) {
			continue
		}
		ln := len(zoneName)
		if ln > longestZoneLength {
			resultID = zoneID
			longestZoneLength = ln
		}
	}

	return resultID, nil
}

func ( p designateProvider ) getHostZoneID ( hostname string , managedZones map [ string ] string ) ( string , error ) { longestZoneLength := 0 resultID := " " for zoneID , zoneName := range managedZones { if ! strings . HasSuffix ( hostname , zoneName ) { continue } ln := len ( zoneName ) if ln > longestZoneLength { resultID = zoneID longestZoneLength = ln } } return resultID , nil }

// finds best suitable DNS zone for the hostname


func addEndpoint(ep *endpoint.Endpoint, recordSets map[string]*recordSet, delete bool) {
	key := fmt.Sprintf("%s/%s", ep.DNSName, ep.RecordType)
	rs := recordSets[key]
	if rs == nil {
		rs = &recordSet{
			dnsName:    canonicalizeDomainName(ep.DNSName),
			recordType: ep.RecordType,
			names:      make(map[string]bool),
		}
	}
	if rs.zoneID == "" {
		rs.zoneID = ep.Labels[designateZoneID]
	}
	if rs.recordSetID == "" {
		rs.recordSetID = ep.Labels[designateRecordSetID]
	}
	for _, rec := range strings.Split(ep.Labels[designateOriginalRecords], "\000") {
		if _, ok := rs.names[rec]; !ok && rec != "" {
			rs.names[rec] = true
		}
	}
	targets := ep.Targets
	if ep.RecordType == endpoint.RecordTypeCNAME {
		targets = canonicalizeDomainNames(targets)
	}
	for _, t := range targets {
		rs.names[t] = !delete
	}
	recordSets[key] = rs
}

func addEndpoint ( ep * endpoint . Endpoint , recordSets map [ string ] * recordSet , delete bool ) { key := fmt . Sprintf ( " " , ep . DNSName , ep . RecordType ) rs := recordSets [ key ] if rs == nil { rs = & recordSet { dnsName : canonicalizeDomainName ( ep . DNSName ) , recordType : ep . RecordType , names : make ( map [ string ] bool ) , } } if rs . zoneID == " " { rs . zoneID = ep . Labels [ designateZoneID ] } if rs . recordSetID == " " { rs . recordSetID = ep . Labels [ designateRecordSetID ] } for _ , rec := range strings . Split ( ep . Labels [ designateOriginalRecords ] , " \000 " ) { if _ , ok := rs . names [ rec ] ; ! ok && rec != " " { rs . names [ rec ] = true } } targets := ep . Targets if ep . RecordType == endpoint . RecordTypeCNAME { targets = canonicalizeDomainNames ( targets ) } for _ , t := range targets { rs . names [ t ] = ! delete } recordSets [ key ] = rs }

// adds endpoint into recordset aggregation, loading original values from endpoint labels first

adds endpoint into recordset aggregation loading original values from endpoint labels first


func NewIstioGatewaySource(
	kubeClient kubernetes.Interface,
	istioClient istiomodel.ConfigStore,
	istioIngressGatewayServices []string,
	namespace string,
	annotationFilter string,
	fqdnTemplate string,
	combineFqdnAnnotation bool,
	ignoreHostnameAnnotation bool,
) (Source, error) {
	var (
		tmpl *template.Template
		err  error
	)
	for _, lbService := range istioIngressGatewayServices {
		if _, _, err = parseIngressGateway(lbService); err != nil {
			return nil, err
		}
	}

	if fqdnTemplate != "" {
		tmpl, err = template.New("endpoint").Funcs(template.FuncMap{
			"trimPrefix": strings.TrimPrefix,
		}).Parse(fqdnTemplate)
		if err != nil {
			return nil, err
		}
	}

	return &gatewaySource{
		kubeClient:                  kubeClient,
		istioClient:                 istioClient,
		istioIngressGatewayServices: istioIngressGatewayServices,
		namespace:                   namespace,
		annotationFilter:            annotationFilter,
		fqdnTemplate:                tmpl,
		combineFQDNAnnotation:       combineFqdnAnnotation,
		ignoreHostnameAnnotation:    ignoreHostnameAnnotation,
	}, nil
}

func NewIstioGatewaySource ( kubeClient kubernetes . Interface , istioClient istiomodel . ConfigStore , istioIngressGatewayServices [ ] string , namespace string , annotationFilter string , fqdnTemplate string , combineFqdnAnnotation bool , ignoreHostnameAnnotation bool , ) ( Source , error ) { var ( tmpl * template . Template err error ) for _ , lbService := range istioIngressGatewayServices { if _ , _ , err = parseIngressGateway ( lbService ) ; err != nil { return nil , err } } if fqdnTemplate != " " { tmpl , err = template . New ( " " ) . Funcs ( template . FuncMap { " " : strings . TrimPrefix , } ) . Parse ( fqdnTemplate ) if err != nil { return nil , err } } return & gatewaySource { kubeClient : kubeClient , istioClient : istioClient , istioIngressGatewayServices : istioIngressGatewayServices , namespace : namespace , annotationFilter : annotationFilter , fqdnTemplate : tmpl , combineFQDNAnnotation : combineFqdnAnnotation , ignoreHostnameAnnotation : ignoreHostnameAnnotation , } , nil }

// NewIstioGatewaySource creates a new gatewaySource with the given config.

NewIstioGatewaySource creates a new gatewaySource with the given config .


func (sc *gatewaySource) filterByAnnotations(configs []istiomodel.Config) ([]istiomodel.Config, error) {
	labelSelector, err := metav1.ParseToLabelSelector(sc.annotationFilter)
	if err != nil {
		return nil, err
	}
	selector, err := metav1.LabelSelectorAsSelector(labelSelector)
	if err != nil {
		return nil, err
	}

	// empty filter returns original list
	if selector.Empty() {
		return configs, nil
	}

	filteredList := []istiomodel.Config{}

	for _, config := range configs {
		// convert the annotations to an equivalent label selector
		annotations := labels.Set(config.Annotations)

		// include if the annotations match the selector
		if selector.Matches(annotations) {
			filteredList = append(filteredList, config)
		}
	}

	return filteredList, nil
}

// filterByAnnotations filters a list of configs by a given annotation selector.

filterByAnnotations filters a list of configs by a given annotation selector .


func (sc *gatewaySource) endpointsFromGatewayConfig(config istiomodel.Config) ([]*endpoint.Endpoint, error) {
	var endpoints []*endpoint.Endpoint

	ttl, err := getTTLFromAnnotations(config.Annotations)
	if err != nil {
		log.Warn(err)
	}

	targets := getTargetsFromTargetAnnotation(config.Annotations)

	if len(targets) == 0 {
		targets, err = sc.targetsFromIstioIngressGatewayServices()
		if err != nil {
			return nil, err
		}
	}

	gateway := config.Spec.(*istionetworking.Gateway)

	providerSpecific := getProviderSpecificAnnotations(config.Annotations)

	for _, server := range gateway.Servers {
		for _, host := range server.Hosts {
			if host == "" {
				continue
			}
			endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific)...)
		}
	}

	// Skip endpoints if we do not want entries from annotations
	if !sc.ignoreHostnameAnnotation {
		hostnameList := getHostnamesFromAnnotations(config.Annotations)
		for _, hostname := range hostnameList {
			endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...)
		}
	}

	return endpoints, nil
}

// endpointsFromGatewayConfig extracts the endpoints from an Istio Gateway Config object


func (in *DNSEndpoint) DeepCopy() *DNSEndpoint {
	if in == nil {
		return nil
	}
	out := new(DNSEndpoint)
	in.DeepCopyInto(out)
	return out
}

func ( in * DNSEndpoint ) DeepCopy ( ) * DNSEndpoint { if in == nil { return nil } out := new ( DNSEndpoint ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSEndpoint.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new DNSEndpoint .


func (in *DNSEndpointList) DeepCopy() *DNSEndpointList {
	if in == nil {
		return nil
	}
	out := new(DNSEndpointList)
	in.DeepCopyInto(out)
	return out
}

func ( in * DNSEndpointList ) DeepCopy ( ) * DNSEndpointList { if in == nil { return nil } out := new ( DNSEndpointList ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSEndpointList.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new DNSEndpointList .


func (in *DNSEndpointSpec) DeepCopy() *DNSEndpointSpec {
	if in == nil {
		return nil
	}
	out := new(DNSEndpointSpec)
	in.DeepCopyInto(out)
	return out
}

func ( in * DNSEndpointSpec ) DeepCopy ( ) * DNSEndpointSpec { if in == nil { return nil } out := new ( DNSEndpointSpec ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSEndpointSpec.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new DNSEndpointSpec .


func (in *DNSEndpointStatus) DeepCopy() *DNSEndpointStatus {
	if in == nil {
		return nil
	}
	out := new(DNSEndpointStatus)
	in.DeepCopyInto(out)
	return out
}

func ( in * DNSEndpointStatus ) DeepCopy ( ) * DNSEndpointStatus { if in == nil { return nil } out := new ( DNSEndpointStatus ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSEndpointStatus.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new DNSEndpointStatus .


func (in *Endpoint) DeepCopy() *Endpoint {
	if in == nil {
		return nil
	}
	out := new(Endpoint)
	in.DeepCopyInto(out)
	return out
}

func ( in * Endpoint ) DeepCopy ( ) * Endpoint { if in == nil { return nil } out := new ( Endpoint ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Endpoint.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new Endpoint .


func (in Labels) DeepCopy() Labels {
	if in == nil {
		return nil
	}
	out := new(Labels)
	in.DeepCopyInto(out)
	return *out
}

func ( in Labels ) DeepCopy ( ) Labels { if in == nil { return nil } out := new ( Labels ) in . DeepCopyInto ( out ) return * out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Labels.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new Labels .


func (in ProviderSpecific) DeepCopy() ProviderSpecific {
	if in == nil {
		return nil
	}
	out := new(ProviderSpecific)
	in.DeepCopyInto(out)
	return *out
}

func ( in ProviderSpecific ) DeepCopy ( ) ProviderSpecific { if in == nil { return nil } out := new ( ProviderSpecific ) in . DeepCopyInto ( out ) return * out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderSpecific.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new ProviderSpecific .


func (in Targets) DeepCopy() Targets {
	if in == nil {
		return nil
	}
	out := new(Targets)
	in.DeepCopyInto(out)
	return *out
}

func ( in Targets ) DeepCopy ( ) Targets { if in == nil { return nil } out := new ( Targets ) in . DeepCopyInto ( out ) return * out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Targets.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new Targets .


func ensureTrailingDot(hostname string) string {
	if net.ParseIP(hostname) != nil {
		return hostname
	}

	return strings.TrimSuffix(hostname, ".") + "."
}

func ensureTrailingDot ( hostname string ) string { if net . ParseIP ( hostname ) != nil { return hostname } return strings . TrimSuffix ( hostname , " " ) + " " }

// ensureTrailingDot ensures that the hostname receives a trailing dot if it hasn't already.

ensureTrailingDot ensures that the hostname receives a trailing dot if it hasn t already .


func NewNoopRegistry(provider provider.Provider) (*NoopRegistry, error) {
	return &NoopRegistry{
		provider: provider,
	}, nil
}

func NewNoopRegistry ( provider provider . Provider ) ( * NoopRegistry , error ) { return & NoopRegistry { provider : provider , } , nil }

// NewNoopRegistry returns new NoopRegistry object


func (im *NoopRegistry) ApplyChanges(changes *plan.Changes) error {
	return im.provider.ApplyChanges(changes)
}

func ( im * NoopRegistry ) ApplyChanges ( changes * plan . Changes ) error { return im . provider . ApplyChanges ( changes ) }

// ApplyChanges propagates changes to the dns provider


func (ms *dedupSource) Endpoints() ([]*endpoint.Endpoint, error) {
	result := []*endpoint.Endpoint{}
	collected := map[string]bool{}

	endpoints, err := ms.source.Endpoints()
	if err != nil {
		return nil, err
	}

	for _, ep := range endpoints {
		identifier := ep.DNSName + " / " + ep.Targets.String()

		if _, ok := collected[identifier]; ok {
			log.Debugf("Removing duplicate endpoint %s", ep)
			continue
		}

		collected[identifier] = true
		result = append(result, ep)
	}

	return result, nil
}

func ( ms * dedupSource ) Endpoints ( ) ( [ ] * endpoint . Endpoint , error ) { result := [ ] * endpoint . Endpoint { } collected := map [ string ] bool { } endpoints , err := ms . source . Endpoints ( ) if err != nil { return nil , err } for _ , ep := range endpoints { identifier := ep . DNSName + " " + ep . Targets . String ( ) if _ , ok := collected [ identifier ] ; ok { log . Debugf ( " " , ep ) continue } collected [ identifier ] = true result = append ( result , ep ) } return result , nil }

// Endpoints collects endpoints from its wrapped source and returns them without duplicates.

Endpoints collects endpoints from its wrapped source and returns them without duplicates .


func NewFakeSource(fqdnTemplate string) (Source, error) {
	if fqdnTemplate == "" {
		fqdnTemplate = defaultFQDNTemplate
	}

	return &fakeSource{
		dnsName: fqdnTemplate,
	}, nil
}

func NewFakeSource ( fqdnTemplate string ) ( Source , error ) { if fqdnTemplate == " " { fqdnTemplate = defaultFQDNTemplate } return & fakeSource { dnsName : fqdnTemplate , } , nil }

// NewFakeSource creates a new fakeSource with the given config.

NewFakeSource creates a new fakeSource with the given config .


func CreateTLSConfig(prefix string) (*tls.Config, error) {
	caFile := os.Getenv(fmt.Sprintf("%s_CA_FILE", prefix))
	certFile := os.Getenv(fmt.Sprintf("%s_CERT_FILE", prefix))
	keyFile := os.Getenv(fmt.Sprintf("%s_KEY_FILE", prefix))
	serverName := os.Getenv(fmt.Sprintf("%s_TLS_SERVER_NAME", prefix))
	isInsecureStr := strings.ToLower(os.Getenv(fmt.Sprintf("%s_TLS_INSECURE", prefix)))
	isInsecure := isInsecureStr == "true" || isInsecureStr == "yes" || isInsecureStr == "1"
	tlsConfig, err := NewTLSConfig(certFile, keyFile, caFile, serverName, isInsecure, defaultMinVersion)
	if err != nil {
		return nil, err
	}
	return tlsConfig, nil
}

func CreateTLSConfig ( prefix string ) ( * tls . Config , error ) { caFile := os . Getenv ( fmt . Sprintf ( " " , prefix ) ) certFile := os . Getenv ( fmt . Sprintf ( " " , prefix ) ) keyFile := os . Getenv ( fmt . Sprintf ( " " , prefix ) ) serverName := os . Getenv ( fmt . Sprintf ( " " , prefix ) ) isInsecureStr := strings . ToLower ( os . Getenv ( fmt . Sprintf ( " " , prefix ) ) ) isInsecure := isInsecureStr == " " || isInsecureStr == " " || isInsecureStr == " " tlsConfig , err := NewTLSConfig ( certFile , keyFile , caFile , serverName , isInsecure , defaultMinVersion ) if err != nil { return nil , err } return tlsConfig , nil }

// CreateTLSConfig creates tls.Config instance from TLS parameters passed in environment variables with the given prefix

CreateTLSConfig creates tls . Config instance from TLS parameters passed in environment variables with the given prefix


func NewAWSSDRegistry(provider provider.Provider, ownerID string) (*AWSSDRegistry, error) {
	if ownerID == "" {
		return nil, errors.New("owner id cannot be empty")
	}
	return &AWSSDRegistry{
		provider: provider,
		ownerID:  ownerID,
	}, nil
}

func NewAWSSDRegistry ( provider provider . Provider , ownerID string ) ( * AWSSDRegistry , error ) { if ownerID == " " { return nil , errors . New ( " " ) } return & AWSSDRegistry { provider : provider , ownerID : ownerID , } , nil }

// NewAWSSDRegistry returns implementation of registry for AWS SD


func (sdr *AWSSDRegistry) ApplyChanges(changes *plan.Changes) error {
	filteredChanges := &plan.Changes{
		Create:    changes.Create,
		UpdateNew: filterOwnedRecords(sdr.ownerID, changes.UpdateNew),
		UpdateOld: filterOwnedRecords(sdr.ownerID, changes.UpdateOld),
		Delete:    filterOwnedRecords(sdr.ownerID, changes.Delete),
	}

	sdr.updateLabels(filteredChanges.Create)
	sdr.updateLabels(filteredChanges.UpdateNew)
	sdr.updateLabels(filteredChanges.UpdateOld)
	sdr.updateLabels(filteredChanges.Delete)

	return sdr.provider.ApplyChanges(filteredChanges)
}

func ( sdr * AWSSDRegistry ) ApplyChanges ( changes * plan . Changes ) error { filteredChanges := & plan . Changes { Create : changes . Create , UpdateNew : filterOwnedRecords ( sdr . ownerID , changes . UpdateNew ) , UpdateOld : filterOwnedRecords ( sdr . ownerID , changes . UpdateOld ) , Delete : filterOwnedRecords ( sdr . ownerID , changes . Delete ) , } sdr . updateLabels ( filteredChanges . Create ) sdr . updateLabels ( filteredChanges . UpdateNew ) sdr . updateLabels ( filteredChanges . UpdateOld ) sdr . updateLabels ( filteredChanges . Delete ) return sdr . provider . ApplyChanges ( filteredChanges ) }

// ApplyChanges filters out records not owned the External-DNS, additionally it adds the required label
// inserted in the AWS SD instance as a CreateID field

ApplyChanges filters out records not owned the External - DNS additionally it adds the required label inserted in the AWS SD instance as a CreateID field


func LoadOCIConfig(path string) (*OCIConfig, error) {
	contents, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, errors.Wrapf(err, "reading OCI config file %q", path)
	}

	cfg := OCIConfig{}
	if err := yaml.Unmarshal(contents, &cfg); err != nil {
		return nil, errors.Wrapf(err, "parsing OCI config file %q", path)
	}
	return &cfg, nil
}

func LoadOCIConfig ( path string ) ( * OCIConfig , error ) { contents , err := ioutil . ReadFile ( path ) if err != nil { return nil , errors . Wrapf ( err , " " , path ) } cfg := OCIConfig { } if err := yaml . Unmarshal ( contents , & cfg ) ; err != nil { return nil , errors . Wrapf ( err , " " , path ) } return & cfg , nil }

// LoadOCIConfig reads and parses the OCI ExternalDNS config file at the given
// path.

LoadOCIConfig reads and parses the OCI ExternalDNS config file at the given path .


func NewOCIProvider(cfg OCIConfig, domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool) (*OCIProvider, error) {
	var client ociDNSClient
	client, err := dns.NewDnsClientWithConfigurationProvider(common.NewRawConfigurationProvider(
		cfg.Auth.TenancyID,
		cfg.Auth.UserID,
		cfg.Auth.Region,
		cfg.Auth.Fingerprint,
		cfg.Auth.PrivateKey,
		&cfg.Auth.Passphrase,
	))
	if err != nil {
		return nil, errors.Wrap(err, "initialising OCI DNS API client")
	}

	return &OCIProvider{
		client:       client,
		cfg:          cfg,
		domainFilter: domainFilter,
		zoneIDFilter: zoneIDFilter,
		dryRun:       dryRun,
	}, nil
}

func NewOCIProvider ( cfg OCIConfig , domainFilter DomainFilter , zoneIDFilter ZoneIDFilter , dryRun bool ) ( * OCIProvider , error ) { var client ociDNSClient client , err := dns . NewDnsClientWithConfigurationProvider ( common . NewRawConfigurationProvider ( cfg . Auth . TenancyID , cfg . Auth . UserID , cfg . Auth . Region , cfg . Auth . Fingerprint , cfg . Auth . PrivateKey , & cfg . Auth . Passphrase , ) ) if err != nil { return nil , errors . Wrap ( err , " " ) } return & OCIProvider { client : client , cfg : cfg , domainFilter : domainFilter , zoneIDFilter : zoneIDFilter , dryRun : dryRun , } , nil }

// NewOCIProvider initialises a new OCI DNS based Provider.

NewOCIProvider initialises a new OCI DNS based Provider .


func (p *OCIProvider) ApplyChanges(changes *plan.Changes) error {
	log.Debugf("Processing chages: %+v", changes)

	ops := []dns.RecordOperation{}
	ops = append(ops, p.newFilteredRecordOperations(changes.Create, dns.RecordOperationOperationAdd)...)

	ops = append(ops, p.newFilteredRecordOperations(changes.UpdateNew, dns.RecordOperationOperationAdd)...)
	ops = append(ops, p.newFilteredRecordOperations(changes.UpdateOld, dns.RecordOperationOperationRemove)...)

	ops = append(ops, p.newFilteredRecordOperations(changes.Delete, dns.RecordOperationOperationRemove)...)

	if len(ops) == 0 {
		log.Info("All records are already up to date")
		return nil
	}

	ctx := context.Background()
	zones, err := p.zones(ctx)
	if err != nil {
		return errors.Wrap(err, "fetching zones")
	}

	// Separate into per-zone change sets to be passed to OCI API.
	opsByZone := operationsByZone(zones, ops)
	for zoneID, ops := range opsByZone {
		log.Infof("Change zone: %q", zoneID)
		for _, op := range ops {
			log.Info(op)
		}
	}

	if p.dryRun {
		return nil
	}

	for zoneID, ops := range opsByZone {
		if _, err := p.client.PatchZoneRecords(ctx, dns.PatchZoneRecordsRequest{
			CompartmentId:           &p.cfg.CompartmentID,
			ZoneNameOrId:            &zoneID,
			PatchZoneRecordsDetails: dns.PatchZoneRecordsDetails{Items: ops},
		}); err != nil {
			return err
		}
	}

	return nil
}

// ApplyChanges applies a given set of changes to a given zone.

ApplyChanges applies a given set of changes to a given zone .


func newRecordOperation(ep *endpoint.Endpoint, opType dns.RecordOperationOperationEnum) dns.RecordOperation {
	targets := make([]string, len(ep.Targets))
	copy(targets, []string(ep.Targets))
	if ep.RecordType == endpoint.RecordTypeCNAME {
		targets[0] = ensureTrailingDot(targets[0])
	}
	rdata := strings.Join(targets, " ")

	ttl := ociRecordTTL
	if ep.RecordTTL.IsConfigured() {
		ttl = int(ep.RecordTTL)
	}

	return dns.RecordOperation{
		Domain:    &ep.DNSName,
		Rdata:     &rdata,
		Ttl:       &ttl,
		Rtype:     &ep.RecordType,
		Operation: opType,
	}
}

func newRecordOperation ( ep * endpoint . Endpoint , opType dns . RecordOperationOperationEnum ) dns . RecordOperation { targets := make ( [ ] string , len ( ep . Targets ) ) copy ( targets , [ ] string ( ep . Targets ) ) if ep . RecordType == endpoint . RecordTypeCNAME { targets [ 0 ] = ensureTrailingDot ( targets [ 0 ] ) } rdata := strings . Join ( targets , " " ) ttl := ociRecordTTL if ep . RecordTTL . IsConfigured ( ) { ttl = int ( ep . RecordTTL ) } return dns . RecordOperation { Domain : & ep . DNSName , Rdata : & rdata , Ttl : & ttl , Rtype : & ep . RecordType , Operation : opType , } }

// newRecordOperation returns a RecordOperation based on a given endpoint.

newRecordOperation returns a RecordOperation based on a given endpoint .


func operationsByZone(zones map[string]*dns.ZoneSummary, ops []dns.RecordOperation) map[string][]dns.RecordOperation {
	changes := make(map[string][]dns.RecordOperation)

	zoneNameIDMapper := zoneIDName{}
	for _, z := range zones {
		zoneNameIDMapper.Add(*z.Id, *z.Name)
		changes[*z.Id] = []dns.RecordOperation{}
	}

	for _, op := range ops {
		if zoneID, _ := zoneNameIDMapper.FindZone(*op.Domain); zoneID != "" {
			changes[zoneID] = append(changes[zoneID], op)
		} else {
			log.Warnf("No matching zone for record operation %s", op)
		}
	}

	// Remove zones that don't have any changes.
	for zone, ops := range changes {
		if len(ops) == 0 {
			delete(changes, zone)
		}
	}

	return changes
}

// operationsByZone segments a slice of RecordOperations by their zone.

operationsByZone segments a slice of RecordOperations by their zone .


func NewAzureProvider(configFile string, domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, resourceGroup 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)
	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
	}

	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)
	if err != nil {
		return nil, fmt.Errorf("failed to get token: %v", err)
	}

	zonesClient := dns.NewZonesClientWithBaseURI(environment.ResourceManagerEndpoint, cfg.SubscriptionID)
	zonesClient.Authorizer = autorest.NewBearerAuthorizer(token)
	recordsClient := dns.NewRecordSetsClientWithBaseURI(environment.ResourceManagerEndpoint, cfg.SubscriptionID)
	recordsClient.Authorizer = autorest.NewBearerAuthorizer(token)

	provider := &AzureProvider{
		domainFilter:  domainFilter,
		zoneIDFilter:  zoneIDFilter,
		dryRun:        dryRun,
		resourceGroup: cfg.ResourceGroup,
		zonesClient:   zonesClient,
		recordsClient: recordsClient,
	}
	return provider, nil
}

// NewAzureProvider creates a new Azure provider.
//
// Returns the provider or an error if a provider could not be created.

NewAzureProvider creates a new Azure provider . Returns the provider or an error if a provider could not be created .


func getAccessToken(cfg config, environment azure.Environment) (*adal.ServicePrincipalToken, error) {
	// Try to retrive 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)
		}

		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
	}

	// Try to retrieve token with service principal credentials.
	if len(cfg.ClientID) > 0 && len(cfg.ClientSecret) > 0 {
		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
	}

	return nil, fmt.Errorf("no credentials provided for Azure API")
}

// getAccessToken retrieves Azure API access token.

getAccessToken retrieves Azure API access token .


func (c *defaultDiskCreator) Create(size int, publicSSHKeyPath, diskPath string) error {
	log.Debugf("Creating %d MB hard disk image...", size)

	tarBuf, err := mcnutils.MakeDiskImage(publicSSHKeyPath)
	if err != nil {
		return err
	}

	log.Debug("Calling inner createDiskImage")

	return createDiskImage(diskPath, size, tarBuf)
}

func ( c * defaultDiskCreator ) Create ( size int , publicSSHKeyPath , diskPath string ) error { log . Debugf ( " " , size ) tarBuf , err := mcnutils . MakeDiskImage ( publicSSHKeyPath ) if err != nil { return err } log . Debug ( " " ) return createDiskImage ( diskPath , size , tarBuf ) }


func createDiskImage(dest string, size int, r io.Reader) error {
	// Convert a raw image from stdin to the dest VMDK image.
	sizeBytes := int64(size) << 20 // usually won't fit in 32-bit int (max 2GB)
	// FIXME: why isn't this just using the vbm*() functions?
	cmd := exec.Command(vboxManageCmd, "convertfromraw", "stdin", dest,
		fmt.Sprintf("%d", sizeBytes), "--format", "VMDK")

	log.Debug(cmd)

	if os.Getenv("MACHINE_DEBUG") != "" {
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
	}

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}

	log.Debug("Starting command")

	if err := cmd.Start(); err != nil {
		return err
	}

	log.Debug("Copying to stdin")

	n, err := io.Copy(stdin, r)
	if err != nil {
		return err
	}

	log.Debug("Filling zeroes")

	// The total number of bytes written to stdin must match sizeBytes, or
	// VBoxManage.exe on Windows will fail. Fill remaining with zeros.
	if left := sizeBytes - n; left > 0 {
		if err := zeroFill(stdin, left); err != nil {
			return err
		}
	}

	log.Debug("Closing STDIN")

	// cmd won't exit until the stdin is closed.
	if err := stdin.Close(); err != nil {
		return err
	}

	log.Debug("Waiting on cmd")

	return cmd.Wait()
}

// createDiskImage makes a disk image at dest with the given size in MB. If r is
// not nil, it will be read as a raw disk image to convert from.

createDiskImage makes a disk image at dest with the given size in MB . If r is not nil it will be read as a raw disk image to convert from .


func (n *hostOnlyNetwork) Save(vbox VBoxManager) error {
	if err := n.SaveIPv4(vbox); err != nil {
		return err
	}

	if n.DHCP {
		vbox.vbm("hostonlyif", "ipconfig", n.Name, "--dhcp") // not implemented as of VirtualBox 4.3
	}

	return nil
}

func
(
n
*
hostOnlyNetwork
)
Save
(
vbox
VBoxManager
)
error
{
if
err
:=
n
.
SaveIPv4
(
vbox
)
;
err
!=
{
return
err
}
if
n
.
DHCP
{
vbox
.
vbm
(
"
"
,
"
"
,
n
.
Name
,
"
"
)
// not implemented as of VirtualBox 4.3
}
return
}

// Save changes the configuration of the host-only network.

Save changes the configuration of the host - only network .


func (n *hostOnlyNetwork) SaveIPv4(vbox VBoxManager) error {
	if n.IPv4.IP != nil && n.IPv4.Mask != nil {
		if runtime.GOOS == "windows" {
			log.Warn("Windows might ask for the permission to configure a network adapter. Sometimes, such confirmation window is minimized in the taskbar.")
		}

		if err := vbox.vbm("hostonlyif", "ipconfig", n.Name, "--ip", n.IPv4.IP.String(), "--netmask", net.IP(n.IPv4.Mask).String()); err != nil {
			return err
		}
	}

	return nil
}

func ( n * hostOnlyNetwork ) SaveIPv4 ( vbox VBoxManager ) error { if n . IPv4 . IP != nil && n . IPv4 . Mask != nil { if runtime . GOOS == " " { log . Warn ( " " ) } if err := vbox . vbm ( " " , " " , n . Name , " " , n . IPv4 . IP . String ( ) , " " , net . IP ( n . IPv4 . Mask ) . String ( ) ) ; err != nil { return err } } return nil }

// SaveIPv4 changes the ipv4 configuration of the host-only network.

SaveIPv4 changes the ipv4 configuration of the host - only network .


func createHostonlyAdapter(vbox VBoxManager) (*hostOnlyNetwork, error) {
	if runtime.GOOS == "windows" {
		log.Warn("Windows might ask for the permission to create a network adapter. Sometimes, such confirmation window is minimized in the taskbar.")
	}

	out, err := vbox.vbmOut("hostonlyif", "create")
	if err != nil {
		return nil, err
	}

	res := reHostOnlyAdapterCreated.FindStringSubmatch(string(out))
	if res == nil {
		return nil, errors.New("Failed to create host-only adapter")
	}

	return &hostOnlyNetwork{Name: res[1]}, nil
}

func createHostonlyAdapter ( vbox VBoxManager ) ( * hostOnlyNetwork , error ) { if runtime . GOOS == " " { log . Warn ( " " ) } out , err := vbox . vbmOut ( " " , " " ) if err != nil { return nil , err } res := reHostOnlyAdapterCreated . FindStringSubmatch ( string ( out ) ) if res == nil { return nil , errors . New ( " " ) } return & hostOnlyNetwork { Name : res [ 1 ] } , nil }

// createHostonlyAdapter creates a new host-only network.

createHostonlyAdapter creates a new host - only network .