13 changed files with 391 additions and 162 deletions
@ -1,7 +1,7 @@
|
||||
- name: install threshold CA |
||||
copy: |
||||
src: '{{ clientId }}_CA.crt' |
||||
dest: '/root/threshold/{{ clientId }}_CA.crt' |
||||
dest: '/opt/threshold/{{ clientId }}_CA.crt' |
||||
owner: threshold |
||||
group: threshold |
||||
mode: '0600' |
@ -0,0 +1,148 @@
|
||||
package automation |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"net/http" |
||||
|
||||
errors "git.sequentialread.com/forest/pkg-errors" |
||||
"git.sequentialread.com/forest/rootsystem/configuration" |
||||
) |
||||
|
||||
type gandiLiveDNSRecord struct { |
||||
Name string `json:"rrset_name"` |
||||
Type string `json:"rrset_type"` |
||||
Values []string `json:"rrset_values"` |
||||
} |
||||
|
||||
func handleGandiLiveDNSRecordAlreadyExists( |
||||
config *configuration.Configuration, |
||||
workingDirectory string, |
||||
terraformDirectory string, |
||||
tfShow *TerraformShow, |
||||
) (bool, error) { |
||||
|
||||
var gandiCredential *configuration.Credential = nil |
||||
for _, cred := range config.Credentials { |
||||
if cred.Type == configuration.GANDI { |
||||
gandiCredential = &cred |
||||
break |
||||
} |
||||
} |
||||
|
||||
createLiveDNSRecords := filterResourceChanges( |
||||
tfShow, |
||||
ResourceChangeFilter{ResourceType: "gandi_livedns_record", Action: "create", NotAction: "delete"}, |
||||
) |
||||
|
||||
domainName, hasDomainName := config.Terraform.Variables["domain_name"] |
||||
|
||||
if gandiCredential == nil || len(createLiveDNSRecords) == 0 || !hasDomainName { |
||||
return false, nil |
||||
} |
||||
|
||||
// Get the list of DNS records from the Gandi API
|
||||
httpClient := http.Client{} |
||||
gandiURL := fmt.Sprintf( |
||||
"%s/%s/%s/%s", |
||||
configuration.GANDI_API_URL, "v5/livedns/domains", domainName, "records", |
||||
) |
||||
request, err := http.NewRequest("GET", gandiURL, nil) |
||||
|
||||
request.Header.Add("Authorization", fmt.Sprintf("Apikey %s", gandiCredential.Password)) |
||||
response, err := httpClient.Do(request) |
||||
|
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
if response.StatusCode != 200 { |
||||
return false, fmt.Errorf("HTTP %d when calling %s ", gandiURL, response.StatusCode) |
||||
} |
||||
bytes, err := ioutil.ReadAll(response.Body) |
||||
if err != nil { |
||||
return false, errors.Wrapf(err, "HTTP read error when calling %s ", gandiURL) |
||||
} |
||||
var recordsFromAPI []gandiLiveDNSRecord |
||||
err = json.Unmarshal(bytes, &recordsFromAPI) |
||||
if err != nil { |
||||
return false, errors.Wrapf(err, "JSON parse error when calling %s ", gandiURL) |
||||
} |
||||
|
||||
recordsFromAPIById := map[string]gandiLiveDNSRecord{} |
||||
|
||||
for _, record := range recordsFromAPI { |
||||
recordsFromAPIById[fmt.Sprintf("%s/%s/%s", domainName, record.Name, record.Type)] = record |
||||
} |
||||
|
||||
importedAnyRecords := false |
||||
|
||||
// for each record terraform plans to create, check if it already exists in Gandi
|
||||
for _, resource := range createLiveDNSRecords { |
||||
recordName, hasName := getStringValue(resource, "name") |
||||
recordType, hasType := getStringValue(resource, "type") |
||||
//recordName, hasName := getStringValue(resource, "type")
|
||||
if !hasName { |
||||
return false, fmt.Errorf("gandi_livedns_record %s (from terraform planned_values) is missing string value \"name\" ", resource.Address) |
||||
} |
||||
if !hasType { |
||||
return false, fmt.Errorf("gandi_livedns_record %s (from terraform planned_values) is missing string value \"type\" ", resource.Address) |
||||
} |
||||
|
||||
recordId := fmt.Sprintf("%s/%s/%s", domainName, recordName, recordType) |
||||
|
||||
_, gandiAlreadyHasRecord := recordsFromAPIById[recordId] |
||||
|
||||
if gandiAlreadyHasRecord { |
||||
fmt.Println("---------------------------------------") |
||||
fmt.Printf( |
||||
"Terraform plans to add the DNS record %s %s to gandi, but gandi already has a record like that \n", |
||||
recordType, domainName, |
||||
) |
||||
exitCode := removeAndImportResource(terraformDirectory, resource.Address, recordId) |
||||
|
||||
if exitCode != 0 { |
||||
return false, errors.New("terraform import returned a non-zero exit code") |
||||
} |
||||
importedAnyRecords = true |
||||
|
||||
fmt.Println("---------------------------------------") |
||||
} else { |
||||
altType := "" |
||||
if recordType == "CNAME" { |
||||
altType = "A" |
||||
} |
||||
if recordType == "A" { |
||||
altType = "CNAME" |
||||
} |
||||
altRecordId := fmt.Sprintf("%s/%s/%s", domainName, recordName, altType) |
||||
|
||||
_, gandiHasAltRecord := recordsFromAPIById[altRecordId] |
||||
if gandiHasAltRecord { |
||||
|
||||
fmt.Printf( |
||||
"Terraform plans to add the DNS record [%s %s] to gandi, but gandi has a conflicting record [%s %s]. Deleting the conflicting record...\n", |
||||
recordType, domainName, altType, domainName, |
||||
) |
||||
|
||||
gandiURL := fmt.Sprintf( |
||||
"%s/%s/%s/%s/%s/%s", |
||||
configuration.GANDI_API_URL, "v5/livedns/domains", domainName, "records", recordName, altType, |
||||
) |
||||
request, err := http.NewRequest("DELETE", gandiURL, nil) |
||||
request.Header.Add("Authorization", fmt.Sprintf("Apikey %s", gandiCredential.Password)) |
||||
response, err := httpClient.Do(request) |
||||
|
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
if response.StatusCode != 200 { |
||||
return false, fmt.Errorf("HTTP %d when calling DELETE %s ", gandiURL, response.StatusCode) |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
return importedAnyRecords, nil |
||||
} |
@ -0,0 +1,81 @@
|
||||
package automation |
||||
|
||||
import ( |
||||
"fmt" |
||||
) |
||||
|
||||
func getStringValue(change *TerraformShowResourceChange, key string) (string, bool) { |
||||
interfaceValue, has := change.Values[key] |
||||
if !has { |
||||
return "", false |
||||
} |
||||
switch stringValue := interfaceValue.(type) { |
||||
case string: |
||||
return stringValue, true |
||||
default: |
||||
return "", false |
||||
} |
||||
} |
||||
|
||||
func getStringSliceValue(change *TerraformShowResourceChange, key string) ([]string, bool) { |
||||
interfaceValue, has := change.Values[key] |
||||
if !has { |
||||
return []string{}, false |
||||
} |
||||
switch stringSliceValue := interfaceValue.(type) { |
||||
case []string: |
||||
return stringSliceValue, true |
||||
default: |
||||
return []string{}, false |
||||
} |
||||
} |
||||
|
||||
func removeAndImportResource(terraformDirectory, address, id string) int { |
||||
|
||||
// fullyQualifiedResourcePath := fmt.Sprintf("%s[%d]", tfResourcePath, index)
|
||||
fmt.Printf( |
||||
"to fix this, I will run: \n(%s) $ terraform state rm %s\n(%s) $ terraform import %s %s\n\n", |
||||
terraformDirectory, address, terraformDirectory, address, id, |
||||
) |
||||
exitCode, stdout, stderr, _ := shellExec(terraformDirectory, "terraform", "state", "rm", address) |
||||
fmt.Printf( |
||||
"exitCode: %d\n\nstdout:\n%s\n\nstderr:\n%s\n\n", |
||||
exitCode, stdout, stderr, |
||||
) |
||||
exitCode, stdout, stderr, err := shellExec(terraformDirectory, "terraform", "import", address, id) |
||||
fmt.Printf( |
||||
"exitCode: %d\n\nstdout:\n%s\n\nstderr:\n%s\n\n", |
||||
exitCode, stdout, stderr, |
||||
) |
||||
if err != nil { |
||||
fmt.Println("shellExec failed! aborting.") |
||||
return exitCode |
||||
} |
||||
return exitCode |
||||
} |
||||
|
||||
func filterResourceChanges(tfShow *TerraformShow, filter ResourceChangeFilter) []*TerraformShowResourceChange { |
||||
toReturn := []*TerraformShowResourceChange{} |
||||
|
||||
for _, resource := range tfShow.ResourceChanges { |
||||
hasAction := filter.Action == "" |
||||
hasNotAction := false |
||||
for _, action := range resource.Change.Actions { |
||||
if action == filter.Action { |
||||
hasAction = true |
||||
} |
||||
if action == filter.NotAction { |
||||
hasNotAction = true |
||||
} |
||||
} |
||||
// fmt.Printf(
|
||||
// "resource.Type: %s == filter.ResourceType: %s, hasAction %t, !hasNotAction %t \n",
|
||||
// resource.Type, filter.ResourceType, hasAction, !hasNotAction,
|
||||
// )
|
||||
if resource.Type == filter.ResourceType && hasAction && !hasNotAction { |
||||
toReturn = append(toReturn, resource) |
||||
} |
||||
|
||||
} |
||||
return toReturn |
||||
} |
Loading…
Reference in new issue