mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2026-05-05 14:46:10 +02:00
Merge pull request #2696 from KohlsTechnology/issue-2187
extract bluecat api code into its own package
This commit is contained in:
commit
1dcc89eea4
2
main.go
2
main.go
@ -211,7 +211,7 @@ func main() {
|
||||
case "azure-private-dns":
|
||||
p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
|
||||
case "bluecat":
|
||||
p, err = bluecat.NewBluecatProvider(cfg.BluecatConfigFile, cfg.BluecatDNSConfiguration, cfg.BluecatDNSServerName, cfg.BluecatDNSDeployType, cfg.BluecatDNSView, cfg.BluecatGatewayHost, cfg.BluecatRootZone, domainFilter, zoneIDFilter, cfg.DryRun, cfg.BluecatSkipTLSVerify)
|
||||
p, err = bluecat.NewBluecatProvider(cfg.BluecatConfigFile, cfg.BluecatDNSConfiguration, cfg.BluecatDNSServerName, cfg.BluecatDNSDeployType, cfg.BluecatDNSView, cfg.BluecatGatewayHost, cfg.BluecatRootZone, cfg.TXTPrefix, cfg.TXTSuffix, domainFilter, zoneIDFilter, cfg.DryRun, cfg.BluecatSkipTLSVerify)
|
||||
case "vinyldns":
|
||||
p, err = vinyldns.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
||||
case "vultr":
|
||||
|
||||
@ -15,17 +15,16 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO: Ensure we have proper error handling/logging for API calls to Bluecat. getBluecatGatewayToken has a good example of this
|
||||
// TODO: Remove naked returns
|
||||
// TODO: Remove studdering
|
||||
// TODO: Make API calls more consistent (eg error handling on HTTP response codes)
|
||||
// TODO: zone-id-filter does not seem to work with our provider
|
||||
|
||||
package bluecat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@ -37,20 +36,9 @@ import (
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
api "sigs.k8s.io/external-dns/provider/bluecat/gateway"
|
||||
)
|
||||
|
||||
type bluecatConfig struct {
|
||||
GatewayHost string `json:"gatewayHost"`
|
||||
GatewayUsername string `json:"gatewayUsername,omitempty"`
|
||||
GatewayPassword string `json:"gatewayPassword,omitempty"`
|
||||
DNSConfiguration string `json:"dnsConfiguration"`
|
||||
DNSServerName string `json:"dnsServerName"`
|
||||
DNSDeployType string `json:"dnsDeployType"`
|
||||
View string `json:"dnsView"`
|
||||
RootZone string `json:"rootZone"`
|
||||
SkipTLSVerify bool `json:"skipTLSVerify"`
|
||||
}
|
||||
|
||||
// BluecatProvider implements the DNS provider for Bluecat DNS
|
||||
type BluecatProvider struct {
|
||||
provider.BaseProvider
|
||||
@ -62,68 +50,9 @@ type BluecatProvider struct {
|
||||
DNSServerName string
|
||||
DNSDeployType string
|
||||
View string
|
||||
gatewayClient GatewayClient
|
||||
}
|
||||
|
||||
type GatewayClient interface {
|
||||
getBluecatZones(zoneName string) ([]BluecatZone, error)
|
||||
getHostRecords(zone string, records *[]BluecatHostRecord) error
|
||||
getCNAMERecords(zone string, records *[]BluecatCNAMERecord) error
|
||||
getHostRecord(name string, record *BluecatHostRecord) error
|
||||
getCNAMERecord(name string, record *BluecatCNAMERecord) error
|
||||
createHostRecord(zone string, req *bluecatCreateHostRecordRequest) (res interface{}, err error)
|
||||
createCNAMERecord(zone string, req *bluecatCreateCNAMERecordRequest) (res interface{}, err error)
|
||||
deleteHostRecord(name string, zone string) (err error)
|
||||
deleteCNAMERecord(name string, zone string) (err error)
|
||||
buildHTTPRequest(method, url string, body io.Reader) (*http.Request, error)
|
||||
getTXTRecords(zone string, records *[]BluecatTXTRecord) error
|
||||
getTXTRecord(name string, record *BluecatTXTRecord) error
|
||||
createTXTRecord(zone string, req *bluecatCreateTXTRecordRequest) (res interface{}, err error)
|
||||
deleteTXTRecord(name string, zone string) error
|
||||
serverFullDeploy() error
|
||||
}
|
||||
|
||||
// GatewayClientConfig defines new client on bluecat gateway
|
||||
type GatewayClientConfig struct {
|
||||
Cookie http.Cookie
|
||||
Token string
|
||||
Host string
|
||||
DNSConfiguration string
|
||||
View string
|
||||
RootZone string
|
||||
DNSServerName string
|
||||
SkipTLSVerify bool
|
||||
}
|
||||
|
||||
// BluecatZone defines a zone to hold records
|
||||
type BluecatZone struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Properties string `json:"properties"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// BluecatHostRecord defines dns Host record
|
||||
type BluecatHostRecord struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Properties string `json:"properties"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// BluecatCNAMERecord defines dns CNAME record
|
||||
type BluecatCNAMERecord struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Properties string `json:"properties"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// BluecatTXTRecord defines dns TXT record
|
||||
type BluecatTXTRecord struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Properties string `json:"properties"`
|
||||
gatewayClient api.GatewayClient
|
||||
TxtPrefix string
|
||||
TxtSuffix string
|
||||
}
|
||||
|
||||
type bluecatRecordSet struct {
|
||||
@ -131,38 +60,15 @@ type bluecatRecordSet struct {
|
||||
res interface{}
|
||||
}
|
||||
|
||||
type bluecatCreateHostRecordRequest struct {
|
||||
AbsoluteName string `json:"absolute_name"`
|
||||
IP4Address string `json:"ip4_address"`
|
||||
TTL int `json:"ttl"`
|
||||
Properties string `json:"properties"`
|
||||
}
|
||||
|
||||
type bluecatCreateCNAMERecordRequest struct {
|
||||
AbsoluteName string `json:"absolute_name"`
|
||||
LinkedRecord string `json:"linked_record"`
|
||||
TTL int `json:"ttl"`
|
||||
Properties string `json:"properties"`
|
||||
}
|
||||
|
||||
type bluecatCreateTXTRecordRequest struct {
|
||||
AbsoluteName string `json:"absolute_name"`
|
||||
Text string `json:"txt"`
|
||||
}
|
||||
|
||||
type bluecatServerFullDeployRequest struct {
|
||||
ServerName string `json:"server_name"`
|
||||
}
|
||||
|
||||
// NewBluecatProvider creates a new Bluecat provider.
|
||||
//
|
||||
// Returns a pointer to the provider or an error if a provider could not be created.
|
||||
func NewBluecatProvider(configFile, dnsConfiguration, dnsServerName, dnsDeployType, dnsView, gatewayHost, rootZone string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun, skipTLSVerify bool) (*BluecatProvider, error) {
|
||||
cfg := bluecatConfig{}
|
||||
func NewBluecatProvider(configFile, dnsConfiguration, dnsServerName, dnsDeployType, dnsView, gatewayHost, rootZone, txtPrefix, txtSuffix string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun, skipTLSVerify bool) (*BluecatProvider, error) {
|
||||
cfg := api.BluecatConfig{}
|
||||
contents, err := os.ReadFile(configFile)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
cfg = bluecatConfig{
|
||||
cfg = api.BluecatConfig{
|
||||
GatewayHost: gatewayHost,
|
||||
DNSConfiguration: dnsConfiguration,
|
||||
DNSServerName: dnsServerName,
|
||||
@ -183,15 +89,15 @@ func NewBluecatProvider(configFile, dnsConfiguration, dnsServerName, dnsDeployTy
|
||||
}
|
||||
}
|
||||
|
||||
if !isValidDNSDeployType(cfg.DNSDeployType) {
|
||||
if !api.IsValidDNSDeployType(cfg.DNSDeployType) {
|
||||
return nil, errors.Errorf("%v is not a valid deployment type", cfg.DNSDeployType)
|
||||
}
|
||||
|
||||
token, cookie, err := getBluecatGatewayToken(cfg)
|
||||
token, cookie, err := api.GetBluecatGatewayToken(cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get API token from Bluecat Gateway")
|
||||
}
|
||||
gatewayClient := NewGatewayClient(cookie, token, cfg.GatewayHost, cfg.DNSConfiguration, cfg.View, cfg.RootZone, cfg.DNSServerName, cfg.SkipTLSVerify)
|
||||
gatewayClient := api.NewGatewayClientConfig(cookie, token, cfg.GatewayHost, cfg.DNSConfiguration, cfg.View, cfg.RootZone, cfg.DNSServerName, cfg.SkipTLSVerify)
|
||||
|
||||
provider := &BluecatProvider{
|
||||
domainFilter: domainFilter,
|
||||
@ -203,31 +109,12 @@ func NewBluecatProvider(configFile, dnsConfiguration, dnsServerName, dnsDeployTy
|
||||
DNSDeployType: cfg.DNSDeployType,
|
||||
View: cfg.View,
|
||||
RootZone: cfg.RootZone,
|
||||
TxtPrefix: txtPrefix,
|
||||
TxtSuffix: txtSuffix,
|
||||
}
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// NewGatewayClient creates and returns a new Bluecat gateway client
|
||||
func NewGatewayClient(cookie http.Cookie, token, gatewayHost, dnsConfiguration, view, rootZone, dnsServerName string, skipTLSVerify bool) GatewayClientConfig {
|
||||
// TODO: do not handle defaulting here
|
||||
//
|
||||
// Right now the Bluecat gateway doesn't seem to have a way to get the root zone from the API. If the user
|
||||
// doesn't provide one via the config file we'll assume it's 'com'
|
||||
if rootZone == "" {
|
||||
rootZone = "com"
|
||||
}
|
||||
return GatewayClientConfig{
|
||||
Cookie: cookie,
|
||||
Token: token,
|
||||
Host: gatewayHost,
|
||||
DNSConfiguration: dnsConfiguration,
|
||||
DNSServerName: dnsServerName,
|
||||
View: view,
|
||||
RootZone: rootZone,
|
||||
SkipTLSVerify: skipTLSVerify,
|
||||
}
|
||||
}
|
||||
|
||||
// Records fetches Host, CNAME, and TXT records from bluecat gateway
|
||||
func (p *BluecatProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, err error) {
|
||||
zones, err := p.zones()
|
||||
@ -239,8 +126,8 @@ func (p *BluecatProvider) Records(ctx context.Context) (endpoints []*endpoint.En
|
||||
for _, zone := range zones {
|
||||
log.Debugf("fetching records from zone '%s'", zone)
|
||||
|
||||
var resT []BluecatTXTRecord
|
||||
err = p.gatewayClient.getTXTRecords(zone, &resT)
|
||||
var resT []api.BluecatTXTRecord
|
||||
err = p.gatewayClient.GetTXTRecords(zone, &resT)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not fetch TXT records for zone: %v", zone)
|
||||
}
|
||||
@ -253,14 +140,14 @@ func (p *BluecatProvider) Records(ctx context.Context) (endpoints []*endpoint.En
|
||||
endpoints = append(endpoints, tempEndpoint)
|
||||
}
|
||||
|
||||
var resH []BluecatHostRecord
|
||||
err = p.gatewayClient.getHostRecords(zone, &resH)
|
||||
var resH []api.BluecatHostRecord
|
||||
err = p.gatewayClient.GetHostRecords(zone, &resH)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not fetch host records for zone: %v", zone)
|
||||
}
|
||||
var ep *endpoint.Endpoint
|
||||
for _, rec := range resH {
|
||||
propMap := splitProperties(rec.Properties)
|
||||
propMap := api.SplitProperties(rec.Properties)
|
||||
ips := strings.Split(propMap["addresses"], ",")
|
||||
for _, ip := range ips {
|
||||
if _, ok := propMap["ttl"]; ok {
|
||||
@ -273,7 +160,7 @@ func (p *BluecatProvider) Records(ctx context.Context) (endpoints []*endpoint.En
|
||||
ep = endpoint.NewEndpoint(propMap["absoluteName"], endpoint.RecordTypeA, ip)
|
||||
}
|
||||
for _, txtRec := range resT {
|
||||
if strings.Compare(rec.Name, txtRec.Name) == 0 {
|
||||
if strings.Compare(p.TxtPrefix+rec.Name+p.TxtSuffix, txtRec.Name) == 0 {
|
||||
ep.Labels[endpoint.OwnerLabelKey], err = extractOwnerfromTXTRecord(txtRec.Properties)
|
||||
if err != nil {
|
||||
log.Debugf("External DNS Owner %s", err)
|
||||
@ -284,14 +171,14 @@ func (p *BluecatProvider) Records(ctx context.Context) (endpoints []*endpoint.En
|
||||
}
|
||||
}
|
||||
|
||||
var resC []BluecatCNAMERecord
|
||||
err = p.gatewayClient.getCNAMERecords(zone, &resC)
|
||||
var resC []api.BluecatCNAMERecord
|
||||
err = p.gatewayClient.GetCNAMERecords(zone, &resC)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not fetch CNAME records for zone: %v", zone)
|
||||
}
|
||||
|
||||
for _, rec := range resC {
|
||||
propMap := splitProperties(rec.Properties)
|
||||
propMap := api.SplitProperties(rec.Properties)
|
||||
if _, ok := propMap["ttl"]; ok {
|
||||
ttl, err := strconv.Atoi(propMap["ttl"])
|
||||
if err != nil {
|
||||
@ -302,7 +189,7 @@ func (p *BluecatProvider) Records(ctx context.Context) (endpoints []*endpoint.En
|
||||
ep = endpoint.NewEndpoint(propMap["absoluteName"], endpoint.RecordTypeCNAME, propMap["linkedRecordName"])
|
||||
}
|
||||
for _, txtRec := range resT {
|
||||
if strings.Compare(rec.Name, txtRec.Name) == 0 {
|
||||
if strings.Compare(p.TxtPrefix+rec.Name+p.TxtSuffix, txtRec.Name) == 0 {
|
||||
ep.Labels[endpoint.OwnerLabelKey], err = extractOwnerfromTXTRecord(txtRec.Properties)
|
||||
if err != nil {
|
||||
log.Debugf("External DNS Owner %s", err)
|
||||
@ -334,14 +221,18 @@ func (p *BluecatProvider) ApplyChanges(ctx context.Context, changes *plan.Change
|
||||
p.createRecords(created)
|
||||
|
||||
if p.DNSServerName != "" {
|
||||
switch p.DNSDeployType {
|
||||
case "full-deploy":
|
||||
err := p.gatewayClient.serverFullDeploy()
|
||||
if err != nil {
|
||||
return err
|
||||
if p.dryRun {
|
||||
log.Debug("Not executing deploy because this is running in dry-run mode")
|
||||
} else {
|
||||
switch p.DNSDeployType {
|
||||
case "full-deploy":
|
||||
err := p.gatewayClient.ServerFullDeploy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "no-deploy":
|
||||
log.Debug("Not executing deploy because DNSDeployType is set to 'no-deploy'")
|
||||
}
|
||||
case "no-deploy":
|
||||
log.Debug("Not executing deploy because DNSDeployType is set to 'no-deploy'")
|
||||
}
|
||||
} else {
|
||||
log.Debug("Not executing deploy because server name was not provided")
|
||||
@ -404,7 +295,7 @@ func (p *BluecatProvider) zones() ([]string, error) {
|
||||
log.Debugf("retrieving Bluecat zones for configuration: %s, view: %s", p.DNSConfiguration, p.View)
|
||||
var zones []string
|
||||
|
||||
zonelist, err := p.gatewayClient.getBluecatZones(p.RootZone)
|
||||
zonelist, err := p.gatewayClient.GetBluecatZones(p.RootZone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -419,7 +310,7 @@ func (p *BluecatProvider) zones() ([]string, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
zoneProps := splitProperties(zone.Properties)
|
||||
zoneProps := api.SplitProperties(zone.Properties)
|
||||
|
||||
zones = append(zones, zoneProps["absoluteName"])
|
||||
}
|
||||
@ -462,11 +353,11 @@ func (p *BluecatProvider) createRecords(created bluecatChangeMap) {
|
||||
var response interface{}
|
||||
switch ep.RecordType {
|
||||
case endpoint.RecordTypeA:
|
||||
response, err = p.gatewayClient.createHostRecord(zone, recordSet.obj.(*bluecatCreateHostRecordRequest))
|
||||
err = p.gatewayClient.CreateHostRecord(zone, recordSet.obj.(*api.BluecatCreateHostRecordRequest))
|
||||
case endpoint.RecordTypeCNAME:
|
||||
response, err = p.gatewayClient.createCNAMERecord(zone, recordSet.obj.(*bluecatCreateCNAMERecordRequest))
|
||||
err = p.gatewayClient.CreateCNAMERecord(zone, recordSet.obj.(*api.BluecatCreateCNAMERecordRequest))
|
||||
case endpoint.RecordTypeTXT:
|
||||
response, err = p.gatewayClient.createTXTRecord(zone, recordSet.obj.(*bluecatCreateTXTRecordRequest))
|
||||
err = p.gatewayClient.CreateTXTRecord(zone, recordSet.obj.(*api.BluecatCreateTXTRecordRequest))
|
||||
}
|
||||
log.Debugf("Response from create: %v", response)
|
||||
if err != nil {
|
||||
@ -516,16 +407,16 @@ func (p *BluecatProvider) deleteRecords(deleted bluecatChangeMap) {
|
||||
|
||||
switch ep.RecordType {
|
||||
case endpoint.RecordTypeA:
|
||||
for _, record := range *recordSet.res.(*[]BluecatHostRecord) {
|
||||
err = p.gatewayClient.deleteHostRecord(record.Name, zone)
|
||||
for _, record := range *recordSet.res.(*[]api.BluecatHostRecord) {
|
||||
err = p.gatewayClient.DeleteHostRecord(record.Name, zone)
|
||||
}
|
||||
case endpoint.RecordTypeCNAME:
|
||||
for _, record := range *recordSet.res.(*[]BluecatCNAMERecord) {
|
||||
err = p.gatewayClient.deleteCNAMERecord(record.Name, zone)
|
||||
for _, record := range *recordSet.res.(*[]api.BluecatCNAMERecord) {
|
||||
err = p.gatewayClient.DeleteCNAMERecord(record.Name, zone)
|
||||
}
|
||||
case endpoint.RecordTypeTXT:
|
||||
for _, record := range *recordSet.res.(*[]BluecatTXTRecord) {
|
||||
err = p.gatewayClient.deleteTXTRecord(record.Name, zone)
|
||||
for _, record := range *recordSet.res.(*[]api.BluecatTXTRecord) {
|
||||
err = p.gatewayClient.DeleteTXTRecord(record.Name, zone)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
@ -543,16 +434,16 @@ func (p *BluecatProvider) deleteRecords(deleted bluecatChangeMap) {
|
||||
func (p *BluecatProvider) recordSet(ep *endpoint.Endpoint, getObject bool) (recordSet bluecatRecordSet, err error) {
|
||||
switch ep.RecordType {
|
||||
case endpoint.RecordTypeA:
|
||||
var res []BluecatHostRecord
|
||||
obj := bluecatCreateHostRecordRequest{
|
||||
var res []api.BluecatHostRecord
|
||||
obj := api.BluecatCreateHostRecordRequest{
|
||||
AbsoluteName: ep.DNSName,
|
||||
IP4Address: ep.Targets[0],
|
||||
TTL: int(ep.RecordTTL),
|
||||
Properties: "",
|
||||
}
|
||||
if getObject {
|
||||
var record BluecatHostRecord
|
||||
err = p.gatewayClient.getHostRecord(ep.DNSName, &record)
|
||||
var record api.BluecatHostRecord
|
||||
err = p.gatewayClient.GetHostRecord(ep.DNSName, &record)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -563,16 +454,16 @@ func (p *BluecatProvider) recordSet(ep *endpoint.Endpoint, getObject bool) (reco
|
||||
res: &res,
|
||||
}
|
||||
case endpoint.RecordTypeCNAME:
|
||||
var res []BluecatCNAMERecord
|
||||
obj := bluecatCreateCNAMERecordRequest{
|
||||
var res []api.BluecatCNAMERecord
|
||||
obj := api.BluecatCreateCNAMERecordRequest{
|
||||
AbsoluteName: ep.DNSName,
|
||||
LinkedRecord: ep.Targets[0],
|
||||
TTL: int(ep.RecordTTL),
|
||||
Properties: "",
|
||||
}
|
||||
if getObject {
|
||||
var record BluecatCNAMERecord
|
||||
err = p.gatewayClient.getCNAMERecord(ep.DNSName, &record)
|
||||
var record api.BluecatCNAMERecord
|
||||
err = p.gatewayClient.GetCNAMERecord(ep.DNSName, &record)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -583,16 +474,16 @@ func (p *BluecatProvider) recordSet(ep *endpoint.Endpoint, getObject bool) (reco
|
||||
res: &res,
|
||||
}
|
||||
case endpoint.RecordTypeTXT:
|
||||
var res []BluecatTXTRecord
|
||||
var res []api.BluecatTXTRecord
|
||||
// TODO: Allow setting TTL
|
||||
// This is not implemented in the Bluecat Gateway
|
||||
obj := bluecatCreateTXTRecordRequest{
|
||||
obj := api.BluecatCreateTXTRecordRequest{
|
||||
AbsoluteName: ep.DNSName,
|
||||
Text: ep.Targets[0],
|
||||
}
|
||||
if getObject {
|
||||
var record BluecatTXTRecord
|
||||
err = p.gatewayClient.getTXTRecord(ep.DNSName, &record)
|
||||
var record api.BluecatTXTRecord
|
||||
err = p.gatewayClient.GetTXTRecord(ep.DNSName, &record)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -606,455 +497,7 @@ func (p *BluecatProvider) recordSet(ep *endpoint.Endpoint, getObject bool) (reco
|
||||
return
|
||||
}
|
||||
|
||||
// getBluecatGatewayToken retrieves a Bluecat Gateway API token.
|
||||
func getBluecatGatewayToken(cfg bluecatConfig) (string, http.Cookie, error) {
|
||||
var username string
|
||||
if cfg.GatewayUsername != "" {
|
||||
username = cfg.GatewayUsername
|
||||
}
|
||||
if v, ok := os.LookupEnv("BLUECAT_USERNAME"); ok {
|
||||
username = v
|
||||
}
|
||||
|
||||
var password string
|
||||
if cfg.GatewayPassword != "" {
|
||||
password = cfg.GatewayPassword
|
||||
}
|
||||
if v, ok := os.LookupEnv("BLUECAT_PASSWORD"); ok {
|
||||
password = v
|
||||
}
|
||||
|
||||
body, err := json.Marshal(map[string]string{
|
||||
"username": username,
|
||||
"password": password,
|
||||
})
|
||||
if err != nil {
|
||||
return "", http.Cookie{}, errors.Wrap(err, "could not unmarshal credentials for bluecat gateway config")
|
||||
}
|
||||
|
||||
c := newHTTPClient(cfg.SkipTLSVerify)
|
||||
|
||||
resp, err := c.Post(cfg.GatewayHost+"/rest_login", "application/json", bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return "", http.Cookie{}, errors.Wrap(err, "error obtaining API token from bluecat gateway")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
details, _ := ioutil.ReadAll(resp.Body)
|
||||
return "", http.Cookie{}, errors.Errorf("got HTTP response code %v, detailed message: %v", resp.StatusCode, string(details))
|
||||
}
|
||||
|
||||
res, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", http.Cookie{}, errors.Wrap(err, "error reading get_token response from bluecat gateway")
|
||||
}
|
||||
|
||||
resJSON := map[string]string{}
|
||||
err = json.Unmarshal(res, &resJSON)
|
||||
if err != nil {
|
||||
return "", http.Cookie{}, errors.Wrap(err, "error unmarshaling json response (auth) from bluecat gateway")
|
||||
}
|
||||
|
||||
// Example response: {"access_token": "BAMAuthToken: abc123"}
|
||||
// We only care about the actual token string - i.e. abc123
|
||||
// The gateway also creates a cookie as part of the response. This seems to be the actual auth mechanism, at least
|
||||
// for now.
|
||||
return strings.Split(resJSON["access_token"], " ")[1], *resp.Cookies()[0], nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) getBluecatZones(zoneName string) ([]BluecatZone, error) {
|
||||
client := newHTTPClient(c.SkipTLSVerify)
|
||||
|
||||
zonePath := expandZone(zoneName)
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath
|
||||
req, err := c.buildHTTPRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error building http request")
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error retrieving zone(s) from gateway: %v, %v", url, zoneName)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
zones := []BluecatZone{}
|
||||
json.NewDecoder(resp.Body).Decode(&zones)
|
||||
|
||||
// Bluecat Gateway only returns subzones one level deeper than the provided zone
|
||||
// so this recursion is needed to traverse subzones until none are returned
|
||||
for _, zone := range zones {
|
||||
zoneProps := splitProperties(zone.Properties)
|
||||
subZones, err := c.getBluecatZones(zoneProps["absoluteName"])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error retrieving subzones from gateway: %v", zoneName)
|
||||
}
|
||||
zones = append(zones, subZones...)
|
||||
}
|
||||
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) getHostRecords(zone string, records *[]BluecatHostRecord) error {
|
||||
client := newHTTPClient(c.SkipTLSVerify)
|
||||
|
||||
zonePath := expandZone(zone)
|
||||
|
||||
// Remove the trailing 'zones/'
|
||||
zonePath = strings.TrimSuffix(zonePath, "zones/")
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath + "host_records/"
|
||||
req, err := c.buildHTTPRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error building http request")
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving record(s) from gateway: %v", zone)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
json.NewDecoder(resp.Body).Decode(records)
|
||||
log.Debugf("Get Host Records Response: %v", records)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) getCNAMERecords(zone string, records *[]BluecatCNAMERecord) error {
|
||||
client := newHTTPClient(c.SkipTLSVerify)
|
||||
|
||||
zonePath := expandZone(zone)
|
||||
|
||||
// Remove the trailing 'zones/'
|
||||
zonePath = strings.TrimSuffix(zonePath, "zones/")
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath + "cname_records/"
|
||||
req, err := c.buildHTTPRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error building http request")
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving record(s) from gateway: %v", zone)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
json.NewDecoder(resp.Body).Decode(records)
|
||||
log.Debugf("Get CName Records Response: %v", records)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) getTXTRecords(zone string, records *[]BluecatTXTRecord) error {
|
||||
client := newHTTPClient(c.SkipTLSVerify)
|
||||
|
||||
zonePath := expandZone(zone)
|
||||
|
||||
// Remove the trailing 'zones/'
|
||||
zonePath = strings.TrimSuffix(zonePath, "zones/")
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath + "text_records/"
|
||||
req, err := c.buildHTTPRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error building http request")
|
||||
}
|
||||
log.Debugf("Request: %v", req)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving record(s) from gateway: %v", zone)
|
||||
}
|
||||
log.Debugf("Get Txt Records response: %v", resp)
|
||||
|
||||
defer resp.Body.Close()
|
||||
json.NewDecoder(resp.Body).Decode(records)
|
||||
log.Debugf("Get TXT Records Body: %v", records)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) getHostRecord(name string, record *BluecatHostRecord) error {
|
||||
client := newHTTPClient(c.SkipTLSVerify)
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
|
||||
"/views/" + c.View + "/" +
|
||||
"host_records/" + name + "/"
|
||||
req, err := c.buildHTTPRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error building http request: %v", name)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving record(s) from gateway: %v", name)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
json.NewDecoder(resp.Body).Decode(record)
|
||||
log.Debugf("Get Host Record Response: %v", record)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) getCNAMERecord(name string, record *BluecatCNAMERecord) error {
|
||||
client := newHTTPClient(c.SkipTLSVerify)
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
|
||||
"/views/" + c.View + "/" +
|
||||
"cname_records/" + name + "/"
|
||||
req, err := c.buildHTTPRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error building http request: %v", name)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving record(s) from gateway: %v", name)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
json.NewDecoder(resp.Body).Decode(record)
|
||||
log.Debugf("Get CName Record Response: %v", record)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) getTXTRecord(name string, record *BluecatTXTRecord) error {
|
||||
client := newHTTPClient(c.SkipTLSVerify)
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
|
||||
"/views/" + c.View + "/" +
|
||||
"text_records/" + name + "/"
|
||||
|
||||
req, err := c.buildHTTPRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error building http request")
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving record(s) from gateway: %v", name)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
json.NewDecoder(resp.Body).Decode(record)
|
||||
log.Debugf("Get TXT Record Response: %v", record)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) createHostRecord(zone string, req *bluecatCreateHostRecordRequest) (res interface{}, err error) {
|
||||
client := newHTTPClient(c.SkipTLSVerify)
|
||||
|
||||
zonePath := expandZone(zone)
|
||||
// Remove the trailing 'zones/'
|
||||
zonePath = strings.TrimSuffix(zonePath, "zones/")
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath + "host_records/"
|
||||
body, _ := json.Marshal(req)
|
||||
hreq, err := c.buildHTTPRequest("POST", url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error building http request")
|
||||
}
|
||||
hreq.Header.Add("Content-Type", "application/json")
|
||||
res, err = client.Do(hreq)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) createCNAMERecord(zone string, req *bluecatCreateCNAMERecordRequest) (res interface{}, err error) {
|
||||
client := newHTTPClient(c.SkipTLSVerify)
|
||||
|
||||
zonePath := expandZone(zone)
|
||||
// Remove the trailing 'zones/'
|
||||
zonePath = strings.TrimSuffix(zonePath, "zones/")
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath + "cname_records/"
|
||||
body, _ := json.Marshal(req)
|
||||
|
||||
hreq, err := c.buildHTTPRequest("POST", url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error building http request")
|
||||
}
|
||||
|
||||
hreq.Header.Add("Content-Type", "application/json")
|
||||
res, err = client.Do(hreq)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) createTXTRecord(zone string, req *bluecatCreateTXTRecordRequest) (interface{}, error) {
|
||||
client := newHTTPClient(c.SkipTLSVerify)
|
||||
|
||||
zonePath := expandZone(zone)
|
||||
// Remove the trailing 'zones/'
|
||||
zonePath = strings.TrimSuffix(zonePath, "zones/")
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath + "text_records/"
|
||||
body, _ := json.Marshal(req)
|
||||
hreq, err := c.buildHTTPRequest("POST", url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hreq.Header.Add("Content-Type", "application/json")
|
||||
res, err := client.Do(hreq)
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) deleteHostRecord(name string, zone string) (err error) {
|
||||
client := newHTTPClient(c.SkipTLSVerify)
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
|
||||
"/views/" + c.View + "/" +
|
||||
"host_records/" + name + "." + zone + "/"
|
||||
req, err := c.buildHTTPRequest("DELETE", url, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error building http request: %v", name)
|
||||
}
|
||||
|
||||
_, err = client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error deleting record(s) from gateway: %v", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) deleteCNAMERecord(name string, zone string) (err error) {
|
||||
client := newHTTPClient(c.SkipTLSVerify)
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
|
||||
"/views/" + c.View + "/" +
|
||||
"cname_records/" + name + "." + zone + "/"
|
||||
req, err := c.buildHTTPRequest("DELETE", url, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error building http request: %v", name)
|
||||
}
|
||||
|
||||
_, err = client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error deleting record(s) from gateway: %v", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) deleteTXTRecord(name string, zone string) error {
|
||||
client := newHTTPClient(c.SkipTLSVerify)
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
|
||||
"/views/" + c.View + "/" +
|
||||
"text_records/" + name + "." + zone + "/"
|
||||
|
||||
req, err := c.buildHTTPRequest("DELETE", url, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error building http request")
|
||||
}
|
||||
|
||||
_, err = client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error deleting record(s) from gateway: %v", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) serverFullDeploy() error {
|
||||
log.Infof("Executing full deploy on server %s", c.DNSServerName)
|
||||
httpClient := newHTTPClient(c.SkipTLSVerify)
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/server/full_deploy/"
|
||||
requestBody := bluecatServerFullDeployRequest{
|
||||
ServerName: c.DNSServerName,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal body for server full deploy")
|
||||
}
|
||||
|
||||
request, err := c.buildHTTPRequest("POST", url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error building http request")
|
||||
}
|
||||
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
response, err := httpClient.Do(request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error executing full deploy")
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusCreated {
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read full deploy response body")
|
||||
}
|
||||
return errors.Errorf("got HTTP response code %v, detailed message: %v", response.StatusCode, string(responseBody))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//buildHTTPRequest builds a standard http Request and adds authentication headers required by Bluecat Gateway
|
||||
func (c GatewayClientConfig) buildHTTPRequest(method, url string, body io.Reader) (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
req.Header.Add("Accept", "application/json")
|
||||
req.Header.Add("Authorization", "Basic "+c.Token)
|
||||
req.AddCookie(&c.Cookie)
|
||||
return req, err
|
||||
}
|
||||
|
||||
//splitProperties is a helper function to break a '|' separated string into key/value pairs
|
||||
// i.e. "foo=bar|baz=mop"
|
||||
func splitProperties(props string) map[string]string {
|
||||
propMap := make(map[string]string)
|
||||
// remove trailing | character before we split
|
||||
props = strings.TrimSuffix(props, "|")
|
||||
|
||||
splits := strings.Split(props, "|")
|
||||
for _, pair := range splits {
|
||||
items := strings.Split(pair, "=")
|
||||
propMap[items[0]] = items[1]
|
||||
}
|
||||
|
||||
return propMap
|
||||
}
|
||||
|
||||
// isValidDNSDeployType validates the deployment type provided by a users configuration is supported by the Bluecat Provider.
|
||||
func isValidDNSDeployType(deployType string) bool {
|
||||
validDNSDeployTypes := []string{"no-deploy", "full-deploy"}
|
||||
for _, t := range validDNSDeployTypes {
|
||||
if t == deployType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//expandZone takes an absolute domain name such as 'example.com' and returns a zone hierarchy used by Bluecat Gateway,
|
||||
//such as '/zones/com/zones/example/zones/'
|
||||
func expandZone(zone string) string {
|
||||
ze := "zones/"
|
||||
parts := strings.Split(zone, ".")
|
||||
if len(parts) > 1 {
|
||||
last := len(parts) - 1
|
||||
for i := range parts {
|
||||
ze = ze + parts[last-i] + "/zones/"
|
||||
}
|
||||
} else {
|
||||
ze = ze + zone + "/zones/"
|
||||
}
|
||||
return ze
|
||||
}
|
||||
|
||||
//extractOwnerFromTXTRecord takes a single text property string and returns the owner after parsing theowner string.
|
||||
// extractOwnerFromTXTRecord takes a single text property string and returns the owner after parsing the owner string.
|
||||
func extractOwnerfromTXTRecord(propString string) (string, error) {
|
||||
if len(propString) == 0 {
|
||||
return "", errors.Errorf("External-DNS Owner not found")
|
||||
@ -1066,15 +509,3 @@ func extractOwnerfromTXTRecord(propString string) (string, error) {
|
||||
}
|
||||
return strings.Split(match[0], "=")[1], nil
|
||||
}
|
||||
|
||||
// newHTTPClient returns an instance of http client
|
||||
func newHTTPClient(skipTLSVerify bool) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: skipTLSVerify,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,8 +16,6 @@ package bluecat
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -26,13 +24,14 @@ import (
|
||||
"sigs.k8s.io/external-dns/internal/testutils"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
api "sigs.k8s.io/external-dns/provider/bluecat/gateway"
|
||||
)
|
||||
|
||||
type mockGatewayClient struct {
|
||||
mockBluecatZones *[]BluecatZone
|
||||
mockBluecatHosts *[]BluecatHostRecord
|
||||
mockBluecatCNAMEs *[]BluecatCNAMERecord
|
||||
mockBluecatTXTs *[]BluecatTXTRecord
|
||||
mockBluecatZones *[]api.BluecatZone
|
||||
mockBluecatHosts *[]api.BluecatHostRecord
|
||||
mockBluecatCNAMEs *[]api.BluecatCNAMERecord
|
||||
mockBluecatTXTs *[]api.BluecatTXTRecord
|
||||
}
|
||||
|
||||
type Changes struct {
|
||||
@ -46,18 +45,18 @@ type Changes struct {
|
||||
Delete []*endpoint.Endpoint
|
||||
}
|
||||
|
||||
func (g mockGatewayClient) getBluecatZones(zoneName string) ([]BluecatZone, error) {
|
||||
func (g mockGatewayClient) GetBluecatZones(zoneName string) ([]api.BluecatZone, error) {
|
||||
return *g.mockBluecatZones, nil
|
||||
}
|
||||
func (g mockGatewayClient) getHostRecords(zone string, records *[]BluecatHostRecord) error {
|
||||
func (g mockGatewayClient) GetHostRecords(zone string, records *[]api.BluecatHostRecord) error {
|
||||
*records = *g.mockBluecatHosts
|
||||
return nil
|
||||
}
|
||||
func (g mockGatewayClient) getCNAMERecords(zone string, records *[]BluecatCNAMERecord) error {
|
||||
func (g mockGatewayClient) GetCNAMERecords(zone string, records *[]api.BluecatCNAMERecord) error {
|
||||
*records = *g.mockBluecatCNAMEs
|
||||
return nil
|
||||
}
|
||||
func (g mockGatewayClient) getHostRecord(name string, record *BluecatHostRecord) error {
|
||||
func (g mockGatewayClient) GetHostRecord(name string, record *api.BluecatHostRecord) error {
|
||||
for _, currentRecord := range *g.mockBluecatHosts {
|
||||
if currentRecord.Name == strings.Split(name, ".")[0] {
|
||||
*record = currentRecord
|
||||
@ -66,7 +65,7 @@ func (g mockGatewayClient) getHostRecord(name string, record *BluecatHostRecord)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (g mockGatewayClient) getCNAMERecord(name string, record *BluecatCNAMERecord) error {
|
||||
func (g mockGatewayClient) GetCNAMERecord(name string, record *api.BluecatCNAMERecord) error {
|
||||
for _, currentRecord := range *g.mockBluecatCNAMEs {
|
||||
if currentRecord.Name == strings.Split(name, ".")[0] {
|
||||
*record = currentRecord
|
||||
@ -75,25 +74,25 @@ func (g mockGatewayClient) getCNAMERecord(name string, record *BluecatCNAMERecor
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (g mockGatewayClient) createHostRecord(zone string, req *bluecatCreateHostRecordRequest) (res interface{}, err error) {
|
||||
return nil, nil
|
||||
func (g mockGatewayClient) CreateHostRecord(zone string, req *api.BluecatCreateHostRecordRequest) (err error) {
|
||||
return nil
|
||||
}
|
||||
func (g mockGatewayClient) createCNAMERecord(zone string, req *bluecatCreateCNAMERecordRequest) (res interface{}, err error) {
|
||||
return nil, nil
|
||||
func (g mockGatewayClient) CreateCNAMERecord(zone string, req *api.BluecatCreateCNAMERecordRequest) (err error) {
|
||||
return nil
|
||||
}
|
||||
func (g mockGatewayClient) deleteHostRecord(name string, zone string) (err error) {
|
||||
func (g mockGatewayClient) DeleteHostRecord(name string, zone string) (err error) {
|
||||
*g.mockBluecatHosts = nil
|
||||
return nil
|
||||
}
|
||||
func (g mockGatewayClient) deleteCNAMERecord(name string, zone string) (err error) {
|
||||
func (g mockGatewayClient) DeleteCNAMERecord(name string, zone string) (err error) {
|
||||
*g.mockBluecatCNAMEs = nil
|
||||
return nil
|
||||
}
|
||||
func (g mockGatewayClient) getTXTRecords(zone string, records *[]BluecatTXTRecord) error {
|
||||
func (g mockGatewayClient) GetTXTRecords(zone string, records *[]api.BluecatTXTRecord) error {
|
||||
*records = *g.mockBluecatTXTs
|
||||
return nil
|
||||
}
|
||||
func (g mockGatewayClient) getTXTRecord(name string, record *BluecatTXTRecord) error {
|
||||
func (g mockGatewayClient) GetTXTRecord(name string, record *api.BluecatTXTRecord) error {
|
||||
for _, currentRecord := range *g.mockBluecatTXTs {
|
||||
if currentRecord.Name == name {
|
||||
*record = currentRecord
|
||||
@ -102,58 +101,53 @@ func (g mockGatewayClient) getTXTRecord(name string, record *BluecatTXTRecord) e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (g mockGatewayClient) createTXTRecord(zone string, req *bluecatCreateTXTRecordRequest) (res interface{}, err error) {
|
||||
return nil, nil
|
||||
func (g mockGatewayClient) CreateTXTRecord(zone string, req *api.BluecatCreateTXTRecordRequest) error {
|
||||
return nil
|
||||
}
|
||||
func (g mockGatewayClient) deleteTXTRecord(name string, zone string) error {
|
||||
func (g mockGatewayClient) DeleteTXTRecord(name string, zone string) error {
|
||||
*g.mockBluecatTXTs = nil
|
||||
return nil
|
||||
}
|
||||
func (g mockGatewayClient) serverFullDeploy() error {
|
||||
func (g mockGatewayClient) ServerFullDeploy() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g mockGatewayClient) buildHTTPRequest(method, url string, body io.Reader) (*http.Request, error) {
|
||||
request, _ := http.NewRequest("GET", fmt.Sprintf("%s/users", "http://some.com/api/v1"), nil)
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func createMockBluecatZone(fqdn string) BluecatZone {
|
||||
func createMockBluecatZone(fqdn string) api.BluecatZone {
|
||||
props := "absoluteName=" + fqdn
|
||||
return BluecatZone{
|
||||
return api.BluecatZone{
|
||||
Properties: props,
|
||||
Name: fqdn,
|
||||
ID: 3,
|
||||
}
|
||||
}
|
||||
|
||||
func createMockBluecatHostRecord(fqdn, target string, ttl int) BluecatHostRecord {
|
||||
func createMockBluecatHostRecord(fqdn, target string, ttl int) api.BluecatHostRecord {
|
||||
props := "absoluteName=" + fqdn + "|addresses=" + target + "|ttl=" + fmt.Sprint(ttl) + "|"
|
||||
nameParts := strings.Split(fqdn, ".")
|
||||
return BluecatHostRecord{
|
||||
return api.BluecatHostRecord{
|
||||
Name: nameParts[0],
|
||||
Properties: props,
|
||||
ID: 3,
|
||||
}
|
||||
}
|
||||
|
||||
func createMockBluecatCNAME(alias, target string, ttl int) BluecatCNAMERecord {
|
||||
func createMockBluecatCNAME(alias, target string, ttl int) api.BluecatCNAMERecord {
|
||||
props := "absoluteName=" + alias + "|linkedRecordName=" + target + "|ttl=" + fmt.Sprint(ttl) + "|"
|
||||
nameParts := strings.Split(alias, ".")
|
||||
return BluecatCNAMERecord{
|
||||
return api.BluecatCNAMERecord{
|
||||
Name: nameParts[0],
|
||||
Properties: props,
|
||||
}
|
||||
}
|
||||
|
||||
func createMockBluecatTXT(fqdn, txt string) BluecatTXTRecord {
|
||||
return BluecatTXTRecord{
|
||||
func createMockBluecatTXT(fqdn, txt string) api.BluecatTXTRecord {
|
||||
return api.BluecatTXTRecord{
|
||||
Name: fqdn,
|
||||
Properties: txt,
|
||||
}
|
||||
}
|
||||
|
||||
func newBluecatProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, client GatewayClient) *BluecatProvider {
|
||||
func newBluecatProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, client mockGatewayClient) *BluecatProvider {
|
||||
return &BluecatProvider{
|
||||
domainFilter: domainFilter,
|
||||
zoneIDFilter: zoneIDFilter,
|
||||
@ -219,20 +213,20 @@ var tests = bluecatTestData{
|
||||
|
||||
func TestBluecatRecords(t *testing.T) {
|
||||
client := mockGatewayClient{
|
||||
mockBluecatZones: &[]BluecatZone{
|
||||
mockBluecatZones: &[]api.BluecatZone{
|
||||
createMockBluecatZone("example.com"),
|
||||
},
|
||||
mockBluecatTXTs: &[]BluecatTXTRecord{
|
||||
mockBluecatTXTs: &[]api.BluecatTXTRecord{
|
||||
createMockBluecatTXT("kdb.example.com", "heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"),
|
||||
createMockBluecatTXT("wack.example.com", "hello"),
|
||||
createMockBluecatTXT("sack.example.com", ""),
|
||||
},
|
||||
mockBluecatHosts: &[]BluecatHostRecord{
|
||||
mockBluecatHosts: &[]api.BluecatHostRecord{
|
||||
createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
|
||||
createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
|
||||
createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
|
||||
},
|
||||
mockBluecatCNAMEs: &[]BluecatCNAMERecord{
|
||||
mockBluecatCNAMEs: &[]api.BluecatCNAMERecord{
|
||||
createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
|
||||
},
|
||||
}
|
||||
@ -252,12 +246,12 @@ func TestBluecatRecords(t *testing.T) {
|
||||
|
||||
func TestBluecatApplyChangesCreate(t *testing.T) {
|
||||
client := mockGatewayClient{
|
||||
mockBluecatZones: &[]BluecatZone{
|
||||
mockBluecatZones: &[]api.BluecatZone{
|
||||
createMockBluecatZone("example.com"),
|
||||
},
|
||||
mockBluecatHosts: &[]BluecatHostRecord{},
|
||||
mockBluecatCNAMEs: &[]BluecatCNAMERecord{},
|
||||
mockBluecatTXTs: &[]BluecatTXTRecord{},
|
||||
mockBluecatHosts: &[]api.BluecatHostRecord{},
|
||||
mockBluecatCNAMEs: &[]api.BluecatCNAMERecord{},
|
||||
mockBluecatTXTs: &[]api.BluecatTXTRecord{},
|
||||
}
|
||||
|
||||
provider := newBluecatProvider(
|
||||
@ -279,18 +273,18 @@ func TestBluecatApplyChangesCreate(t *testing.T) {
|
||||
}
|
||||
func TestBluecatApplyChangesDelete(t *testing.T) {
|
||||
client := mockGatewayClient{
|
||||
mockBluecatZones: &[]BluecatZone{
|
||||
mockBluecatZones: &[]api.BluecatZone{
|
||||
createMockBluecatZone("example.com"),
|
||||
},
|
||||
mockBluecatHosts: &[]BluecatHostRecord{
|
||||
mockBluecatHosts: &[]api.BluecatHostRecord{
|
||||
createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
|
||||
createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
|
||||
createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
|
||||
},
|
||||
mockBluecatCNAMEs: &[]BluecatCNAMERecord{
|
||||
mockBluecatCNAMEs: &[]api.BluecatCNAMERecord{
|
||||
createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
|
||||
},
|
||||
mockBluecatTXTs: &[]BluecatTXTRecord{
|
||||
mockBluecatTXTs: &[]api.BluecatTXTRecord{
|
||||
createMockBluecatTXT("kdb.example.com", "heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"),
|
||||
createMockBluecatTXT("wack.example.com", "hello"),
|
||||
createMockBluecatTXT("sack.example.com", ""),
|
||||
@ -317,18 +311,18 @@ func TestBluecatApplyChangesDelete(t *testing.T) {
|
||||
|
||||
func TestBluecatApplyChangesDeleteWithOwner(t *testing.T) {
|
||||
client := mockGatewayClient{
|
||||
mockBluecatZones: &[]BluecatZone{
|
||||
mockBluecatZones: &[]api.BluecatZone{
|
||||
createMockBluecatZone("example.com"),
|
||||
},
|
||||
mockBluecatHosts: &[]BluecatHostRecord{
|
||||
mockBluecatHosts: &[]api.BluecatHostRecord{
|
||||
createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
|
||||
createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
|
||||
createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
|
||||
},
|
||||
mockBluecatCNAMEs: &[]BluecatCNAMERecord{
|
||||
mockBluecatCNAMEs: &[]api.BluecatCNAMERecord{
|
||||
createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
|
||||
},
|
||||
mockBluecatTXTs: &[]BluecatTXTRecord{
|
||||
mockBluecatTXTs: &[]api.BluecatTXTRecord{
|
||||
createMockBluecatTXT("kdb.example.com", "heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"),
|
||||
createMockBluecatTXT("wack.example.com", "hello"),
|
||||
createMockBluecatTXT("sack.example.com", ""),
|
||||
@ -344,9 +338,10 @@ func TestBluecatApplyChangesDeleteWithOwner(t *testing.T) {
|
||||
if strings.Contains(ep.Targets.String(), "external-dns") {
|
||||
owner, err := extractOwnerfromTXTRecord(ep.Targets.String())
|
||||
if err != nil {
|
||||
continue
|
||||
t.Logf("%v", err)
|
||||
} else {
|
||||
t.Logf("Owner %s", owner)
|
||||
}
|
||||
t.Logf("Owner %s %s", owner, err)
|
||||
}
|
||||
}
|
||||
err := provider.ApplyChanges(context.Background(), &plan.Changes{Delete: ti.Endpoints})
|
||||
@ -362,33 +357,6 @@ func TestBluecatApplyChangesDeleteWithOwner(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestExpandZones(t *testing.T) {
|
||||
mockZones := []string{"example.com", "nginx.example.com", "hack.example.com"}
|
||||
expected := []string{"zones/com/zones/example/zones/", "zones/com/zones/example/zones/nginx/zones/", "zones/com/zones/example/zones/hack/zones/"}
|
||||
for i := range mockZones {
|
||||
if expandZone(mockZones[i]) != expected[i] {
|
||||
t.Fatalf("%s", expected[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBluecatNewGatewayClient(t *testing.T) {
|
||||
testCookie := http.Cookie{Name: "testCookie", Value: "exampleCookie"}
|
||||
testToken := "exampleToken"
|
||||
testgateWayHost := "exampleHost"
|
||||
testDNSConfiguration := "exampleDNSConfiguration"
|
||||
testDNSServer := "exampleServer"
|
||||
testView := "testView"
|
||||
testZone := "example.com"
|
||||
testVerify := true
|
||||
|
||||
client := NewGatewayClient(testCookie, testToken, testgateWayHost, testDNSConfiguration, testView, testZone, testDNSServer, testVerify)
|
||||
|
||||
if client.Cookie.Value != testCookie.Value || client.Cookie.Name != testCookie.Name || client.Token != testToken || client.Host != testgateWayHost || client.DNSConfiguration != testDNSConfiguration || client.View != testView || client.RootZone != testZone || client.SkipTLSVerify != testVerify {
|
||||
t.Fatal("Client values dont match")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ensure findZone method is tested
|
||||
// TODO: ensure zones method is tested
|
||||
// TODO: ensure createRecords method is tested
|
||||
@ -398,18 +366,18 @@ func TestBluecatNewGatewayClient(t *testing.T) {
|
||||
// TODO: Figure out why recordSet.res is not being set properly
|
||||
func TestBluecatRecordset(t *testing.T) {
|
||||
client := mockGatewayClient{
|
||||
mockBluecatZones: &[]BluecatZone{
|
||||
mockBluecatZones: &[]api.BluecatZone{
|
||||
createMockBluecatZone("example.com"),
|
||||
},
|
||||
mockBluecatHosts: &[]BluecatHostRecord{
|
||||
mockBluecatHosts: &[]api.BluecatHostRecord{
|
||||
createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
|
||||
createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
|
||||
createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
|
||||
},
|
||||
mockBluecatCNAMEs: &[]BluecatCNAMERecord{
|
||||
mockBluecatCNAMEs: &[]api.BluecatCNAMERecord{
|
||||
createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
|
||||
},
|
||||
mockBluecatTXTs: &[]BluecatTXTRecord{
|
||||
mockBluecatTXTs: &[]api.BluecatTXTRecord{
|
||||
createMockBluecatTXT("abc.example.com", "hello"),
|
||||
},
|
||||
}
|
||||
@ -420,11 +388,11 @@ func TestBluecatRecordset(t *testing.T) {
|
||||
|
||||
// Test txt records for recordSet function
|
||||
testTxtEndpoint := endpoint.NewEndpoint("abc.example.com", endpoint.RecordTypeTXT, "hello")
|
||||
txtObj := bluecatCreateTXTRecordRequest{
|
||||
txtObj := api.BluecatCreateTXTRecordRequest{
|
||||
AbsoluteName: testTxtEndpoint.DNSName,
|
||||
Text: testTxtEndpoint.Targets[0],
|
||||
}
|
||||
txtRecords := []BluecatTXTRecord{
|
||||
txtRecords := []api.BluecatTXTRecord{
|
||||
createMockBluecatTXT("abc.example.com", "hello"),
|
||||
}
|
||||
expected := bluecatRecordSet{
|
||||
@ -440,11 +408,11 @@ func TestBluecatRecordset(t *testing.T) {
|
||||
|
||||
// Test a records for recordSet function
|
||||
testHostEndpoint := endpoint.NewEndpoint("whitespace.example.com", endpoint.RecordTypeA, "123.123.123.124")
|
||||
hostObj := bluecatCreateHostRecordRequest{
|
||||
hostObj := api.BluecatCreateHostRecordRequest{
|
||||
AbsoluteName: testHostEndpoint.DNSName,
|
||||
IP4Address: testHostEndpoint.Targets[0],
|
||||
}
|
||||
hostRecords := []BluecatHostRecord{
|
||||
hostRecords := []api.BluecatHostRecord{
|
||||
createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
|
||||
}
|
||||
hostExpected := bluecatRecordSet{
|
||||
@ -460,11 +428,11 @@ func TestBluecatRecordset(t *testing.T) {
|
||||
|
||||
// Test CName records for recordSet function
|
||||
testCnameEndpoint := endpoint.NewEndpoint("hack.example.com", endpoint.RecordTypeCNAME, "bluecatnetworks.com")
|
||||
cnameObj := bluecatCreateCNAMERecordRequest{
|
||||
cnameObj := api.BluecatCreateCNAMERecordRequest{
|
||||
AbsoluteName: testCnameEndpoint.DNSName,
|
||||
LinkedRecord: testCnameEndpoint.Targets[0],
|
||||
}
|
||||
cnameRecords := []BluecatCNAMERecord{
|
||||
cnameRecords := []api.BluecatCNAMERecord{
|
||||
createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
|
||||
}
|
||||
cnameExpected := bluecatRecordSet{
|
||||
@ -479,21 +447,6 @@ func TestBluecatRecordset(t *testing.T) {
|
||||
assert.Equal(t, cnameActual.res, cnameExpected.res)
|
||||
}
|
||||
|
||||
func TestValidDeployTypes(t *testing.T) {
|
||||
validTypes := []string{"no-deploy", "full-deploy"}
|
||||
invalidTypes := []string{"anything-else"}
|
||||
for _, i := range validTypes {
|
||||
if !isValidDNSDeployType(i) {
|
||||
t.Fatalf("%s should be a valid deploy type", i)
|
||||
}
|
||||
}
|
||||
for _, i := range invalidTypes {
|
||||
if isValidDNSDeployType(i) {
|
||||
t.Fatalf("%s should be a invalid deploy type", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateEndpoints(t *testing.T, actual, expected []*endpoint.Endpoint) {
|
||||
assert.True(t, testutils.SameEndpoints(actual, expected), "actual and expected endpoints don't match. %s:%s", actual, expected)
|
||||
}
|
||||
|
||||
585
provider/bluecat/gateway/api.go
Normal file
585
provider/bluecat/gateway/api.go
Normal file
@ -0,0 +1,585 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// TODO: add logging
|
||||
// TODO: add timeouts
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// TODO: Ensure DNS Deploy Type Defaults to no-deploy instead of ""
|
||||
type BluecatConfig struct {
|
||||
GatewayHost string `json:"gatewayHost"`
|
||||
GatewayUsername string `json:"gatewayUsername,omitempty"`
|
||||
GatewayPassword string `json:"gatewayPassword,omitempty"`
|
||||
DNSConfiguration string `json:"dnsConfiguration"`
|
||||
DNSServerName string `json:"dnsServerName"`
|
||||
DNSDeployType string `json:"dnsDeployType"`
|
||||
View string `json:"dnsView"`
|
||||
RootZone string `json:"rootZone"`
|
||||
SkipTLSVerify bool `json:"skipTLSVerify"`
|
||||
}
|
||||
|
||||
type GatewayClient interface {
|
||||
GetBluecatZones(zoneName string) ([]BluecatZone, error)
|
||||
GetHostRecords(zone string, records *[]BluecatHostRecord) error
|
||||
GetCNAMERecords(zone string, records *[]BluecatCNAMERecord) error
|
||||
GetHostRecord(name string, record *BluecatHostRecord) error
|
||||
GetCNAMERecord(name string, record *BluecatCNAMERecord) error
|
||||
CreateHostRecord(zone string, req *BluecatCreateHostRecordRequest) error
|
||||
CreateCNAMERecord(zone string, req *BluecatCreateCNAMERecordRequest) error
|
||||
DeleteHostRecord(name string, zone string) (err error)
|
||||
DeleteCNAMERecord(name string, zone string) (err error)
|
||||
GetTXTRecords(zone string, records *[]BluecatTXTRecord) error
|
||||
GetTXTRecord(name string, record *BluecatTXTRecord) error
|
||||
CreateTXTRecord(zone string, req *BluecatCreateTXTRecordRequest) error
|
||||
DeleteTXTRecord(name string, zone string) error
|
||||
ServerFullDeploy() error
|
||||
}
|
||||
|
||||
// GatewayClientConfig defines the configuration for a Bluecat Gateway Client
|
||||
type GatewayClientConfig struct {
|
||||
Cookie http.Cookie
|
||||
Token string
|
||||
Host string
|
||||
DNSConfiguration string
|
||||
View string
|
||||
RootZone string
|
||||
DNSServerName string
|
||||
SkipTLSVerify bool
|
||||
}
|
||||
|
||||
// BluecatZone defines a zone to hold records
|
||||
type BluecatZone struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Properties string `json:"properties"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// BluecatHostRecord defines dns Host record
|
||||
type BluecatHostRecord struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Properties string `json:"properties"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// BluecatCNAMERecord defines dns CNAME record
|
||||
type BluecatCNAMERecord struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Properties string `json:"properties"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// BluecatTXTRecord defines dns TXT record
|
||||
type BluecatTXTRecord struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Properties string `json:"properties"`
|
||||
}
|
||||
|
||||
type BluecatCreateHostRecordRequest struct {
|
||||
AbsoluteName string `json:"absolute_name"`
|
||||
IP4Address string `json:"ip4_address"`
|
||||
TTL int `json:"ttl"`
|
||||
Properties string `json:"properties"`
|
||||
}
|
||||
|
||||
type BluecatCreateCNAMERecordRequest struct {
|
||||
AbsoluteName string `json:"absolute_name"`
|
||||
LinkedRecord string `json:"linked_record"`
|
||||
TTL int `json:"ttl"`
|
||||
Properties string `json:"properties"`
|
||||
}
|
||||
|
||||
type BluecatCreateTXTRecordRequest struct {
|
||||
AbsoluteName string `json:"absolute_name"`
|
||||
Text string `json:"txt"`
|
||||
}
|
||||
|
||||
type BluecatServerFullDeployRequest struct {
|
||||
ServerName string `json:"server_name"`
|
||||
}
|
||||
|
||||
// NewGatewayClient creates and returns a new Bluecat gateway client
|
||||
func NewGatewayClientConfig(cookie http.Cookie, token, gatewayHost, dnsConfiguration, view, rootZone, dnsServerName string, skipTLSVerify bool) GatewayClientConfig {
|
||||
// TODO: do not handle defaulting here
|
||||
//
|
||||
// Right now the Bluecat gateway doesn't seem to have a way to get the root zone from the API. If the user
|
||||
// doesn't provide one via the config file we'll assume it's 'com'
|
||||
if rootZone == "" {
|
||||
rootZone = "com"
|
||||
}
|
||||
return GatewayClientConfig{
|
||||
Cookie: cookie,
|
||||
Token: token,
|
||||
Host: gatewayHost,
|
||||
DNSConfiguration: dnsConfiguration,
|
||||
DNSServerName: dnsServerName,
|
||||
View: view,
|
||||
RootZone: rootZone,
|
||||
SkipTLSVerify: skipTLSVerify,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBluecatGatewayToken retrieves a Bluecat Gateway API token.
|
||||
func GetBluecatGatewayToken(cfg BluecatConfig) (string, http.Cookie, error) {
|
||||
var username string
|
||||
if cfg.GatewayUsername != "" {
|
||||
username = cfg.GatewayUsername
|
||||
}
|
||||
if v, ok := os.LookupEnv("BLUECAT_USERNAME"); ok {
|
||||
username = v
|
||||
}
|
||||
|
||||
var password string
|
||||
if cfg.GatewayPassword != "" {
|
||||
password = cfg.GatewayPassword
|
||||
}
|
||||
if v, ok := os.LookupEnv("BLUECAT_PASSWORD"); ok {
|
||||
password = v
|
||||
}
|
||||
|
||||
body, err := json.Marshal(map[string]string{
|
||||
"username": username,
|
||||
"password": password,
|
||||
})
|
||||
if err != nil {
|
||||
return "", http.Cookie{}, errors.Wrap(err, "could not unmarshal credentials for bluecat gateway config")
|
||||
}
|
||||
url := cfg.GatewayHost + "/rest_login"
|
||||
|
||||
response, err := executeHTTPRequest(cfg.SkipTLSVerify, http.MethodPost, url, "", bytes.NewBuffer(body), http.Cookie{})
|
||||
|
||||
if err != nil {
|
||||
return "", http.Cookie{}, errors.Wrap(err, "error obtaining API token from bluecat gateway")
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", http.Cookie{}, errors.Wrap(err, "failed to read login response from bluecat gateway")
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return "", http.Cookie{}, errors.Errorf("got HTTP response code %v, detailed message: %v", response.StatusCode, string(responseBody))
|
||||
}
|
||||
|
||||
jsonResponse := map[string]string{}
|
||||
err = json.Unmarshal(responseBody, &jsonResponse)
|
||||
if err != nil {
|
||||
return "", http.Cookie{}, errors.Wrap(err, "error unmarshaling json response (auth) from bluecat gateway")
|
||||
}
|
||||
|
||||
// Example response: {"access_token": "BAMAuthToken: abc123"}
|
||||
// We only care about the actual token string - i.e. abc123
|
||||
// The gateway also creates a cookie as part of the response. This seems to be the actual auth mechanism, at least
|
||||
// for now.
|
||||
return strings.Split(jsonResponse["access_token"], " ")[1], *response.Cookies()[0], nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) GetBluecatZones(zoneName string) ([]BluecatZone, error) {
|
||||
zonePath := expandZone(zoneName)
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodGet, url, c.Token, nil, c.Cookie)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error requesting zones from gateway: %v, %v", url, zoneName)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, errors.Errorf("received http %v requesting zones from gateway in zone %v", response.StatusCode, zoneName)
|
||||
}
|
||||
|
||||
zones := []BluecatZone{}
|
||||
json.NewDecoder(response.Body).Decode(&zones)
|
||||
|
||||
// Bluecat Gateway only returns subzones one level deeper than the provided zone
|
||||
// so this recursion is needed to traverse subzones until none are returned
|
||||
for _, zone := range zones {
|
||||
zoneProps := SplitProperties(zone.Properties)
|
||||
subZones, err := c.GetBluecatZones(zoneProps["absoluteName"])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error retrieving subzones from gateway: %v", zoneName)
|
||||
}
|
||||
zones = append(zones, subZones...)
|
||||
}
|
||||
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) GetHostRecords(zone string, records *[]BluecatHostRecord) error {
|
||||
zonePath := expandZone(zone)
|
||||
// Remove the trailing 'zones/'
|
||||
zonePath = strings.TrimSuffix(zonePath, "zones/")
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath + "host_records/"
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodGet, url, c.Token, nil, c.Cookie)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error requesting host records from gateway in zone %v", zone)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return errors.Errorf("received http %v requesting host records from gateway in zone %v", response.StatusCode, zone)
|
||||
}
|
||||
|
||||
json.NewDecoder(response.Body).Decode(records)
|
||||
log.Debugf("Get Host Records Response: %v", records)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) GetCNAMERecords(zone string, records *[]BluecatCNAMERecord) error {
|
||||
zonePath := expandZone(zone)
|
||||
// Remove the trailing 'zones/'
|
||||
zonePath = strings.TrimSuffix(zonePath, "zones/")
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath + "cname_records/"
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodGet, url, c.Token, nil, c.Cookie)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving cname records from gateway in zone %v", zone)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return errors.Errorf("received http %v requesting cname records from gateway in zone %v", response.StatusCode, zone)
|
||||
}
|
||||
|
||||
json.NewDecoder(response.Body).Decode(records)
|
||||
log.Debugf("Get CName Records Response: %v", records)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) GetTXTRecords(zone string, records *[]BluecatTXTRecord) error {
|
||||
zonePath := expandZone(zone)
|
||||
// Remove the trailing 'zones/'
|
||||
zonePath = strings.TrimSuffix(zonePath, "zones/")
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath + "text_records/"
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodGet, url, c.Token, nil, c.Cookie)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving txt records from gateway in zone %v", zone)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return errors.Errorf("received http %v requesting txt records from gateway in zone %v", response.StatusCode, zone)
|
||||
}
|
||||
|
||||
log.Debugf("Get Txt Records response: %v", response)
|
||||
json.NewDecoder(response.Body).Decode(records)
|
||||
log.Debugf("Get TXT Records Body: %v", records)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) GetHostRecord(name string, record *BluecatHostRecord) error {
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
|
||||
"/views/" + c.View + "/" +
|
||||
"host_records/" + name + "/"
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodGet, url, c.Token, nil, c.Cookie)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving host record %v from gateway", name)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return errors.Errorf("received http %v while retrieving host record %v from gateway", response.StatusCode, name)
|
||||
}
|
||||
|
||||
json.NewDecoder(response.Body).Decode(record)
|
||||
log.Debugf("Get Host Record Response: %v", record)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) GetCNAMERecord(name string, record *BluecatCNAMERecord) error {
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
|
||||
"/views/" + c.View + "/" +
|
||||
"cname_records/" + name + "/"
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodGet, url, c.Token, nil, c.Cookie)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving cname record %v from gateway", name)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return errors.Errorf("received http %v while retrieving cname record %v from gateway", response.StatusCode, name)
|
||||
}
|
||||
|
||||
json.NewDecoder(response.Body).Decode(record)
|
||||
log.Debugf("Get CName Record Response: %v", record)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) GetTXTRecord(name string, record *BluecatTXTRecord) error {
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
|
||||
"/views/" + c.View + "/" +
|
||||
"text_records/" + name + "/"
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodGet, url, c.Token, nil, c.Cookie)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving record %v from gateway", name)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return errors.Errorf("received http %v while retrieving txt record %v from gateway", response.StatusCode, name)
|
||||
}
|
||||
|
||||
json.NewDecoder(response.Body).Decode(record)
|
||||
log.Debugf("Get TXT Record Response: %v", record)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) CreateHostRecord(zone string, req *BluecatCreateHostRecordRequest) error {
|
||||
zonePath := expandZone(zone)
|
||||
// Remove the trailing 'zones/'
|
||||
zonePath = strings.TrimSuffix(zonePath, "zones/")
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath + "host_records/"
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal body for create host record")
|
||||
}
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodPost, url, c.Token, bytes.NewBuffer(body), c.Cookie)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating host record %v in gateway", req.AbsoluteName)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusCreated {
|
||||
return errors.Errorf("received http %v while creating host record %v in gateway", response.StatusCode, req.AbsoluteName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) CreateCNAMERecord(zone string, req *BluecatCreateCNAMERecordRequest) error {
|
||||
zonePath := expandZone(zone)
|
||||
// Remove the trailing 'zones/'
|
||||
zonePath = strings.TrimSuffix(zonePath, "zones/")
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath + "cname_records/"
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal body for create cname record")
|
||||
}
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodPost, url, c.Token, bytes.NewBuffer(body), c.Cookie)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating cname record %v in gateway", req.AbsoluteName)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusCreated {
|
||||
return errors.Errorf("received http %v while creating cname record %v to alias %v in gateway", response.StatusCode, req.AbsoluteName, req.LinkedRecord)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) CreateTXTRecord(zone string, req *BluecatCreateTXTRecordRequest) error {
|
||||
zonePath := expandZone(zone)
|
||||
// Remove the trailing 'zones/'
|
||||
zonePath = strings.TrimSuffix(zonePath, "zones/")
|
||||
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath + "text_records/"
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal body for create txt record")
|
||||
}
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodPost, url, c.Token, bytes.NewBuffer(body), c.Cookie)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating txt record %v in gateway", req.AbsoluteName)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusCreated {
|
||||
return errors.Errorf("received http %v while creating txt record %v in gateway", response.StatusCode, req.AbsoluteName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) DeleteHostRecord(name string, zone string) (err error) {
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
|
||||
"/views/" + c.View + "/" +
|
||||
"host_records/" + name + "." + zone + "/"
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodDelete, url, c.Token, nil, c.Cookie)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error deleting host record %v from gateway", name)
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusNoContent {
|
||||
return errors.Errorf("received http %v while deleting host record %v from gateway", response.StatusCode, name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) DeleteCNAMERecord(name string, zone string) (err error) {
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
|
||||
"/views/" + c.View + "/" +
|
||||
"cname_records/" + name + "." + zone + "/"
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodDelete, url, c.Token, nil, c.Cookie)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error deleting cname record %v from gateway", name)
|
||||
}
|
||||
if response.StatusCode != http.StatusNoContent {
|
||||
return errors.Errorf("received http %v while deleting cname record %v from gateway", response.StatusCode, name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) DeleteTXTRecord(name string, zone string) error {
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
|
||||
"/views/" + c.View + "/" +
|
||||
"text_records/" + name + "." + zone + "/"
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodDelete, url, c.Token, nil, c.Cookie)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error deleting txt record %v from gateway", name)
|
||||
}
|
||||
if response.StatusCode != http.StatusNoContent {
|
||||
return errors.Errorf("received http %v while deleting txt record %v from gateway", response.StatusCode, name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) ServerFullDeploy() error {
|
||||
log.Infof("Executing full deploy on server %s", c.DNSServerName)
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/server/full_deploy/"
|
||||
requestBody := BluecatServerFullDeployRequest{
|
||||
ServerName: c.DNSServerName,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal body for server full deploy")
|
||||
}
|
||||
|
||||
response, err := executeHTTPRequest(c.SkipTLSVerify, http.MethodPost, url, c.Token, bytes.NewBuffer(body), c.Cookie)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error executing full deploy")
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusCreated {
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read full deploy response body")
|
||||
}
|
||||
return errors.Errorf("got HTTP response code %v, detailed message: %v", response.StatusCode, string(responseBody))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SplitProperties is a helper function to break a '|' separated string into key/value pairs
|
||||
// i.e. "foo=bar|baz=mop"
|
||||
func SplitProperties(props string) map[string]string {
|
||||
propMap := make(map[string]string)
|
||||
// remove trailing | character before we split
|
||||
props = strings.TrimSuffix(props, "|")
|
||||
|
||||
splits := strings.Split(props, "|")
|
||||
for _, pair := range splits {
|
||||
items := strings.Split(pair, "=")
|
||||
propMap[items[0]] = items[1]
|
||||
}
|
||||
|
||||
return propMap
|
||||
}
|
||||
|
||||
// IsValidDNSDeployType validates the deployment type provided by a users configuration is supported by the Bluecat Provider.
|
||||
func IsValidDNSDeployType(deployType string) bool {
|
||||
validDNSDeployTypes := []string{"no-deploy", "full-deploy"}
|
||||
for _, t := range validDNSDeployTypes {
|
||||
if t == deployType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// expandZone takes an absolute domain name such as 'example.com' and returns a zone hierarchy used by Bluecat Gateway,
|
||||
// such as '/zones/com/zones/example/zones/'
|
||||
func expandZone(zone string) string {
|
||||
ze := "zones/"
|
||||
parts := strings.Split(zone, ".")
|
||||
if len(parts) > 1 {
|
||||
last := len(parts) - 1
|
||||
for i := range parts {
|
||||
ze = ze + parts[last-i] + "/zones/"
|
||||
}
|
||||
} else {
|
||||
ze = ze + zone + "/zones/"
|
||||
}
|
||||
return ze
|
||||
}
|
||||
|
||||
func executeHTTPRequest(skipTLSVerify bool, method, url, token string, body io.Reader, cookie http.Cookie) (*http.Response, error) {
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: skipTLSVerify,
|
||||
},
|
||||
},
|
||||
}
|
||||
request, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if request.Method == http.MethodPost {
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
}
|
||||
request.Header.Add("Accept", "application/json")
|
||||
|
||||
if token != "" {
|
||||
request.Header.Add("Authorization", "Basic "+token)
|
||||
}
|
||||
request.AddCookie(&cookie)
|
||||
|
||||
return httpClient.Do(request)
|
||||
}
|
||||
228
provider/bluecat/gateway/api_test.go
Normal file
228
provider/bluecat/gateway/api_test.go
Normal file
@ -0,0 +1,228 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestBluecatNewGatewayClient(t *testing.T) {
|
||||
testCookie := http.Cookie{Name: "testCookie", Value: "exampleCookie"}
|
||||
testToken := "exampleToken"
|
||||
testgateWayHost := "exampleHost"
|
||||
testDNSConfiguration := "exampleDNSConfiguration"
|
||||
testDNSServer := "exampleServer"
|
||||
testView := "testView"
|
||||
testZone := "example.com"
|
||||
testVerify := true
|
||||
|
||||
client := NewGatewayClientConfig(testCookie, testToken, testgateWayHost, testDNSConfiguration, testView, testZone, testDNSServer, testVerify)
|
||||
|
||||
if client.Cookie.Value != testCookie.Value || client.Cookie.Name != testCookie.Name || client.Token != testToken || client.Host != testgateWayHost || client.DNSConfiguration != testDNSConfiguration || client.View != testView || client.RootZone != testZone || client.SkipTLSVerify != testVerify {
|
||||
t.Fatal("Client values dont match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBluecatExpandZones(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
"with subdomain": {input: "example.com", want: "zones/com/zones/example/zones/"},
|
||||
"only top level domain": {input: "com", want: "zones/com/zones/"},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := expandZone(tc.input)
|
||||
diff := cmp.Diff(tc.want, got)
|
||||
if diff != "" {
|
||||
t.Fatalf(diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBluecatValidDeployTypes(t *testing.T) {
|
||||
validTypes := []string{"no-deploy", "full-deploy"}
|
||||
invalidTypes := []string{"anything-else"}
|
||||
for _, i := range validTypes {
|
||||
if !IsValidDNSDeployType(i) {
|
||||
t.Fatalf("%s should be a valid deploy type", i)
|
||||
}
|
||||
}
|
||||
for _, i := range invalidTypes {
|
||||
if IsValidDNSDeployType(i) {
|
||||
t.Fatalf("%s should be a invalid deploy type", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add error checking in case "properties" are not properly formatted
|
||||
// Example test case... "invalid": {input: "abcde", want: map[string]string{}, err: InvalidProperty},
|
||||
func TestBluecatSplitProperties(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
input string
|
||||
want map[string]string
|
||||
}{
|
||||
"simple": {input: "ab=cd|ef=gh", want: map[string]string{"ab": "cd", "ef": "gh"}},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := SplitProperties(tc.input)
|
||||
diff := cmp.Diff(tc.want, got)
|
||||
if diff != "" {
|
||||
t.Fatalf(diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTXTRecord(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
req := BluecatCreateTXTRecordRequest{}
|
||||
requestBodyBytes, _ := ioutil.ReadAll(r.Body)
|
||||
err := json.Unmarshal(requestBodyBytes, &req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal body for server full deploy")
|
||||
}
|
||||
if req.AbsoluteName == "alreadyexists.test.com" {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tests := map[string]struct {
|
||||
config GatewayClientConfig
|
||||
zone string
|
||||
record BluecatCreateTXTRecordRequest
|
||||
expectError bool
|
||||
}{
|
||||
"simple-success": {GatewayClientConfig{Host: server.URL}, "test.com", BluecatCreateTXTRecordRequest{AbsoluteName: "my.test.com", Text: "here is my text"}, false},
|
||||
"simple-failure": {GatewayClientConfig{Host: server.URL}, "test.com", BluecatCreateTXTRecordRequest{AbsoluteName: "alreadyexists.test.com", Text: "here is my text"}, true},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := tc.config.CreateTXTRecord(tc.zone, &tc.record)
|
||||
if got != nil && !tc.expectError {
|
||||
t.Fatalf("expected error %v, received error %v", tc.expectError, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTXTRecord(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.Contains(r.RequestURI, "doesnotexist") {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tests := map[string]struct {
|
||||
config GatewayClientConfig
|
||||
name string
|
||||
expectError bool
|
||||
}{
|
||||
"simple-success": {GatewayClientConfig{Host: server.URL}, "mytxtrecord", false},
|
||||
"simple-failure": {GatewayClientConfig{Host: server.URL}, "doesnotexist", true},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
record := BluecatTXTRecord{}
|
||||
got := tc.config.GetTXTRecord(tc.name, &record)
|
||||
if got != nil && !tc.expectError {
|
||||
t.Fatalf("expected error %v, received error %v", tc.expectError, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteTXTRecord(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.Contains(r.RequestURI, "doesnotexist") {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tests := map[string]struct {
|
||||
config GatewayClientConfig
|
||||
name string
|
||||
zone string
|
||||
expectError bool
|
||||
}{
|
||||
"simple-success": {GatewayClientConfig{Host: server.URL}, "todelete", "test.com", false},
|
||||
"simple-failure": {GatewayClientConfig{Host: server.URL}, "doesnotexist", "test.com", true},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := tc.config.DeleteTXTRecord(tc.name, tc.zone)
|
||||
if got != nil && !tc.expectError {
|
||||
t.Fatalf("expected error %v, received error %v", tc.expectError, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerFullDeploy(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
req := BluecatServerFullDeployRequest{}
|
||||
requestBodyBytes, _ := ioutil.ReadAll(r.Body)
|
||||
err := json.Unmarshal(requestBodyBytes, &req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal body for server full deploy")
|
||||
}
|
||||
if req.ServerName == "serverdoesnotexist" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tests := map[string]struct {
|
||||
config GatewayClientConfig
|
||||
expectError bool
|
||||
}{
|
||||
"simple-success": {GatewayClientConfig{Host: server.URL, DNSServerName: "myserver"}, false},
|
||||
"simple-failure": {GatewayClientConfig{Host: server.URL, DNSServerName: "serverdoesnotexist"}, true},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := tc.config.ServerFullDeploy()
|
||||
if got != nil && !tc.expectError {
|
||||
t.Fatalf("expected error %v, received error %v", tc.expectError, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user