provider/cloudflare: Add migration for v1 to v4 API libraries (#6969)
* provider/cloudflare: Add migration for v1 to v4 API libraries - mock out api calls
This commit is contained in:
parent
b0bc6718a1
commit
7d23570e2d
|
@ -15,6 +15,8 @@ func resourceCloudFlareRecord() *schema.Resource {
|
|||
Update: resourceCloudFlareRecordUpdate,
|
||||
Delete: resourceCloudFlareRecordDelete,
|
||||
|
||||
SchemaVersion: 1,
|
||||
MigrateState: resourceCloudFlareRecordMigrateState,
|
||||
Schema: map[string]*schema.Schema{
|
||||
"domain": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
package cloudflare
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/cloudflare/cloudflare-go"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func resourceCloudFlareRecordMigrateState(
|
||||
v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
|
||||
switch v {
|
||||
case 0:
|
||||
log.Println("[INFO] Found CloudFlare Record State v0; migrating to v1")
|
||||
return migrateCloudFlareRecordStateV0toV1(is, meta)
|
||||
default:
|
||||
return is, fmt.Errorf("Unexpected schema version: %d", v)
|
||||
}
|
||||
}
|
||||
|
||||
func migrateCloudFlareRecordStateV0toV1(is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
|
||||
if is.Empty() {
|
||||
log.Println("[DEBUG] Empty InstanceState; nothing to migrate.")
|
||||
return is, nil
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Attributes before migration: %#v", is.Attributes)
|
||||
client := meta.(*cloudflare.API)
|
||||
|
||||
// look up new id based on attributes
|
||||
domain := is.Attributes["domain"]
|
||||
zoneId, err := client.ZoneIDByName(domain)
|
||||
if err != nil {
|
||||
return is, fmt.Errorf("Error finding zone %q: %s", domain, err)
|
||||
}
|
||||
|
||||
// all other information is ignored in the DNSRecords call
|
||||
searchRecord := cloudflare.DNSRecord{
|
||||
Type: is.Attributes["type"],
|
||||
Name: is.Attributes["hostname"],
|
||||
Content: is.Attributes["value"],
|
||||
}
|
||||
|
||||
records, err := client.DNSRecords(zoneId, searchRecord)
|
||||
if err != nil {
|
||||
return is, err
|
||||
}
|
||||
|
||||
for _, r := range records {
|
||||
if is.Attributes["ttl"] != "" {
|
||||
v, err := strconv.Atoi(is.Attributes["ttl"])
|
||||
if err != nil {
|
||||
return is, fmt.Errorf("Error converting ttl to int in CloudFlare Record Migration")
|
||||
}
|
||||
|
||||
if v != r.TTL {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if is.Attributes["proxied"] != "" {
|
||||
b, err := strconv.ParseBool(is.Attributes["proxied"])
|
||||
if err != nil {
|
||||
return is, fmt.Errorf("Error converting proxied to bool in CloudFlare Record Migration")
|
||||
}
|
||||
|
||||
if b != r.Proxied {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if is.Attributes["priority"] != "" {
|
||||
v, err := strconv.Atoi(is.Attributes["priority"])
|
||||
if err != nil {
|
||||
return is, fmt.Errorf("Error converting priority to int in CloudFlare Record Migration")
|
||||
}
|
||||
|
||||
if v != r.Priority {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// assume record found
|
||||
is.Attributes["id"] = r.ID
|
||||
is.ID = r.ID
|
||||
log.Printf("[DEBUG] Attributes after migration: %#v", is.Attributes)
|
||||
return is, nil
|
||||
}
|
||||
|
||||
// assume no record found
|
||||
log.Printf("[DEBUG] Attributes after no migration: %#v", is.Attributes)
|
||||
return is, fmt.Errorf("No matching Record found")
|
||||
}
|
|
@ -0,0 +1,335 @@
|
|||
package cloudflare
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/cloudflare-go"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestCloudFlareRecordMigrateState(t *testing.T) {
|
||||
// create the test server for mocking the API calls
|
||||
ts := mockCloudFlareEnv()
|
||||
defer ts.Close()
|
||||
|
||||
// Create a CloudFlare client, overriding the BaseURL
|
||||
cfMeta, err := cloudflare.New(
|
||||
"sometoken",
|
||||
"someemail",
|
||||
mockHTTPClient(ts.URL),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error building CloudFlare API: %s", err)
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
StateVersion int
|
||||
ID string
|
||||
Attributes map[string]string
|
||||
Expected string
|
||||
ShouldFail bool
|
||||
}{
|
||||
"ttl_120": {
|
||||
StateVersion: 0,
|
||||
ID: "123456",
|
||||
Attributes: map[string]string{
|
||||
"id": "123456",
|
||||
"name": "notthesub",
|
||||
"hostname": "notthesub.hashicorptest.com",
|
||||
"type": "A",
|
||||
"content": "10.0.2.5",
|
||||
"ttl": "120",
|
||||
"zone_id": "1234567890",
|
||||
"domain": "hashicorptest.com",
|
||||
},
|
||||
Expected: "7778f8766e583af8de0abfcd76c5dAAA",
|
||||
},
|
||||
"ttl_121": {
|
||||
StateVersion: 0,
|
||||
ID: "123456",
|
||||
Attributes: map[string]string{
|
||||
"id": "123456",
|
||||
"name": "notthesub",
|
||||
"hostname": "notthesub.hashicorptest.com",
|
||||
"type": "A",
|
||||
"content": "10.0.2.5",
|
||||
"ttl": "121",
|
||||
"zone_id": "1234567890",
|
||||
"domain": "hashicorptest.com",
|
||||
},
|
||||
Expected: "5558f8766e583af8de0abfcd76c5dBBB",
|
||||
},
|
||||
"mx_priority": {
|
||||
StateVersion: 0,
|
||||
ID: "123456",
|
||||
Attributes: map[string]string{
|
||||
"id": "123456",
|
||||
"name": "hashicorptest.com",
|
||||
"type": "MX",
|
||||
"content": "some.registrar-servers.com",
|
||||
"ttl": "1",
|
||||
"priority": "20",
|
||||
"zone_id": "1234567890",
|
||||
"domain": "hashicorptest.com",
|
||||
},
|
||||
Expected: "12342092cbc4c391be33ce548713bba3",
|
||||
},
|
||||
"mx_priority_mismatch": {
|
||||
StateVersion: 0,
|
||||
ID: "123456",
|
||||
Attributes: map[string]string{
|
||||
"id": "123456",
|
||||
"type": "MX",
|
||||
"name": "hashicorptest.com",
|
||||
"content": "some.registrar-servers.com",
|
||||
"ttl": "1",
|
||||
"priority": "10",
|
||||
"zone_id": "1234567890",
|
||||
"domain": "hashicorptest.com",
|
||||
},
|
||||
Expected: "12342092cbc4c391be33ce548713bba3",
|
||||
ShouldFail: true,
|
||||
},
|
||||
"proxied": {
|
||||
StateVersion: 0,
|
||||
ID: "123456",
|
||||
Attributes: map[string]string{
|
||||
"id": "123456",
|
||||
"name": "tftestingsubv616",
|
||||
"hostname": "tftestingsubv616.hashicorptest.com",
|
||||
"type": "A",
|
||||
"content": "52.39.212.111",
|
||||
"proxied": "true",
|
||||
"ttl": "1",
|
||||
"zone_id": "1234567890",
|
||||
"domain": "hashicorptest.com",
|
||||
},
|
||||
Expected: "888ffe3f93a31231ad6b0c6d09185eee",
|
||||
},
|
||||
"not_proxied": {
|
||||
StateVersion: 0,
|
||||
ID: "123456",
|
||||
Attributes: map[string]string{
|
||||
"id": "123456",
|
||||
"name": "tftestingsubv616",
|
||||
"hostname": "tftestingsubv616.hashicorptest.com",
|
||||
"type": "A",
|
||||
"content": "52.39.212.111",
|
||||
"proxied": "false",
|
||||
"ttl": "1",
|
||||
"zone_id": "1234567890",
|
||||
"domain": "hashicorptest.com",
|
||||
},
|
||||
Expected: "222ffe3f93a31231ad6b0c6d09185jjj",
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range cases {
|
||||
is := &terraform.InstanceState{
|
||||
ID: tc.ID,
|
||||
Attributes: tc.Attributes,
|
||||
}
|
||||
is, err := resourceCloudFlareRecordMigrateState(
|
||||
tc.StateVersion, is, cfMeta)
|
||||
|
||||
if err != nil {
|
||||
if tc.ShouldFail {
|
||||
// expected error
|
||||
continue
|
||||
}
|
||||
t.Fatalf("bad: %s, err: %#v", tn, err)
|
||||
}
|
||||
|
||||
if is.ID != tc.Expected {
|
||||
t.Fatalf("bad sg rule id: %s\n\n expected: %s", is.ID, tc.Expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cloudflareEnv establishes a httptest server to mock out the CloudFlare API
|
||||
// endpoints that we'll be calling.
|
||||
func mockCloudFlareEnv() *httptest.Server {
|
||||
endpoints := mockEndpoints()
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
log.Printf("[DEBUG] Mocker server received request to %q", r.RequestURI)
|
||||
rBase, err := url.ParseRequestURI(r.RequestURI)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to find the base path: %s", err)
|
||||
}
|
||||
for _, e := range endpoints {
|
||||
if rBase.Path == e.BasePath {
|
||||
fmt.Fprintln(w, e.Body)
|
||||
w.WriteHeader(200)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.WriteHeader(400)
|
||||
}))
|
||||
|
||||
return ts
|
||||
}
|
||||
|
||||
// Stub out the two CloudFlare API routes that will be called
|
||||
func mockEndpoints() []*endpoint {
|
||||
return []*endpoint{
|
||||
&endpoint{
|
||||
BasePath: "/zones",
|
||||
Body: zoneResponse,
|
||||
},
|
||||
&endpoint{
|
||||
BasePath: "/zones/1234567890/dns_records",
|
||||
Body: dnsResponse,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type routes struct {
|
||||
Endpoints []*endpoint
|
||||
}
|
||||
type endpoint struct {
|
||||
BasePath string
|
||||
Body string
|
||||
}
|
||||
|
||||
// HTTPClient accepts a custom *http.Client for making API calls.
|
||||
// This function is used as a callback of sorts to override any of the client
|
||||
// options that you can't directly set on the struct
|
||||
func mockHTTPClient(testURL string) cloudflare.Option {
|
||||
return func(api *cloudflare.API) error {
|
||||
api.BaseURL = testURL
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const zoneResponse = `
|
||||
{
|
||||
"result": [
|
||||
{
|
||||
"id": "1234567890",
|
||||
"name": "hashicorptest.com",
|
||||
"status": "active",
|
||||
"paused": false,
|
||||
"type": "full",
|
||||
"development_mode": 0
|
||||
}
|
||||
],
|
||||
"result_info": {
|
||||
"page": 1,
|
||||
"per_page": 20,
|
||||
"total_pages": 1,
|
||||
"count": 1,
|
||||
"total_count": 1
|
||||
},
|
||||
"success": true,
|
||||
"errors": [],
|
||||
"messages": []
|
||||
}
|
||||
`
|
||||
|
||||
const dnsResponse = `
|
||||
{
|
||||
"result": [
|
||||
{
|
||||
"id": "7778f8766e583af8de0abfcd76c5dAAA",
|
||||
"type": "A",
|
||||
"name": "notthesub.hashicorptest.com",
|
||||
"content": "10.0.2.5",
|
||||
"proxiable": false,
|
||||
"proxied": false,
|
||||
"ttl": 120,
|
||||
"locked": false,
|
||||
"zone_id": "1234567890",
|
||||
"zone_name": "hashicorptest.com"
|
||||
},
|
||||
{
|
||||
"id": "5558f8766e583af8de0abfcd76c5dBBB",
|
||||
"type": "A",
|
||||
"name": "notthesub.hashicorptest.com",
|
||||
"content": "10.0.2.5",
|
||||
"proxiable": false,
|
||||
"proxied": false,
|
||||
"ttl": 121,
|
||||
"locked": false,
|
||||
"zone_id": "1234567890",
|
||||
"zone_name": "hashicorptest.com"
|
||||
},
|
||||
{
|
||||
"id": "2220a9593ab869199b65c89bddf72ddd",
|
||||
"type": "A",
|
||||
"name": "maybethesub.hashicorptest.com",
|
||||
"content": "10.0.3.5",
|
||||
"proxiable": false,
|
||||
"proxied": false,
|
||||
"ttl": 120,
|
||||
"locked": false,
|
||||
"zone_id": "1234567890",
|
||||
"zone_name": "hashicorptest.com"
|
||||
},
|
||||
{
|
||||
"id": "222ffe3f93a31231ad6b0c6d09185jjj",
|
||||
"type": "A",
|
||||
"name": "tftestingsubv616.hashicorptest.com",
|
||||
"content": "52.39.212.111",
|
||||
"proxiable": true,
|
||||
"proxied": false,
|
||||
"ttl": 1,
|
||||
"locked": false,
|
||||
"zone_id": "1234567890",
|
||||
"zone_name": "hashicorptest.com"
|
||||
},
|
||||
{
|
||||
"id": "888ffe3f93a31231ad6b0c6d09185eee",
|
||||
"type": "A",
|
||||
"name": "tftestingsubv616.hashicorptest.com",
|
||||
"content": "52.39.212.111",
|
||||
"proxiable": true,
|
||||
"proxied": true,
|
||||
"ttl": 1,
|
||||
"locked": false,
|
||||
"zone_id": "1234567890",
|
||||
"zone_name": "hashicorptest.com"
|
||||
},
|
||||
{
|
||||
"id": "98y6t9ba87e6ee3e6aeba8f3dc52c81b",
|
||||
"type": "CNAME",
|
||||
"name": "somecname.hashicorptest.com",
|
||||
"content": "some.us-west-2.elb.amazonaws.com",
|
||||
"proxiable": true,
|
||||
"proxied": false,
|
||||
"ttl": 120,
|
||||
"locked": false,
|
||||
"zone_id": "1234567890",
|
||||
"zone_name": "hashicorptest.com"
|
||||
},
|
||||
{
|
||||
"id": "12342092cbc4c391be33ce548713bba3",
|
||||
"type": "MX",
|
||||
"name": "hashicorptest.com",
|
||||
"content": "some.registrar-servers.com",
|
||||
"proxiable": false,
|
||||
"proxied": false,
|
||||
"ttl": 1,
|
||||
"priority": 20,
|
||||
"locked": false,
|
||||
"zone_id": "1234567890",
|
||||
"zone_name": "hashicorptest.com"
|
||||
}
|
||||
],
|
||||
"result_info": {
|
||||
"page": 1,
|
||||
"per_page": 20,
|
||||
"total_pages": 2,
|
||||
"count": 20,
|
||||
"total_count": 4
|
||||
},
|
||||
"success": true,
|
||||
"errors": [],
|
||||
"messages": []
|
||||
}`
|
Loading…
Reference in New Issue