diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 390014496..393a2ec78 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -925,6 +925,10 @@ "ImportPath": "github.com/mitchellh/cli", "Rev": "cb6853d606ea4a12a15ac83cc43503df99fd28fb" }, + { + "ImportPath": "github.com/mitchellh/cloudflare-go", + "Rev": "84c7a0993a06d555dbfddd2b32f5fa9b92fa1dc1" + }, { "ImportPath": "github.com/mitchellh/colorstring", "Rev": "8631ce90f28644f54aeedcb3e389a85174e067d1" @@ -999,6 +1003,11 @@ "ImportPath": "github.com/pearkes/mailgun", "Rev": "b88605989c4141d22a6d874f78800399e5bb7ac2" }, + { + "ImportPath": "github.com/pkg/errors", + "Comment": "v0.3.0", + "Rev": "42fa80f2ac6ed17a977ce826074bd3009593fa9d" + }, { "ImportPath": "github.com/rackspace/gophercloud", "Comment": "v1.0.0-884-gc54bbac", diff --git a/builtin/providers/cloudflare/config.go b/builtin/providers/cloudflare/config.go index 446b1ef70..e11fa8ec1 100644 --- a/builtin/providers/cloudflare/config.go +++ b/builtin/providers/cloudflare/config.go @@ -3,7 +3,8 @@ package cloudflare import ( "log" - "github.com/crackcomm/cloudflare" + // NOTE: Temporary until they merge my PR: + "github.com/mitchellh/cloudflare-go" ) type Config struct { @@ -12,13 +13,8 @@ type Config struct { } // Client() returns a new client for accessing cloudflare. -func (c *Config) Client() (*cloudflare.Client, error) { - client := cloudflare.New(&cloudflare.Options{ - Email: c.Email, - Key: c.Token, - }) - +func (c *Config) Client() (*cloudflare.API, error) { + client := cloudflare.New(c.Token, c.Email) log.Printf("[INFO] CloudFlare Client configured for user: %s", c.Email) - return client, nil } diff --git a/builtin/providers/cloudflare/resource_cloudflare_record.go b/builtin/providers/cloudflare/resource_cloudflare_record.go index 628e40df5..ad478dc7d 100644 --- a/builtin/providers/cloudflare/resource_cloudflare_record.go +++ b/builtin/providers/cloudflare/resource_cloudflare_record.go @@ -3,13 +3,11 @@ package cloudflare import ( "fmt" "log" - "strings" - "time" - "golang.org/x/net/context" - - "github.com/crackcomm/cloudflare" "github.com/hashicorp/terraform/helper/schema" + + // NOTE: Temporary until they merge my PR: + "github.com/mitchellh/cloudflare-go" ) func resourceCloudFlareRecord() *schema.Resource { @@ -72,13 +70,13 @@ func resourceCloudFlareRecord() *schema.Resource { } func resourceCloudFlareRecordCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudflare.Client) + client := meta.(*cloudflare.API) - newRecord := &cloudflare.Record{ - Content: d.Get("value").(string), - Name: d.Get("name").(string), - Proxied: d.Get("proxied").(bool), + newRecord := cloudflare.DNSRecord{ Type: d.Get("type").(string), + Name: d.Get("name").(string), + Content: d.Get("value").(string), + Proxied: d.Get("proxied").(bool), ZoneName: d.Get("domain").(string), } @@ -90,24 +88,22 @@ func resourceCloudFlareRecordCreate(d *schema.ResourceData, meta interface{}) er newRecord.TTL = ttl.(int) } - zone, err := retrieveZone(client, newRecord.ZoneName) + zoneId, err := client.ZoneIDByName(newRecord.ZoneName) if err != nil { - return err + return fmt.Errorf("Error finding zone %q: %s", newRecord.ZoneName, err) } - d.Set("zone_id", zone.ID) - newRecord.ZoneID = zone.ID + d.Set("zone_id", zoneId) + newRecord.ZoneID = zoneId log.Printf("[DEBUG] CloudFlare Record create configuration: %#v", newRecord) - ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Second*30)) - - err = client.Records.Create(ctx, newRecord) + r, err := client.CreateDNSRecord(zoneId, newRecord) if err != nil { return fmt.Errorf("Failed to create record: %s", err) } - d.SetId(newRecord.ID) + d.SetId(r.ID) log.Printf("[INFO] CloudFlare Record ID: %s", d.Id()) @@ -115,18 +111,15 @@ func resourceCloudFlareRecordCreate(d *schema.ResourceData, meta interface{}) er } func resourceCloudFlareRecordRead(d *schema.ResourceData, meta interface{}) error { - var ( - client = meta.(*cloudflare.Client) - domain = d.Get("domain").(string) - rName = strings.Join([]string{d.Get("name").(string), domain}, ".") - ) + client := meta.(*cloudflare.API) + domain := d.Get("domain").(string) - zone, err := retrieveZone(client, domain) + zoneId, err := client.ZoneIDByName(domain) if err != nil { - return err + return fmt.Errorf("Error finding zone %q: %s", domain, err) } - record, err := retrieveRecord(client, zone, rName) + record, err := client.DNSRecord(zoneId, d.Id()) if err != nil { return err } @@ -138,21 +131,21 @@ func resourceCloudFlareRecordRead(d *schema.ResourceData, meta interface{}) erro d.Set("ttl", record.TTL) d.Set("priority", record.Priority) d.Set("proxied", record.Proxied) - d.Set("zone_id", zone.ID) + d.Set("zone_id", zoneId) return nil } func resourceCloudFlareRecordUpdate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudflare.Client) + client := meta.(*cloudflare.API) - updateRecord := &cloudflare.Record{ - Content: d.Get("value").(string), + updateRecord := cloudflare.DNSRecord{ ID: d.Id(), - Name: d.Get("name").(string), - Proxied: false, Type: d.Get("type").(string), + Name: d.Get("name").(string), + Content: d.Get("value").(string), ZoneName: d.Get("domain").(string), + Proxied: false, } if priority, ok := d.GetOk("priority"); ok { @@ -167,18 +160,15 @@ func resourceCloudFlareRecordUpdate(d *schema.ResourceData, meta interface{}) er updateRecord.TTL = ttl.(int) } - zone, err := retrieveZone(client, updateRecord.ZoneName) + zoneId, err := client.ZoneIDByName(updateRecord.ZoneName) if err != nil { - return err + return fmt.Errorf("Error finding zone %q: %s", updateRecord.ZoneName, err) } - updateRecord.ZoneID = zone.ID + updateRecord.ZoneID = zoneId log.Printf("[DEBUG] CloudFlare Record update configuration: %#v", updateRecord) - - ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Second*30)) - - err = client.Records.Patch(ctx, updateRecord) + err = client.UpdateDNSRecord(zoneId, d.Id(), updateRecord) if err != nil { return fmt.Errorf("Failed to update CloudFlare Record: %s", err) } @@ -187,79 +177,20 @@ func resourceCloudFlareRecordUpdate(d *schema.ResourceData, meta interface{}) er } func resourceCloudFlareRecordDelete(d *schema.ResourceData, meta interface{}) error { - var ( - client = meta.(*cloudflare.Client) - domain = d.Get("domain").(string) - rName = strings.Join([]string{d.Get("name").(string), domain}, ".") - ) + client := meta.(*cloudflare.API) + domain := d.Get("domain").(string) - zone, err := retrieveZone(client, domain) + zoneId, err := client.ZoneIDByName(domain) if err != nil { - return err - } - - record, err := retrieveRecord(client, zone, rName) - if err != nil { - return err + return fmt.Errorf("Error finding zone %q: %s", domain, err) } log.Printf("[INFO] Deleting CloudFlare Record: %s, %s", domain, d.Id()) - ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Second*30)) - - err = client.Records.Delete(ctx, zone.ID, record.ID) + err = client.DeleteDNSRecord(zoneId, d.Id()) if err != nil { return fmt.Errorf("Error deleting CloudFlare Record: %s", err) } return nil } - -func retrieveRecord( - client *cloudflare.Client, - zone *cloudflare.Zone, - name string, -) (*cloudflare.Record, error) { - ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Second*30)) - - rs, err := client.Records.List(ctx, zone.ID) - if err != nil { - return nil, fmt.Errorf("Unable to retrieve records for (%s): %s", zone.Name, err) - } - - var record *cloudflare.Record - - for _, r := range rs { - if r.Name == name { - record = r - } - } - if record == nil { - return nil, fmt.Errorf("Unable to find Cloudflare record %s", name) - } - - return record, nil -} - -func retrieveZone(client *cloudflare.Client, domain string) (*cloudflare.Zone, error) { - ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Second*30)) - - zs, err := client.Zones.List(ctx) - if err != nil { - return nil, fmt.Errorf("Failed to fetch zone for %s: %s", domain, err) - } - - var zone *cloudflare.Zone - - for _, z := range zs { - if z.Name == domain { - zone = z - } - } - - if zone == nil { - return nil, fmt.Errorf("Failed to find zone for: %s", domain) - } - - return zone, nil -} diff --git a/builtin/providers/cloudflare/resource_cloudflare_record_test.go b/builtin/providers/cloudflare/resource_cloudflare_record_test.go index 82b9ccfa3..8044eef06 100644 --- a/builtin/providers/cloudflare/resource_cloudflare_record_test.go +++ b/builtin/providers/cloudflare/resource_cloudflare_record_test.go @@ -4,17 +4,16 @@ import ( "fmt" "os" "testing" - "time" - "golang.org/x/net/context" - - "github.com/crackcomm/cloudflare" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + + // NOTE: Temporary until they merge my PR: + "github.com/mitchellh/cloudflare-go" ) func TestAccCloudFlareRecord_Basic(t *testing.T) { - var record cloudflare.Record + var record cloudflare.DNSRecord domain := os.Getenv("CLOUDFLARE_DOMAIN") resource.Test(t, resource.TestCase{ @@ -39,8 +38,34 @@ func TestAccCloudFlareRecord_Basic(t *testing.T) { }) } +func TestAccCloudFlareRecord_Apex(t *testing.T) { + var record cloudflare.DNSRecord + domain := os.Getenv("CLOUDFLARE_DOMAIN") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudFlareRecordDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testAccCheckCloudFlareRecordConfigApex, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudFlareRecordExists("cloudflare_record.foobar", &record), + testAccCheckCloudFlareRecordAttributes(&record), + resource.TestCheckResourceAttr( + "cloudflare_record.foobar", "name", "@"), + resource.TestCheckResourceAttr( + "cloudflare_record.foobar", "domain", domain), + resource.TestCheckResourceAttr( + "cloudflare_record.foobar", "value", "192.168.0.10"), + ), + }, + }, + }) +} + func TestAccCloudFlareRecord_Proxied(t *testing.T) { - var record cloudflare.Record + var record cloudflare.DNSRecord domain := os.Getenv("CLOUDFLARE_DOMAIN") resource.Test(t, resource.TestCase{ @@ -69,7 +94,7 @@ func TestAccCloudFlareRecord_Proxied(t *testing.T) { } func TestAccCloudFlareRecord_Updated(t *testing.T) { - var record cloudflare.Record + var record cloudflare.DNSRecord domain := os.Getenv("CLOUDFLARE_DOMAIN") resource.Test(t, resource.TestCase{ @@ -108,7 +133,7 @@ func TestAccCloudFlareRecord_Updated(t *testing.T) { } func TestAccCloudFlareRecord_forceNewRecord(t *testing.T) { - var afterCreate, afterUpdate cloudflare.Record + var afterCreate, afterUpdate cloudflare.DNSRecord domain := os.Getenv("CLOUDFLARE_DOMAIN") resource.Test(t, resource.TestCase{ @@ -134,7 +159,7 @@ func TestAccCloudFlareRecord_forceNewRecord(t *testing.T) { } func testAccCheckCloudFlareRecordRecreated(t *testing.T, - before, after *cloudflare.Record) resource.TestCheckFunc { + before, after *cloudflare.DNSRecord) resource.TestCheckFunc { return func(s *terraform.State) error { if before.ID == after.ID { t.Fatalf("Expected change of Record Ids, but both were %v", before.ID) @@ -144,17 +169,14 @@ func testAccCheckCloudFlareRecordRecreated(t *testing.T, } func testAccCheckCloudFlareRecordDestroy(s *terraform.State) error { - var ( - client = testAccProvider.Meta().(*cloudflare.Client) - ctx, _ = context.WithDeadline(context.Background(), time.Now().Add(time.Second*30)) - ) + client := testAccProvider.Meta().(*cloudflare.API) for _, rs := range s.RootModule().Resources { if rs.Type != "cloudflare_record" { continue } - _, err := client.Records.Details(ctx, rs.Primary.Attributes["zone_id"], rs.Primary.ID) + _, err := client.DNSRecord(rs.Primary.Attributes["zone_id"], rs.Primary.ID) if err == nil { return fmt.Errorf("Record still exists") } @@ -163,7 +185,7 @@ func testAccCheckCloudFlareRecordDestroy(s *terraform.State) error { return nil } -func testAccCheckCloudFlareRecordAttributes(record *cloudflare.Record) resource.TestCheckFunc { +func testAccCheckCloudFlareRecordAttributes(record *cloudflare.DNSRecord) resource.TestCheckFunc { return func(s *terraform.State) error { if record.Content != "192.168.0.10" { @@ -174,7 +196,7 @@ func testAccCheckCloudFlareRecordAttributes(record *cloudflare.Record) resource. } } -func testAccCheckCloudFlareRecordAttributesUpdated(record *cloudflare.Record) resource.TestCheckFunc { +func testAccCheckCloudFlareRecordAttributesUpdated(record *cloudflare.DNSRecord) resource.TestCheckFunc { return func(s *terraform.State) error { if record.Content != "192.168.0.11" { @@ -185,7 +207,7 @@ func testAccCheckCloudFlareRecordAttributesUpdated(record *cloudflare.Record) re } } -func testAccCheckCloudFlareRecordExists(n string, record *cloudflare.Record) resource.TestCheckFunc { +func testAccCheckCloudFlareRecordExists(n string, record *cloudflare.DNSRecord) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -196,12 +218,8 @@ func testAccCheckCloudFlareRecordExists(n string, record *cloudflare.Record) res return fmt.Errorf("No Record ID is set") } - var ( - client = testAccProvider.Meta().(*cloudflare.Client) - ctx, _ = context.WithDeadline(context.Background(), time.Now().Add(time.Second*30)) - ) - - foundRecord, err := client.Records.Details(ctx, rs.Primary.Attributes["zone_id"], rs.Primary.ID) + client := testAccProvider.Meta().(*cloudflare.API) + foundRecord, err := client.DNSRecord(rs.Primary.Attributes["zone_id"], rs.Primary.ID) if err != nil { return err } @@ -210,7 +228,7 @@ func testAccCheckCloudFlareRecordExists(n string, record *cloudflare.Record) res return fmt.Errorf("Record not found") } - *record = *foundRecord + *record = foundRecord return nil } @@ -226,6 +244,15 @@ resource "cloudflare_record" "foobar" { ttl = 3600 }` +const testAccCheckCloudFlareRecordConfigApex = ` +resource "cloudflare_record" "foobar" { + domain = "%s" + name = "@" + value = "192.168.0.10" + type = "A" + ttl = 3600 +}` + const testAccCheckCloudFlareRecordConfigProxied = ` resource "cloudflare_record" "foobar" { domain = "%s" diff --git a/vendor/github.com/mitchellh/cloudflare-go/.travis.yml b/vendor/github.com/mitchellh/cloudflare-go/.travis.yml new file mode 100644 index 000000000..cb3b857f8 --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/.travis.yml @@ -0,0 +1,23 @@ +language: go +sudo: false + +matrix: + include: + - go: 1.4 + - go: 1.5 + - go: 1.6 + - go: tip + allow_failures: + - go: tip + +script: + - go get -t -v $(go list ./... | grep -v '/vendor/') + - diff -u <(echo -n) <(gofmt -d .) + - go vet $(go list ./... | grep -v '/vendor/') + - go test -v -race ./... + +notifications: + email: + recipients: + - jamesog@cloudflare.com + - msilverlock@cloudflare.com diff --git a/vendor/github.com/mitchellh/cloudflare-go/LICENSE b/vendor/github.com/mitchellh/cloudflare-go/LICENSE new file mode 100644 index 000000000..a53798b1c --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2015-2016, CloudFlare. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/mitchellh/cloudflare-go/README.md b/vendor/github.com/mitchellh/cloudflare-go/README.md new file mode 100644 index 000000000..e8551fa41 --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/README.md @@ -0,0 +1,44 @@ +[![GoDoc](https://godoc.org/github.com/cloudflare/cloudflare-go?status.svg)](https://godoc.org/github.com/cloudflare/cloudflare-go) + +# cloudflare + +A Go library for interacting with [CloudFlare's API v4](https://api.cloudflare.com/). + +# Installation + +You need a working Go environment. + +``` +go get github.com/cloudflare/cloudflare-go +``` + +# Getting Started + +``` +package main + +import ( + "fmt" + + "github.com/cloudflare/cloudflare-go" +) + +var api *cloudflare.API + +func main() { + // Construct a new API object + api = cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL")) + + // Fetch the list of zones on the account + zones, err := api.ListZones() + if err != nil { + fmt.Println(err) + } + // Print the zone names + for _, z := range zones { + fmt.Println(z.Name) + } +} +``` + +An example application, [flarectl](cmd/flarectl), is in this repository. diff --git a/vendor/github.com/mitchellh/cloudflare-go/cloudflare.go b/vendor/github.com/mitchellh/cloudflare-go/cloudflare.go new file mode 100644 index 000000000..24fa03ed9 --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/cloudflare.go @@ -0,0 +1,451 @@ +/* +Package cloudflare implements the CloudFlare v4 API. + +New API requests created like: + + api := cloudflare.New(apikey, apiemail) + +*/ +package cloudflare + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "net/http" + + "github.com/pkg/errors" +) + +const apiURL = "https://api.cloudflare.com/client/v4" + +// Error messages +const errMakeRequestError = "Error from makeRequest" +const errUnmarshalError = "Error unmarshalling JSON" + +type API struct { + APIKey string + APIEmail string +} + +// Initializes the API configuration. +func New(key, email string) *API { + return &API{key, email} +} + +// Initializes a new zone. +func NewZone() *Zone { + return &Zone{} +} + +// ZoneIDByName retrieves a zone's ID from the name. +func (api *API) ZoneIDByName(zoneName string) (string, error) { + res, err := api.ListZones(zoneName) + if err != nil { + return "", errors.Wrap(err, "ListZones command failed") + } + for _, zone := range res { + if zone.Name == zoneName { + return zone.ID, nil + } + } + return "", errors.New("Zone could not be found") +} + +// Params can be turned into a URL query string or a body +// TODO: Give this func a better name +func (api *API) makeRequest(method, uri string, params interface{}) ([]byte, error) { + // Replace nil with a JSON object if needed + var reqBody io.Reader + if params != nil { + json, err := json.Marshal(params) + if err != nil { + return nil, errors.Wrap(err, "Error marshalling params to JSON") + } + reqBody = bytes.NewReader(json) + } else { + reqBody = nil + } + req, err := http.NewRequest(method, apiURL+uri, reqBody) + if err != nil { + return nil, errors.Wrap(err, "HTTP request creation failed") + } + req.Header.Add("X-Auth-Key", api.APIKey) + req.Header.Add("X-Auth-Email", api.APIEmail) + // Could be application/json or multipart/form-data + // req.Header.Add("Content-Type", "application/json") + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, errors.Wrap(err, "HTTP request failed") + } + defer resp.Body.Close() + resBody, err := ioutil.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK { + if err != nil { + return nil, errors.Wrap(err, "Error returned from API") + } else if resBody != nil { + return nil, errors.New(string(resBody)) + } else { + return nil, errors.New(resp.Status) + } + } + return resBody, nil +} + +// The Response struct is a template. There will also be a result struct. +// There will be a unique response type for each response, which will include +// this type. +type Response struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` +} + +type ResultInfo struct { + Page int `json:"page"` + PerPage int `json:"per_page"` + Count int `json:"count"` + Total int `json:"total_count"` +} + +// An Organization describes a multi-user organization. (Enterprise only.) +type Organization struct { + ID string + Name string + Status string + Permissions []string + Roles []string +} + +// A User describes a user account. +type User struct { + ID string `json:"id"` + Email string `json:"email"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Username string `json:"username"` + Telephone string `json:"telephone"` + Country string `json:"country"` + Zipcode string `json:"zipcode"` + CreatedOn string `json:"created_on"` // Should this be a time.Date? + ModifiedOn string `json:"modified_on"` + APIKey string `json:"api_key"` + TwoFA bool `json:"two_factor_authentication_enabled"` + Betas []string `json:"betas"` + Organizations []Organization `json:"organizations"` +} + +type UserResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result User `json:"result"` +} + +type Owner struct { + ID string `json:"id"` + Email string `json:"email"` + OwnerType string `json:"owner_type"` +} + +// A Zone describes a CloudFlare zone. +type Zone struct { + ID string `json:"id"` + Name string `json:"name"` + DevMode int `json:"development_mode"` + OriginalNS []string `json:"original_name_servers"` + OriginalRegistrar string `json:"original_registrar"` + OriginalDNSHost string `json:"original_dnshost"` + CreatedOn string `json:"created_on"` + ModifiedOn string `json:"modified_on"` + NameServers []string `json:"name_servers"` + Owner Owner `json:"owner"` + Permissions []string `json:"permissions"` + Plan ZonePlan `json:"plan"` + Status string `json:"status"` + Paused bool `json:"paused"` + Type string `json:"type"` + Host struct { + Name string + Website string + } `json:"host"` + VanityNS []string `json:"vanity_name_servers"` + Betas []string `json:"betas"` + DeactReason string `json:"deactivation_reason"` + Meta ZoneMeta `json:"meta"` +} + +// Contains metadata about a zone. +type ZoneMeta struct { + // custom_certificate_quota is broken - sometimes it's a string, sometimes a number! + // CustCertQuota int `json:"custom_certificate_quota"` + PageRuleQuota int `json:"page_rule_quota"` + WildcardProxiable bool `json:"wildcard_proxiable"` + PhishingDetected bool `json:"phishing_detected"` +} + +// Contains the plan information for a zone. +type ZonePlan struct { + ID string `json:"id"` + Name string `json:"name"` + Price int `json:"price"` + Currency string `json:"currency"` + Frequency string `json:"frequency"` + LegacyID string `json:"legacy_id"` + IsSubscribed bool `json:"is_subscribed"` + CanSubscribe bool `json:"can_subscribe"` +} + +type ZoneResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result []Zone `json:"result"` +} + +type ZonePlanResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result []ZonePlan `json:"result"` +} + +// type zoneSetting struct { +// ID string `json:"id"` +// Editable bool `json:"editable"` +// ModifiedOn string `json:"modified_on"` +// } +// type zoneSettingStringVal struct { +// zoneSetting +// Value string `json:"value"` +// } +// type zoneSettingIntVal struct { +// zoneSetting +// Value int64 `json:"value"` +// } + +type ZoneSetting struct { + ID string `json:"id"` + Editable bool `json:"editable"` + ModifiedOn string `json:"modified_on"` + Value interface{} `json:"value"` + TimeRemaining int `json:"time_remaining"` +} + +type ZoneSettingResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result []ZoneSetting `json:"result"` +} + +// Describes a DNS record for a zone. +type DNSRecord struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Content string `json:"content,omitempty"` + Proxiable bool `json:"proxiable,omitempty"` + Proxied bool `json:"proxied,omitempty"` + TTL int `json:"ttl,omitempty"` + Locked bool `json:"locked,omitempty"` + ZoneID string `json:"zone_id,omitempty"` + ZoneName string `json:"zone_name,omitempty"` + CreatedOn string `json:"created_on,omitempty"` + ModifiedOn string `json:"modified_on,omitempty"` + Data interface{} `json:"data,omitempty"` // data returned by: SRV, LOC + Meta interface{} `json:"meta,omitempty"` + Priority int `json:"priority,omitempty"` +} + +// The response for creating or updating a DNS record. +type DNSRecordResponse struct { + Success bool `json:"success"` + Errors []interface{} `json:"errors"` + Messages []string `json:"messages"` + Result DNSRecord `json:"result"` +} + +// The response for listing DNS records. +type DNSListResponse struct { + Success bool `json:"success"` + Errors []interface{} `json:"errors"` + Messages []string `json:"messages"` + Result []DNSRecord `json:"result"` +} + +// Railgun status for a zone. +type ZoneRailgun struct { + ID string `json:"id"` + Name string `json:"string"` + Enabled bool `json:"enabled"` + Connected bool `json:"connected"` +} + +type ZoneRailgunResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result []ZoneRailgun `json:"result"` +} + +// Custom SSL certificates for a zone. +type ZoneCustomSSL struct { + ID string `json:"id"` + Hosts []string `json:"hosts"` + Issuer string `json:"issuer"` + Priority int `json:"priority"` + Status string `json:"success"` + BundleMethod string `json:"bundle_method"` + ZoneID string `json:"zone_id"` + Permissions []string `json:"permissions"` + UploadedOn string `json:"uploaded_on"` + ModifiedOn string `json:"modified_on"` + ExpiresOn string `json:"expires_on"` + KeylessServer KeylessSSL `json:"keyless_server"` +} + +type ZoneCustomSSLResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result []ZoneCustomSSL `json:"result"` +} + +type KeylessSSL struct { + ID string `json:"id"` + Name string `json:"name"` + Host string `json:"host"` + Port int `json:"port"` + Status string `json:"success"` + Enabled bool `json:"enabled"` + Permissions []string `json:"permissions"` + CreatedOn string `json:"created_on"` + ModifiedOn string `json:"modifed_on"` +} + +type KeylessSSLResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result []KeylessSSL `json:"result"` +} + +type Railgun struct { + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"success"` + Enabled bool `json:"enabled"` + ZonesConnected int `json:"zones_connected"` + Build string `json:"build"` + Version string `json:"version"` + Revision string `json:"revision"` + ActivationKey string `json:"activation_key"` + ActivatedOn string `json:"activated_on"` + CreatedOn string `json:"created_on"` + ModifiedOn string `json:"modified_on"` + // XXX: UpgradeInfo struct { + // version string + // url string + // } `json:"upgrade_info"` +} + +type RailgunResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result []Railgun `json:"result"` +} + +// Custom error pages. +type CustomPage struct { + CreatedOn string `json:"created_on"` + ModifiedOn string `json:"modified_on"` + URL string `json:"url"` + State string `json:"state"` + RequiredTokens []string `json:"required_tokens"` + PreviewTarget string `json:"preview_target"` + Description string `json:"description"` +} + +type CustomPageResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result []CustomPage `json:"result"` +} + +// WAF packages +type WAFPackage struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + ZoneID string `json:"zone_id"` + DetectionMode string `json:"detection_mode"` + Sensitivity string `json:"sensitivity"` + ActionMode string `json:"action_mode"` +} + +type WAFPackagesResponse struct { + Result []WAFPackage `json:"result"` + Success bool `json:"success"` + ResultInfo struct { + Page uint `json:"page"` + PerPage uint `json:"per_page"` + Count uint `json:"count"` + TotalCount uint `json:"total_count"` + } `json:"result_info"` +} + +type WAFRule struct { + ID string `json:"id"` + Description string `json:"description"` + Priority string `json:"priority"` + PackageID string `json:"package_id"` + Group struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"group"` + Mode string `json:"mode"` + DefaultMode string `json:"default_mode"` + AllowedModes []string `json:"allowed_modes"` +} + +type WAFRulesResponse struct { + Result []WAFRule `json:"result"` + Success bool `json:"success"` + ResultInfo struct { + Page uint `json:"page"` + PerPage uint `json:"per_page"` + Count uint `json:"count"` + TotalCount uint `json:"total_count"` + } `json:"result_info"` +} + +type PurgeCacheRequest struct { + Everything bool `json:"purge_everything,omitempty"` + Files []string `json:"files,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +type PurgeCacheResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` +} + +// IPs contains a list of IPv4 and IPv6 CIDRs +type IPRanges struct { + IPv4CIDRs []string `json:"ipv4_cidrs"` + IPv6CIDRs []string `json:"ipv6_cidrs"` +} + +// IPsResponse is the API response containing a list of IPs +type IPsResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result IPRanges `json:"result"` +} diff --git a/vendor/github.com/mitchellh/cloudflare-go/cmd/flarectl/README.md b/vendor/github.com/mitchellh/cloudflare-go/cmd/flarectl/README.md new file mode 100644 index 000000000..95e9630db --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/cmd/flarectl/README.md @@ -0,0 +1,34 @@ +# flarectl + +A CLI application for interacting with a CloudFlare account. + +# Usage + +You must set your API key and account email address in the environment variables `CF_API_KEY` and `CF_API_EMAIL`. + +``` +$ export CF_API_KEY=abcdef1234567890 +$ export CF_API_EMAIL=someone@example.com +$ flarectl +NAME: + flarectl - CloudFlare CLI + +USAGE: + flarectl [global options] command [command options] [arguments...] + +VERSION: + 2015.12.0 + +COMMANDS: + user, u User information + zone, z Zone information + dns, d DNS records + railgun, r Railgun information + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --help, -h show help + --version, -v print the version +``` + + diff --git a/vendor/github.com/mitchellh/cloudflare-go/cmd/flarectl/flarectl.go b/vendor/github.com/mitchellh/cloudflare-go/cmd/flarectl/flarectl.go new file mode 100644 index 000000000..93ba7accd --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/cmd/flarectl/flarectl.go @@ -0,0 +1,726 @@ +package main + +import ( + "errors" + "fmt" + "os" + "reflect" + "strings" + + "github.com/cloudflare/cloudflare-go" + "github.com/codegangsta/cli" +) + +var api *cloudflare.API + +// Map type used for printing a table +type table map[string]string + +// Print a nicely-formatted table +func makeTable(zones []table, cols ...string) { + // Store the maximum length of all columns + // The default is the length of the title + lens := make(map[string]int) + for _, col := range cols { + lens[col] = len(col) + } + // Increase the size of the column if it is larger than the current value + for _, z := range zones { + for col, val := range z { + if _, ok := lens[col]; ok && len(val) > lens[col] { + lens[col] = len(val) + } + } + } + // Print the headings and an underline for each heading + for _, col := range cols { + fmt.Printf("%s%s ", strings.Title(col), strings.Repeat(" ", lens[col]-len(col))) + } + fmt.Println() + for _, col := range cols { + fmt.Printf("%s ", strings.Repeat("-", lens[col])) + } + fmt.Println() + // And finally print the table data + for _, z := range zones { + for _, col := range cols { + fmt.Printf("%s%s ", z[col], strings.Repeat(" ", lens[col]-len(z[col]))) + } + fmt.Println() + } + +} + +func checkEnv() error { + if api.APIKey == "" { + return errors.New("API key not defined") + } + if api.APIEmail == "" { + return errors.New("API email not defined") + } + return nil +} + +// Utility function to check if CLI flags were given. +func checkFlags(c *cli.Context, flags ...string) error { + for _, flag := range flags { + if c.String(flag) == "" { + cli.ShowSubcommandHelp(c) + return fmt.Errorf("%s not specified", flag) + } + } + return nil +} + +func ips(*cli.Context) { + ips, _ := cloudflare.IPs() + fmt.Println("IPv4 ranges:") + for _, r := range ips.IPv4CIDRs { + fmt.Println(" ", r) + } + fmt.Println() + fmt.Println("IPv6 ranges:") + for _, r := range ips.IPv6CIDRs { + fmt.Println(" ", r) + } +} + +func userInfo(*cli.Context) { + if err := checkEnv(); err != nil { + fmt.Println(err) + return + } + user, err := api.UserDetails() + if err != nil { + fmt.Println(err) + return + } + var output []table + output = append(output, table{ + "ID": user.ID, + "Email": user.Email, + "Username": user.Username, + "Name": user.FirstName + " " + user.LastName, + "2FA": fmt.Sprintf("%t", user.TwoFA), + }) + makeTable(output, "ID", "Email", "Username", "Name", "2FA") +} + +func userUpdate(*cli.Context) { +} + +func zoneList(c *cli.Context) { + if err := checkEnv(); err != nil { + fmt.Println(err) + return + } + zones, err := api.ListZones() + if err != nil { + fmt.Println(err) + return + } + var output []table + for _, z := range zones { + output = append(output, table{ + "ID": z.ID, + "Name": z.Name, + "Plan": z.Plan.LegacyID, + "Status": z.Status, + }) + } + makeTable(output, "ID", "Name", "Plan", "Status") +} + +func zoneInfo(c *cli.Context) { + if err := checkEnv(); err != nil { + fmt.Println(err) + return + } + var zone string + if len(c.Args()) > 0 { + zone = c.Args()[0] + } else if c.String("zone") != "" { + zone = c.String("zone") + } else { + cli.ShowSubcommandHelp(c) + return + } + zones, err := api.ListZones(zone) + if err != nil { + fmt.Println(err) + return + } + var output []table + for _, z := range zones { + output = append(output, table{ + "ID": z.ID, + "Zone": z.Name, + "Plan": z.Plan.LegacyID, + "Status": z.Status, + "Name Servers": strings.Join(z.NameServers, ", "), + "Paused": fmt.Sprintf("%t", z.Paused), + "Type": z.Type, + }) + } + makeTable(output, "ID", "Zone", "Plan", "Status", "Name Servers", "Paused", "Type") +} + +func zonePlan(*cli.Context) { +} + +func zoneSettings(*cli.Context) { +} + +func zoneRecords(c *cli.Context) { + if err := checkEnv(); err != nil { + fmt.Println(err) + return + } + var zone string + if len(c.Args()) > 0 { + zone = c.Args()[0] + } else if c.String("zone") != "" { + zone = c.String("zone") + } else { + cli.ShowSubcommandHelp(c) + return + } + + zoneID, err := api.ZoneIDByName(zone) + if err != nil { + fmt.Println(err) + return + } + + // Create a an empty record for searching for records + rr := cloudflare.DNSRecord{} + var records []cloudflare.DNSRecord + if c.String("id") != "" { + rec, err := api.DNSRecord(zoneID, c.String("id")) + if err != nil { + fmt.Println(err) + return + } + records = append(records, rec) + } else { + if c.String("name") != "" { + rr.Name = c.String("name") + } + if c.String("content") != "" { + rr.Name = c.String("content") + } + var err error + records, err = api.DNSRecords(zoneID, rr) + if err != nil { + fmt.Println(err) + return + } + } + var output []table + for _, r := range records { + switch r.Type { + case "MX": + r.Content = fmt.Sprintf("%d %s", r.Priority, r.Content) + case "SRV": + dp := reflect.ValueOf(r.Data).Interface().(map[string]interface{}) + r.Content = fmt.Sprintf("%.f %s", dp["priority"], r.Content) + // CloudFlare's API, annoyingly, automatically prepends the weight + // and port into content, separated by tabs. + // XXX: File this as a bug. LOC doesn't do this. + r.Content = strings.Replace(r.Content, "\t", " ", -1) + } + output = append(output, table{ + "ID": r.ID, + "Type": r.Type, + "Name": r.Name, + "Content": r.Content, + "Proxied": fmt.Sprintf("%t", r.Proxied), + "TTL": fmt.Sprintf("%d", r.TTL), + }) + } + makeTable(output, "ID", "Type", "Name", "Content", "Proxied", "TTL") +} + +func dnsCreate(c *cli.Context) { + if err := checkEnv(); err != nil { + fmt.Println(err) + return + } + if err := checkFlags(c, "zone", "name", "type", "content"); err != nil { + return + } + zone := c.String("zone") + name := c.String("name") + rtype := c.String("type") + content := c.String("content") + ttl := c.Int("ttl") + proxy := c.Bool("proxy") + + zoneID, err := api.ZoneIDByName(zone) + if err != nil { + fmt.Println(err) + return + } + + record := cloudflare.DNSRecord{ + Name: name, + Type: strings.ToUpper(rtype), + Content: content, + TTL: ttl, + Proxied: proxy, + } + err = api.CreateDNSRecord(zoneID, record) + if err != nil { + fmt.Println("Error creating DNS record:", err) + } +} + +func dnsCreateOrUpdate(c *cli.Context) { + if err := checkEnv(); err != nil { + fmt.Println(err) + return + } + if err := checkFlags(c, "zone", "name", "type", "content"); err != nil { + return + } + zone := c.String("zone") + name := c.String("name") + rtype := strings.ToUpper(c.String("type")) + content := c.String("content") + ttl := c.Int("ttl") + proxy := c.Bool("proxy") + + zoneID, err := api.ZoneIDByName(zone) + if err != nil { + fmt.Println(err) + return + } + + // Look for an existing record + rr := cloudflare.DNSRecord{ + Name: name + "." + zone, + } + records, err := api.DNSRecords(zoneID, rr) + if err != nil { + fmt.Println(err) + return + } + + if len(records) > 0 { + // Record exists - find the ID and update it. + // This is imprecise without knowing the original content; if a label + // has multiple RRs we'll just update the first one. + for _, r := range records { + if r.Type == rtype { + rr.ID = r.ID + rr.Type = r.Type + rr.Content = content + rr.TTL = ttl + rr.Proxied = proxy + err := api.UpdateDNSRecord(zoneID, r.ID, rr) + if err != nil { + fmt.Println("Error updating DNS record:", err) + } + } + } + } else { + // Record doesn't exist - create it + rr.Type = rtype + rr.Content = content + rr.TTL = ttl + rr.Proxied = proxy + err := api.CreateDNSRecord(zoneID, rr) + if err != nil { + fmt.Println("Error creating DNS record:", err) + } + } +} + +func dnsUpdate(c *cli.Context) { + if err := checkEnv(); err != nil { + fmt.Println(err) + return + } + if err := checkFlags(c, "zone", "id"); err != nil { + return + } + zone := c.String("zone") + recordID := c.String("id") + content := c.String("content") + ttl := c.Int("ttl") + proxy := c.Bool("proxy") + + zoneID, err := api.ZoneIDByName(zone) + if err != nil { + fmt.Println(err) + return + } + + record := cloudflare.DNSRecord{ + ID: recordID, + Content: content, + TTL: ttl, + Proxied: proxy, + } + err = api.UpdateDNSRecord(zoneID, recordID, record) + if err != nil { + fmt.Println("Error updating DNS record:", err) + } +} + +func dnsDelete(c *cli.Context) { + if err := checkEnv(); err != nil { + fmt.Println(err) + return + } + if err := checkFlags(c, "zone", "id"); err != nil { + return + } + zone := c.String("zone") + recordID := c.String("id") + + zoneID, err := api.ZoneIDByName(zone) + if err != nil { + fmt.Println(err) + return + } + + err = api.DeleteDNSRecord(zoneID, recordID) + if err != nil { + fmt.Println("Error deleting DNS record:", err) + } +} + +func zoneCerts(*cli.Context) { +} + +func zoneKeyless(*cli.Context) { +} + +func zoneRailgun(*cli.Context) { +} + +func pageRules(c *cli.Context) { + if err := checkEnv(); err != nil { + fmt.Println(err) + return + } + if err := checkFlags(c, "zone"); err != nil { + return + } + zone := c.String("zone") + + zoneID, err := api.ZoneIDByName(zone) + if err != nil { + fmt.Println(err) + return + } + + rules, err := api.ListPageRules(zoneID) + if err != nil { + fmt.Println(err) + return + } + fmt.Printf("%3s %-32s %-8s %s\n", "Pri", "ID", "Status", "URL") + for _, r := range rules { + var settings []string + fmt.Printf("%3d %s %-8s %s\n", r.Priority, r.ID, r.Status, r.Targets[0].Constraint.Value) + for _, a := range r.Actions { + v := reflect.ValueOf(a.Value) + var s string + switch a.Value.(type) { + case int: + s = fmt.Sprintf("%s: %d", cloudflare.PageRuleActions[a.ID], v.Int()) + case float64: + s = fmt.Sprintf("%s: %.f", cloudflare.PageRuleActions[a.ID], v.Float()) + case map[string]interface{}: + vmap := a.Value.(map[string]interface{}) + s = fmt.Sprintf("%s: %.f - %s", cloudflare.PageRuleActions[a.ID], vmap["status_code"], vmap["url"]) + case nil: + s = fmt.Sprintf("%s", cloudflare.PageRuleActions[a.ID]) + default: + s = fmt.Sprintf("%s: %s", cloudflare.PageRuleActions[a.ID], strings.Title(strings.Replace(v.String(), "_", " ", -1))) + } + settings = append(settings, s) + } + fmt.Println(" ", strings.Join(settings, ", ")) + } +} + +func railgun(*cli.Context) { +} + +func main() { + api = cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL")) + + app := cli.NewApp() + app.Name = "flarectl" + app.Usage = "CloudFlare CLI" + app.Version = "2016.4.0" + app.Commands = []cli.Command{ + { + Name: "ips", + Aliases: []string{"i"}, + Action: ips, + Usage: "Print CloudFlare IP ranges", + }, + { + Name: "user", + Aliases: []string{"u"}, + Usage: "User information", + Subcommands: []cli.Command{ + { + Name: "info", + Aliases: []string{"i"}, + Action: userInfo, + Usage: "User details", + }, + { + Name: "update", + Aliases: []string{"u"}, + Action: userUpdate, + Usage: "Update user details", + }, + }, + }, + + { + Name: "zone", + Aliases: []string{"z"}, + Usage: "Zone information", + Subcommands: []cli.Command{ + { + Name: "list", + Aliases: []string{"l"}, + Action: zoneList, + Usage: "List all zones on an account", + }, + { + Name: "info", + Aliases: []string{"i"}, + Action: zoneInfo, + Usage: "Information on one zone", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "zone", + Usage: "zone name", + }, + }, + }, + { + Name: "plan", + Aliases: []string{"p"}, + Action: zonePlan, + Usage: "Plan information for one zone", + }, + { + Name: "settings", + Aliases: []string{"s"}, + Action: zoneSettings, + Usage: "Settings for one zone", + }, + { + Name: "dns", + Aliases: []string{"d"}, + Action: zoneRecords, + Usage: "DNS records for a zone", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "zone", + Usage: "zone name", + }, + }, + }, + { + Name: "railgun", + Aliases: []string{"r"}, + Action: zoneRailgun, + Usage: "Railguns for a zone", + }, + { + Name: "certs", + Aliases: []string{"c"}, + Action: zoneCerts, + Usage: "Custom SSL certificates for a zone", + }, + { + Name: "keyless", + Aliases: []string{"k"}, + Action: zoneKeyless, + Usage: "Keyless SSL for a zone", + }, + }, + }, + + { + Name: "dns", + Aliases: []string{"d"}, + Usage: "DNS records", + Subcommands: []cli.Command{ + { + Name: "list", + Aliases: []string{"l"}, + Action: zoneRecords, + Usage: "List DNS records for a zone", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "record id", + }, + cli.StringFlag{ + Name: "zone", + Usage: "zone name", + }, + cli.StringFlag{ + Name: "name", + Usage: "record name", + }, + cli.StringFlag{ + Name: "content", + Usage: "record content", + }, + }, + }, + { + Name: "create", + Aliases: []string{"c"}, + Action: dnsCreate, + Usage: "Create a DNS record", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "zone", + Usage: "zone name", + }, + cli.StringFlag{ + Name: "name", + Usage: "record name", + }, + cli.StringFlag{ + Name: "type", + Usage: "record type", + }, + cli.StringFlag{ + Name: "content", + Usage: "record content", + }, + cli.IntFlag{ + Name: "ttl", + Usage: "TTL (1 = automatic)", + Value: 1, + }, + cli.BoolFlag{ + Name: "proxy", + Usage: "proxy through CloudFlare (orange cloud)", + }, + }, + }, + { + Name: "update", + Aliases: []string{"u"}, + Action: dnsUpdate, + Usage: "Update a DNS record", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "zone", + Usage: "zone name", + }, + cli.StringFlag{ + Name: "id", + Usage: "record id", + }, + cli.StringFlag{ + Name: "content", + Usage: "record content", + }, + cli.IntFlag{ + Name: "ttl", + Usage: "TTL (1 = automatic)", + Value: 1, + }, + cli.BoolFlag{ + Name: "proxy", + Usage: "proxy through CloudFlare (orange cloud)", + }, + }, + }, + { + Name: "create-or-update", + Aliases: []string{"o"}, + Action: dnsCreateOrUpdate, + Usage: "Create a DNS record, or update if it exists", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "zone", + Usage: "zone name", + }, + cli.StringFlag{ + Name: "name", + Usage: "record name", + }, + cli.StringFlag{ + Name: "content", + Usage: "record content", + }, + cli.StringFlag{ + Name: "type", + Usage: "record type", + }, + cli.IntFlag{ + Name: "ttl", + Usage: "TTL (1 = automatic)", + Value: 1, + }, + cli.BoolFlag{ + Name: "proxy", + Usage: "proxy through CloudFlare (orange cloud)", + }, + }, + }, + { + Name: "delete", + Aliases: []string{"d"}, + Action: dnsDelete, + Usage: "Delete a DNS record", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "zone", + Usage: "zone name", + }, + cli.StringFlag{ + Name: "id", + Usage: "record id", + }, + }, + }, + }, + }, + + { + Name: "pagerules", + Aliases: []string{"p"}, + Usage: "Page Rules", + Subcommands: []cli.Command{ + { + Name: "list", + Aliases: []string{"l"}, + Action: pageRules, + Usage: "List Page Rules for a zone", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "zone", + Usage: "zone name", + }, + }, + }, + }, + }, + + { + Name: "railgun", + Aliases: []string{"r"}, + Usage: "Railgun information", + Action: railgun, + }, + } + app.Run(os.Args) +} diff --git a/vendor/github.com/mitchellh/cloudflare-go/cpage.go b/vendor/github.com/mitchellh/cloudflare-go/cpage.go new file mode 100644 index 000000000..73ea3e838 --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/cpage.go @@ -0,0 +1,10 @@ +package cloudflare + +// https://api.cloudflare.com/#custom-pages-for-a-zone-available-custom-pages +// GET /zones/:zone_identifier/custom_pages + +// https://api.cloudflare.com/#custom-pages-for-a-zone-custom-page-details +// GET /zones/:zone_identifier/custom_pages/:identifier + +// https://api.cloudflare.com/#custom-pages-for-a-zone-update-custom-page-url +// PUT /zones/:zone_identifier/custom_pages/:identifier diff --git a/vendor/github.com/mitchellh/cloudflare-go/dns.go b/vendor/github.com/mitchellh/cloudflare-go/dns.go new file mode 100644 index 000000000..b51c9c0f4 --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/dns.go @@ -0,0 +1,134 @@ +package cloudflare + +import ( + "encoding/json" + "net/url" + + "github.com/pkg/errors" +) + +/* +Create a DNS record. + +API reference: + https://api.cloudflare.com/#dns-records-for-a-zone-create-dns-record + POST /zones/:zone_identifier/dns_records +*/ +func (api *API) CreateDNSRecord(zoneID string, rr DNSRecord) (DNSRecord, error) { + uri := "/zones/" + zoneID + "/dns_records" + res, err := api.makeRequest("POST", uri, rr) + if err != nil { + return DNSRecord{}, errors.Wrap(err, errMakeRequestError) + } + var r DNSRecordResponse + err = json.Unmarshal(res, &r) + if err != nil { + return DNSRecord{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +/* +Fetches DNS records for a zone. + +API reference: + https://api.cloudflare.com/#dns-records-for-a-zone-list-dns-records + GET /zones/:zone_identifier/dns_records +*/ +func (api *API) DNSRecords(zoneID string, rr DNSRecord) ([]DNSRecord, error) { + // Construct a query string + v := url.Values{} + if rr.Name != "" { + v.Set("name", rr.Name) + } + if rr.Type != "" { + v.Set("type", rr.Type) + } + if rr.Content != "" { + v.Set("content", rr.Content) + } + var query string + if len(v) > 0 { + query = "?" + v.Encode() + } + uri := "/zones/" + zoneID + "/dns_records" + query + res, err := api.makeRequest("GET", uri, nil) + if err != nil { + return []DNSRecord{}, errors.Wrap(err, errMakeRequestError) + } + var r DNSListResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []DNSRecord{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +/* +Fetches a single DNS record. + +API reference: + https://api.cloudflare.com/#dns-records-for-a-zone-dns-record-details + GET /zones/:zone_identifier/dns_records/:identifier +*/ +func (api *API) DNSRecord(zoneID, recordID string) (DNSRecord, error) { + uri := "/zones/" + zoneID + "/dns_records/" + recordID + res, err := api.makeRequest("GET", uri, nil) + if err != nil { + return DNSRecord{}, errors.Wrap(err, errMakeRequestError) + } + var r DNSRecordResponse + err = json.Unmarshal(res, &r) + if err != nil { + return DNSRecord{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +/* +Change a DNS record. + +API reference: + https://api.cloudflare.com/#dns-records-for-a-zone-update-dns-record + PUT /zones/:zone_identifier/dns_records/:identifier +*/ +func (api *API) UpdateDNSRecord(zoneID, recordID string, rr DNSRecord) error { + rec, err := api.DNSRecord(zoneID, recordID) + if err != nil { + return err + } + rr.Name = rec.Name + rr.Type = rec.Type + uri := "/zones/" + zoneID + "/dns_records/" + recordID + res, err := api.makeRequest("PUT", uri, rr) + if err != nil { + return errors.Wrap(err, errMakeRequestError) + } + var r DNSRecordResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} + +/* +Delete a DNS record. + +API reference: + https://api.cloudflare.com/#dns-records-for-a-zone-delete-dns-record + DELETE /zones/:zone_identifier/dns_records/:identifier +*/ +func (api *API) DeleteDNSRecord(zoneID, recordID string) error { + uri := "/zones/" + zoneID + "/dns_records/" + recordID + res, err := api.makeRequest("DELETE", uri, nil) + if err != nil { + return errors.Wrap(err, errMakeRequestError) + } + var r DNSRecordResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} diff --git a/vendor/github.com/mitchellh/cloudflare-go/ips.go b/vendor/github.com/mitchellh/cloudflare-go/ips.go new file mode 100644 index 000000000..52f79f6a5 --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/ips.go @@ -0,0 +1,36 @@ +package cloudflare + +import ( + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/pkg/errors" +) + +/* +IPs gets a list of CloudFlare's IP ranges + +This does not require logging in to the API. + +API reference: + https://api.cloudflare.com/#cloudflare-ips + GET /client/v4/ips +*/ +func IPs() (IPRanges, error) { + resp, err := http.Get(apiURL + "/ips") + if err != nil { + return IPRanges{}, errors.Wrap(err, "HTTP request failed") + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return IPRanges{}, errors.Wrap(err, "Response body could not be read") + } + var r IPsResponse + err = json.Unmarshal(body, &r) + if err != nil { + return IPRanges{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} diff --git a/vendor/github.com/mitchellh/cloudflare-go/keyless.go b/vendor/github.com/mitchellh/cloudflare-go/keyless.go new file mode 100644 index 000000000..f12c3910c --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/keyless.go @@ -0,0 +1,26 @@ +package cloudflare + +// https://api.cloudflare.com/#keyless-ssl-for-a-zone-create-a-keyless-ssl-configuration +// POST /zones/:zone_identifier/keyless_certificates +func (c *API) CreateKeyless() { +} + +// https://api.cloudflare.com/#keyless-ssl-for-a-zone-list-keyless-ssls +// GET /zones/:zone_identifier/keyless_certificates +func (c *API) ListKeyless() { +} + +// https://api.cloudflare.com/#keyless-ssl-for-a-zone-keyless-ssl-details +// GET /zones/:zone_identifier/keyless_certificates/:identifier +func (c *API) Keyless() { +} + +// https://api.cloudflare.com/#keyless-ssl-for-a-zone-update-keyless-configuration +// PATCH /zones/:zone_identifier/keyless_certificates/:identifier +func (c *API) UpdateKeyless() { +} + +// https://api.cloudflare.com/#keyless-ssl-for-a-zone-delete-keyless-configuration +// DELETE /zones/:zone_identifier/keyless_certificates/:identifier +func (c *API) DeleteKeyless() { +} diff --git a/vendor/github.com/mitchellh/cloudflare-go/pagerules.go b/vendor/github.com/mitchellh/cloudflare-go/pagerules.go new file mode 100644 index 000000000..fae180d71 --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/pagerules.go @@ -0,0 +1,231 @@ +package cloudflare + +import ( + "encoding/json" + + "github.com/pkg/errors" +) + +/* +PageRuleTarget is the target to evaluate on a request. + +Currently Target must always be "url" and Operator must be "matches". Value +is the URL pattern to match against. +*/ +type PageRuleTarget struct { + Target string `json:"target"` + Constraint struct { + Operator string `json:"operator"` + Value string `json:"value"` + } `json:"constraint"` +} + +/* +PageRuleAction is the action to take when the target is matched. + +Valid IDs are: + + always_online + always_use_https + browser_cache_ttl + browser_check + cache_level + disable_apps + disable_performance + disable_security + edge_cache_ttl + email_obfuscation + forwarding_url + ip_geolocation + mirage + railgun + rocket_loader + security_level + server_side_exclude + smart_errors + ssl + waf +*/ +type PageRuleAction struct { + ID string `json:"id"` + Value interface{} `json:"value"` +} + +// PageRuleActions maps API action IDs to human-readable strings +var PageRuleActions = map[string]string{ + "always_online": "Always Online", // Value of type string + "always_use_https": "Always Use HTTPS", // Value of type interface{} + "browser_cache_ttl": "Browser Cache TTL", // Value of type int + "browser_check": "Browser Integrity Check", // Value of type string + "cache_level": "Cache Level", // Value of type string + "disable_apps": "Disable Apps", // Value of type interface{} + "disable_performance": "Disable Performance", // Value of type interface{} + "disable_security": "Disable Security", // Value of type interface{} + "edge_cache_ttl": "Edge Cache TTL", // Value of type int + "email_obfuscation": "Email Obfuscation", // Value of type string + "forwarding_url": "Forwarding URL", // Value of type map[string]interface + "ip_geolocation": "IP Geolocation Header", // Value of type string + "mirage": "Mirage", // Value of type string + "railgun": "Railgun", // Value of type string + "rocket_loader": "Rocker Loader", // Value of type string + "security_level": "Security Level", // Value of type string + "server_side_exclude": "Server Side Excludes", // Value of type string + "smart_errors": "Smart Errors", // Value of type string + "ssl": "SSL", // Value of type string + "waf": "Web Application Firewall", // Value of type string +} + +// PageRule describes a Page Rule. +type PageRule struct { + ID string `json:"id,omitempty"` + Targets []PageRuleTarget `json:"targets"` + Actions []PageRuleAction `json:"actions"` + Priority int `json:"priority"` + Status string `json:"status"` // can be: active, paused + ModifiedOn string `json:"modified_on,omitempty"` + CreatedOn string `json:"created_on,omitempty"` +} + +// PageRuleDetailResponse is the API response, containing a single PageRule. +type PageRuleDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result PageRule `json:"result"` +} + +// PageRulesResponse is the API response, containing an array of PageRules. +type PageRulesResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result []PageRule `json:"result"` +} + +/* +CreatePageRule creates a new Page Rule for a zone. + +API reference: + https://api.cloudflare.com/#page-rules-for-a-zone-create-a-page-rule + POST /zones/:zone_identifier/pagerules +*/ +func (api *API) CreatePageRule(zoneID string, rule PageRule) error { + uri := "/zones/" + zoneID + "/pagerules" + res, err := api.makeRequest("POST", uri, rule) + if err != nil { + return errors.Wrap(err, errMakeRequestError) + } + var r PageRuleDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} + +/* +ListPageRules returns all Page Rules for a zone. + +API reference: + https://api.cloudflare.com/#page-rules-for-a-zone-list-page-rules + GET /zones/:zone_identifier/pagerules +*/ +func (api *API) ListPageRules(zoneID string) ([]PageRule, error) { + uri := "/zones/" + zoneID + "/pagerules" + res, err := api.makeRequest("GET", uri, nil) + if err != nil { + return []PageRule{}, errors.Wrap(err, errMakeRequestError) + } + var r PageRulesResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []PageRule{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +/* +PageRule fetches detail about one Page Rule for a zone. + +API reference: + https://api.cloudflare.com/#page-rules-for-a-zone-page-rule-details + GET /zones/:zone_identifier/pagerules/:identifier +*/ +func (api *API) PageRule(zoneID, ruleID string) (PageRule, error) { + uri := "/zones/" + zoneID + "/pagerules/" + ruleID + res, err := api.makeRequest("GET", uri, nil) + if err != nil { + return PageRule{}, errors.Wrap(err, errMakeRequestError) + } + var r PageRuleDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return PageRule{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +/* +ChangePageRule lets change individual settings for a Page Rule. This is in +contrast to UpdatePageRule which replaces the entire Page Rule. + +API reference: + https://api.cloudflare.com/#page-rules-for-a-zone-change-a-page-rule + PATCH /zones/:zone_identifier/pagerules/:identifier +*/ +func (api *API) ChangePageRule(zoneID, ruleID string, rule PageRule) error { + uri := "/zones/" + zoneID + "/pagerules/" + ruleID + res, err := api.makeRequest("PATCH", uri, rule) + if err != nil { + return errors.Wrap(err, errMakeRequestError) + } + var r PageRuleDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} + +/* +UpdatePageRule lets you replace a Page Rule. This is in contrast to +ChangePageRule which lets you change individual settings. + +API reference: + https://api.cloudflare.com/#page-rules-for-a-zone-update-a-page-rule + PUT /zones/:zone_identifier/pagerules/:identifier +*/ +func (api *API) UpdatePageRule(zoneID, ruleID string, rule PageRule) error { + uri := "/zones/" + zoneID + "/pagerules/" + ruleID + res, err := api.makeRequest("PUT", uri, nil) + if err != nil { + return errors.Wrap(err, errMakeRequestError) + } + var r PageRuleDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} + +/* +DeletePageRule deletes a Page Rule for a zone. + +API reference: + https://api.cloudflare.com/#page-rules-for-a-zone-delete-a-page-rule + DELETE /zones/:zone_identifier/pagerules/:identifier +*/ +func (api *API) DeletePageRule(zoneID, ruleID string) error { + uri := "/zones/" + zoneID + "/pagerules/" + ruleID + res, err := api.makeRequest("DELETE", uri, nil) + if err != nil { + return errors.Wrap(err, errMakeRequestError) + } + var r PageRuleDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} diff --git a/vendor/github.com/mitchellh/cloudflare-go/railgun.go b/vendor/github.com/mitchellh/cloudflare-go/railgun.go new file mode 100644 index 000000000..a4769d206 --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/railgun.go @@ -0,0 +1,37 @@ +package cloudflare + +// Railgun + +// https://api.cloudflare.com/#railgun-create-railgun +// POST /railguns +func (c *API) CreateRailgun() { +} + +// https://api.cloudflare.com/#railgun-railgun-details +// GET /railguns/:identifier + +// https://api.cloudflare.com/#railgun-get-zones-connected-to-a-railgun +// GET /railguns/:identifier/zones + +// https://api.cloudflare.com/#railgun-enable-or-disable-a-railgun +// PATCH /railguns/:identifier + +// https://api.cloudflare.com/#railgun-delete-railgun +// DELETE /railguns/:identifier + +// Zone railgun info + +// https://api.cloudflare.com/#railguns-for-a-zone-get-available-railguns +// GET /zones/:zone_identifier/railguns +func (c *API) Railguns() { +} + +// https://api.cloudflare.com/#railguns-for-a-zone-get-railgun-details +// GET /zones/:zone_identifier/railguns/:identifier +func (c *API) Railgun() { +} + +// https://api.cloudflare.com/#railguns-for-a-zone-connect-or-disconnect-a-railgun +// PATCH /zones/:zone_identifier/railguns/:identifier +func (c *API) ZoneRailgun(connected bool) { +} diff --git a/vendor/github.com/mitchellh/cloudflare-go/ssl.go b/vendor/github.com/mitchellh/cloudflare-go/ssl.go new file mode 100644 index 000000000..8dfc74f99 --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/ssl.go @@ -0,0 +1,31 @@ +package cloudflare + +// https://api.cloudflare.com/#custom-ssl-for-a-zone-create-ssl-configuration +// POST /zones/:zone_identifier/custom_certificates +func (c *API) CreateSSL() { +} + +// https://api.cloudflare.com/#custom-ssl-for-a-zone-list-ssl-configurations +// GET /zones/:zone_identifier/custom_certificates +func (c *API) ListSSL() { +} + +// https://api.cloudflare.com/#custom-ssl-for-a-zone-ssl-configuration-details +// GET /zones/:zone_identifier/custom_certificates/:identifier +func (c *API) SSLDetails() { +} + +// https://api.cloudflare.com/#custom-ssl-for-a-zone-update-ssl-configuration +// PATCH /zones/:zone_identifier/custom_certificates/:identifier +func (c *API) UpdateSSL() { +} + +// https://api.cloudflare.com/#custom-ssl-for-a-zone-re-prioritize-ssl-certificates +// PUT /zones/:zone_identifier/custom_certificates/prioritize +func (c *API) ReprioSSL() { +} + +// https://api.cloudflare.com/#custom-ssl-for-a-zone-delete-an-ssl-certificate +// DELETE /zones/:zone_identifier/custom_certificates/:identifier +func (c *API) DeleteSSL() { +} diff --git a/vendor/github.com/mitchellh/cloudflare-go/user.go b/vendor/github.com/mitchellh/cloudflare-go/user.go new file mode 100644 index 000000000..8c2344453 --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/user.go @@ -0,0 +1,35 @@ +package cloudflare + +import ( + "encoding/json" + + "github.com/pkg/errors" +) + +/* +Information about the logged-in user. + +API reference: https://api.cloudflare.com/#user-user-details +*/ +func (api API) UserDetails() (User, error) { + var r UserResponse + res, err := api.makeRequest("GET", "/user", nil) + if err != nil { + return User{}, errors.Wrap(err, errMakeRequestError) + } + err = json.Unmarshal(res, &r) + if err != nil { + return User{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +/* +Update user properties. + +API reference: https://api.cloudflare.com/#user-update-user +*/ +func (api API) UpdateUser() (User, error) { + // api.makeRequest("PATCH", "/user", user) + return User{}, nil +} diff --git a/vendor/github.com/mitchellh/cloudflare-go/waf.go b/vendor/github.com/mitchellh/cloudflare-go/waf.go new file mode 100644 index 000000000..f3dbe5cfe --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/waf.go @@ -0,0 +1,55 @@ +package cloudflare + +import ( + "encoding/json" + + "github.com/pkg/errors" +) + +func (api *API) ListWAFPackages(zoneID string) ([]WAFPackage, error) { + var p WAFPackagesResponse + var packages []WAFPackage + var res []byte + var err error + uri := "/zones/" + zoneID + "/firewall/waf/packages" + res, err = api.makeRequest("GET", uri, nil) + if err != nil { + return []WAFPackage{}, errors.Wrap(err, errMakeRequestError) + } + err = json.Unmarshal(res, &p) + if err != nil { + return []WAFPackage{}, errors.Wrap(err, errUnmarshalError) + } + if !p.Success { + // TODO: Provide an actual error message instead of always returning nil + return []WAFPackage{}, err + } + for pi, _ := range p.Result { + packages = append(packages, p.Result[pi]) + } + return packages, nil +} + +func (api *API) ListWAFRules(zoneID, packageID string) ([]WAFRule, error) { + var r WAFRulesResponse + var rules []WAFRule + var res []byte + var err error + uri := "/zones/" + zoneID + "/firewall/waf/packages/" + packageID + "/rules" + res, err = api.makeRequest("GET", uri, nil) + if err != nil { + return []WAFRule{}, errors.Wrap(err, errMakeRequestError) + } + err = json.Unmarshal(res, &r) + if err != nil { + return []WAFRule{}, errors.Wrap(err, errUnmarshalError) + } + if !r.Success { + // TODO: Provide an actual error message instead of always returning nil + return []WAFRule{}, err + } + for ri, _ := range r.Result { + rules = append(rules, r.Result[ri]) + } + return rules, nil +} diff --git a/vendor/github.com/mitchellh/cloudflare-go/zone.go b/vendor/github.com/mitchellh/cloudflare-go/zone.go new file mode 100644 index 000000000..7a681c5de --- /dev/null +++ b/vendor/github.com/mitchellh/cloudflare-go/zone.go @@ -0,0 +1,145 @@ +package cloudflare + +import ( + "encoding/json" + "net/url" + + "github.com/pkg/errors" +) + +/* +Creates a zone on an account. + +API reference: https://api.cloudflare.com/#zone-create-a-zone +*/ +func (api *API) CreateZone(z Zone) { + // res, err := api.makeRequest("POST", "/zones", z) +} + +/* +List zones on an account. Optionally takes a list of zones to filter results. + +API reference: https://api.cloudflare.com/#zone-list-zones +*/ +func (api *API) ListZones(z ...string) ([]Zone, error) { + v := url.Values{} + var res []byte + var r ZoneResponse + var zones []Zone + var err error + if len(z) > 0 { + for _, zone := range z { + v.Set("name", zone) + res, err = api.makeRequest("GET", "/zones?"+v.Encode(), nil) + if err != nil { + return []Zone{}, errors.Wrap(err, errMakeRequestError) + } + err = json.Unmarshal(res, &r) + if err != nil { + return []Zone{}, errors.Wrap(err, errUnmarshalError) + } + if !r.Success { + // TODO: Provide an actual error message instead of always returning nil + return []Zone{}, err + } + for zi, _ := range r.Result { + zones = append(zones, r.Result[zi]) + } + } + } else { + res, err = api.makeRequest("GET", "/zones", nil) + if err != nil { + return []Zone{}, errors.Wrap(err, errMakeRequestError) + } + err = json.Unmarshal(res, &r) + if err != nil { + return []Zone{}, errors.Wrap(err, errUnmarshalError) + } + zones = r.Result + } + + return zones, nil +} + +/* +Fetches information about a zone. + + + https://api.cloudflare.com/#zone-zone-details + GET /zones/:id +*/ +func (api *API) ZoneDetails(z Zone) { + // XXX: Should we make the user get the zone ID themselves with ListZones, or do the hard work here? + // ListZones gives the same information as this endpoint anyway so perhaps this is of limited use? + // Maybe for users who already know the ID or fetched it in another call. + type result struct { + Response + Result Zone `json:"result"` + } + // If z has an ID then query for that directly, else call ListZones to + // fetch by name. + // var zone Zone + if z.ID != "" { + // res, _ := makeRequest(c, "GET", "/zones/"+z.ID, nil) + // zone = res.Result + } else { + // zones, err := ListZones(c, z.Name) + // if err != nil { + // return + // } + // Only one zone should have been returned + // zone := zones[0] + } +} + +// https://api.cloudflare.com/#zone-edit-zone-properties +// PATCH /zones/:id +func EditZone() { +} + +// https://api.cloudflare.com/#zone-purge-all-files +// DELETE /zones/:id/purge_cache +func (api *API) PurgeEverything(zoneID string) (PurgeCacheResponse, error) { + uri := "/zones/" + zoneID + "/purge_cache" + res, err := api.makeRequest("DELETE", uri, PurgeCacheRequest{true, nil, nil}) + if err != nil { + return PurgeCacheResponse{}, errors.Wrap(err, errMakeRequestError) + } + var r PurgeCacheResponse + err = json.Unmarshal(res, &r) + if err != nil { + return PurgeCacheResponse{}, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +// https://api.cloudflare.com/#zone-purge-individual-files-by-url-and-cache-tags +// DELETE /zones/:id/purge_cache +func (api *API) PurgeCache(zoneID string, pcr PurgeCacheRequest) (PurgeCacheResponse, error) { + uri := "/zones/" + zoneID + "/purge_cache" + res, err := api.makeRequest("DELETE", uri, pcr) + if err != nil { + return PurgeCacheResponse{}, errors.Wrap(err, errMakeRequestError) + } + var r PurgeCacheResponse + err = json.Unmarshal(res, &r) + if err != nil { + return PurgeCacheResponse{}, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +// https://api.cloudflare.com/#zone-delete-a-zone +// DELETE /zones/:id +func DeleteZone() { +} + +// Zone Plan +// https://api.cloudflare.com/#zone-plan-available-plans +// https://api.cloudflare.com/#zone-plan-plan-details + +// Zone Settings +// https://api.cloudflare.com/#zone-settings-for-a-zone-get-all-zone-settings +// e.g. +// https://api.cloudflare.com/#zone-settings-for-a-zone-get-always-online-setting +// https://api.cloudflare.com/#zone-settings-for-a-zone-change-always-online-setting diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore new file mode 100644 index 000000000..daf913b1b --- /dev/null +++ b/vendor/github.com/pkg/errors/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml new file mode 100644 index 000000000..13f087a7d --- /dev/null +++ b/vendor/github.com/pkg/errors/.travis.yml @@ -0,0 +1,10 @@ +language: go +go_import_path: github.com/pkg/errors +go: + - 1.4.3 + - 1.5.4 + - 1.6.1 + - tip + +script: + - go test -v ./... diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 000000000..fafcaafdc --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md new file mode 100644 index 000000000..a9f7d88ef --- /dev/null +++ b/vendor/github.com/pkg/errors/README.md @@ -0,0 +1,52 @@ +# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) + +Package errors implements functions for manipulating errors. + +The traditional error handling idiom in Go is roughly akin to +``` +if err != nil { + return err +} +``` +which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. + +## Adding context to an error + +The errors.Wrap function returns a new error that adds context to the original error. For example +``` +_, err := ioutil.ReadAll(r) +if err != nil { + return errors.Wrap(err, "read failed") +} +``` +In addition, `errors.Wrap` records the file and line where it was called, allowing the programmer to retrieve the path to the original error. + +## Retrieving the cause of an error + +Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to recurse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. +``` +type causer interface { + Cause() error +} +``` +`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: +``` +switch err := errors.Cause(err).(type) { +case *MyError: + // handle specifically +default: + // unknown error +} +``` + +Would you like to know more? Read the [blog post](http://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully). + +## Contributing + +We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. + +Before proposing a change, please discuss your change by raising an issue. + +## Licence + +MIT diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 000000000..7ec1c5dd5 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,248 @@ +// Package errors implements functions for manipulating errors. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// In addition, errors.Wrap records the file and line where it was called, +// allowing the programmer to retrieve the path to the original error. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does nor implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +package errors + +import ( + "errors" + "fmt" + "io" + "os" + "runtime" + "strings" +) + +// location represents a program counter that +// implements the Location() method. +type location uintptr + +func (l location) Location() (string, int) { + pc := uintptr(l) - 1 + fn := runtime.FuncForPC(pc) + if fn == nil { + return "unknown", 0 + } + + file, line := fn.FileLine(pc) + + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(fn.Name(), sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + + return file, line +} + +// New returns an error that formats as the given text. +func New(text string) error { + pc, _, _, _ := runtime.Caller(1) + return struct { + error + location + }{ + errors.New(text), + location(pc), + } +} + +type cause struct { + cause error + message string +} + +func (c cause) Error() string { return c.Message() + ": " + c.Cause().Error() } +func (c cause) Cause() error { return c.cause } +func (c cause) Message() string { return c.message } + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +func Errorf(format string, args ...interface{}) error { + pc, _, _, _ := runtime.Caller(1) + return struct { + error + location + }{ + fmt.Errorf(format, args...), + location(pc), + } +} + +// Wrap returns an error annotating the cause with message. +// If cause is nil, Wrap returns nil. +func Wrap(cause error, message string) error { + if cause == nil { + return nil + } + pc, _, _, _ := runtime.Caller(1) + return wrap(cause, message, pc) +} + +// Wrapf returns an error annotating the cause with the format specifier. +// If cause is nil, Wrapf returns nil. +func Wrapf(cause error, format string, args ...interface{}) error { + if cause == nil { + return nil + } + pc, _, _, _ := runtime.Caller(1) + return wrap(cause, fmt.Sprintf(format, args...), pc) +} + +func wrap(err error, msg string, pc uintptr) error { + return struct { + cause + location + }{ + cause{ + cause: err, + message: msg, + }, + location(pc), + } +} + +type causer interface { + Cause() error +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type Causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} + +// Print prints the error to Stderr. +// If the error implements the Causer interface described in Cause +// Print will recurse into the error's cause. +// If the error implements the inteface: +// +// type Location interface { +// Location() (file string, line int) +// } +// +// Print will also print the file and line of the error. +func Print(err error) { + Fprint(os.Stderr, err) +} + +// Fprint prints the error to the supplied writer. +// The format of the output is the same as Print. +// If err is nil, nothing is printed. +func Fprint(w io.Writer, err error) { + type location interface { + Location() (string, int) + } + type message interface { + Message() string + } + + for err != nil { + if err, ok := err.(location); ok { + file, line := err.Location() + fmt.Fprintf(w, "%s:%d: ", file, line) + } + switch err := err.(type) { + case message: + fmt.Fprintln(w, err.Message()) + default: + fmt.Fprintln(w, err.Error()) + } + + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } +}