CloudFlare as a new provider (#140)

* CloudFlare Provider

* updating glide

* gofmt cloudflare_test.go

* Unset envs to test NewCloudFlareProvider

* More tests

* fix(cloudflare): fix compiler errors resulting from merge

* Typo

* Undo vendor changes

* decrease api calls, fix some nits

* Cloudflare iteration (#2)

* reduce the number of API calls

* match by type and name for record id

* improve coverage and fix the bug with suitable zone

* tests failed due to wrong formatting

* add cloudflare integration to the main

* vendor cloudflare deps

* fix cloudflare zone detection + tests

* fix conflicting test function names
This commit is contained in:
Nick Jüttner 2017-06-16 11:28:13 +02:00 committed by Yerken
parent c1f5179739
commit cb5863344b
45 changed files with 7768 additions and 8 deletions

8
glide.lock generated
View File

@ -1,5 +1,5 @@
hash: 2cc9f8e5b9c63f3e474cb84c7f7d491a46c45ec4f12ad985544535c80834ac63
updated: 2017-06-16T10:35:11.175866087+02:00
hash: 9b0956761f95e461a9cbf8edfac1537ef55005b27f9be0659582b336a227b1c2
updated: 2017-06-16T10:50:06.229451646+02:00
imports:
- name: bitbucket.org/ww/goautoneg
version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675
@ -60,6 +60,8 @@ imports:
version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
subpackages:
- quantile
- name: github.com/cloudflare/cloudflare-go
version: 9d186a5948cb4bb3d1f3343bc6bcae91e7aa6479
- name: github.com/coreos/go-oidc
version: be73733bb8cc830d0205609b95d125215f8e9c70
subpackages:
@ -147,6 +149,8 @@ imports:
version: fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a
subpackages:
- pbutil
- name: github.com/pkg/errors
version: 645ef00459ed84a119197bfb8d8205042c6df63d
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:

View File

@ -49,6 +49,8 @@ import:
version: ~8.0.0
- package: github.com/dgrijalva/jwt-go
version: ~3.0.0
- package: github.com/cloudflare/cloudflare-go
version: ~0.7.3
- package: github.com/digitalocean/godo
version: ~1.1.0
- package: github.com/coreos/go-oidc

10
main.go
View File

@ -110,14 +110,16 @@ func main() {
var p provider.Provider
switch cfg.Provider {
case "google":
p, err = provider.NewGoogleProvider(cfg.GoogleProject, cfg.DomainFilter, cfg.DryRun)
case "aws":
p, err = provider.NewAWSProvider(cfg.DomainFilter, cfg.DryRun)
case "digitalocean":
p, err = provider.NewDigitalOceanProvider(cfg.DomainFilter, cfg.DryRun)
case "azure":
p, err = provider.NewAzureProvider(cfg.AzureConfigFile, cfg.DomainFilter, cfg.AzureResourceGroup, cfg.DryRun)
case "cloudflare":
p, err = provider.NewCloudFlareProvider(cfg.DomainFilter, cfg.DryRun)
case "google":
p, err = provider.NewGoogleProvider(cfg.GoogleProject, cfg.DomainFilter, cfg.DryRun)
case "digitalocean":
p, err = provider.NewDigitalOceanProvider(cfg.DomainFilter, cfg.DryRun)
case "inmemory":
p, err = provider.NewInMemoryProvider(provider.InMemoryWithDomain(cfg.DomainFilter), provider.InMemoryWithLogging()), nil
default:

View File

@ -97,7 +97,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("compatibility", "Process annotation semantics from legacy implementations (optional, options: mate, molecule)").Default(defaultConfig.Compatibility).EnumVar(&cfg.Compatibility, "", "mate", "molecule")
// Flags related to providers
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, google, inmemory, azure, digitalocean)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "google", "digitalocean", "azure", "inmemory")
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, google, azure, cloudflare, digitalocean, inmemory)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "google", "azure", "cloudflare", "digitalocean", "inmemory")
app.Flag("google-project", "When using the Google provider, specify the Google project (required when --provider=google)").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject)
app.Flag("domain-filter", "Limit possible target zones by a domain suffix (optional)").Default(defaultConfig.DomainFilter).StringVar(&cfg.DomainFilter)
app.Flag("azure-config-file", "When using the Azure provider, specify the Azure configuration file (required when --provider=azure").Default(defaultConfig.AzureConfigFile).StringVar(&cfg.AzureConfigFile)

293
provider/cloudflare.go Normal file
View File

@ -0,0 +1,293 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"fmt"
"os"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/cloudflare/cloudflare-go"
"github.com/kubernetes-incubator/external-dns/endpoint"
"github.com/kubernetes-incubator/external-dns/plan"
)
const (
// cloudFlareCreate is a ChangeAction enum value
cloudFlareCreate = "CREATE"
// cloudFlareDelete is a ChangeAction enum value
cloudFlareDelete = "DELETE"
// cloudFlareUpdate is a ChangeAction enum value
cloudFlareUpdate = "UPDATE"
)
// cloudFlareDNS is the subset of the CloudFlare API that we actually use. Add methods as required. Signatures must match exactly.
type cloudFlareDNS interface {
UserDetails() (cloudflare.User, error)
ZoneIDByName(zoneName string) (string, error)
ListZones(zoneID ...string) ([]cloudflare.Zone, error)
DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error)
CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error)
DeleteDNSRecord(zoneID, recordID string) error
UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error
}
type zoneService struct {
service *cloudflare.API
}
func (z zoneService) UserDetails() (cloudflare.User, error) {
return z.service.UserDetails()
}
func (z zoneService) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
return z.service.ListZones(zoneID...)
}
func (z zoneService) ZoneIDByName(zoneName string) (string, error) {
return z.service.ZoneIDByName(zoneName)
}
func (z zoneService) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
return z.service.CreateDNSRecord(zoneID, rr)
}
func (z zoneService) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
return z.service.DNSRecords(zoneID, rr)
}
func (z zoneService) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
return z.service.UpdateDNSRecord(zoneID, recordID, rr)
}
func (z zoneService) DeleteDNSRecord(zoneID, recordID string) error {
return z.service.DeleteDNSRecord(zoneID, recordID)
}
// CloudFlareProvider is an implementation of Provider for CloudFlare DNS.
type CloudFlareProvider struct {
Client cloudFlareDNS
// only consider hosted zones managing domains ending in this suffix
domainFilter string
DryRun bool
}
// cloudFlareChange differentiates between ChangActions
type cloudFlareChange struct {
Action string
ResourceRecordSet cloudflare.DNSRecord
}
// NewCloudFlareProvider initializes a new CloudFlare DNS based Provider.
func NewCloudFlareProvider(domainFilter string, dryRun bool) (*CloudFlareProvider, error) {
// initialize via API email and API key and returns new API object
config, err := cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL"))
if err != nil {
return nil, fmt.Errorf("failed to initialize cloudflare provider: %v", err)
}
provider := &CloudFlareProvider{
//Client: config,
Client: zoneService{config},
domainFilter: domainFilter,
DryRun: dryRun,
}
return provider, nil
}
// Zones returns the list of hosted zones.
func (p *CloudFlareProvider) Zones() ([]cloudflare.Zone, error) {
result := []cloudflare.Zone{}
zones, err := p.Client.ListZones()
if err != nil {
return nil, err
}
for _, zone := range zones {
if strings.HasSuffix(zone.Name, p.domainFilter) {
result = append(result, zone)
}
}
return result, nil
}
// Records returns the list of records.
func (p *CloudFlareProvider) Records() ([]*endpoint.Endpoint, error) {
zones, err := p.Zones()
if err != nil {
return nil, err
}
endpoints := []*endpoint.Endpoint{}
for _, zone := range zones {
records, err := p.Client.DNSRecords(zone.ID, cloudflare.DNSRecord{})
if err != nil {
return nil, err
}
for _, r := range records {
switch r.Type {
case "A", "CNAME", "TXT":
break
default:
continue
}
endpoints = append(endpoints, endpoint.NewEndpoint(r.Name, r.Content, r.Type))
}
}
return endpoints, nil
}
// ApplyChanges applies a given set of changes in a given zone.
func (p *CloudFlareProvider) ApplyChanges(changes *plan.Changes) error {
combinedChanges := make([]*cloudFlareChange, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete))
combinedChanges = append(combinedChanges, newCloudFlareChanges(cloudFlareCreate, changes.Create)...)
combinedChanges = append(combinedChanges, newCloudFlareChanges(cloudFlareUpdate, changes.UpdateNew)...)
combinedChanges = append(combinedChanges, newCloudFlareChanges(cloudFlareDelete, changes.Delete)...)
return p.submitChanges(combinedChanges)
}
// submitChanges takes a zone and a collection of Changes and sends them as a single transaction.
func (p *CloudFlareProvider) submitChanges(changes []*cloudFlareChange) error {
// return early if there is nothing to change
if len(changes) == 0 {
return nil
}
zones, err := p.Zones()
if err != nil {
return err
}
// separate into per-zone change sets to be passed to the API.
changesByZone := cloudflareChangesByZone(zones, changes)
for zoneID, changes := range changesByZone {
records, err := p.Client.DNSRecords(zoneID, cloudflare.DNSRecord{})
if err != nil {
return fmt.Errorf("could not fetch records from zone, %v", err)
}
for _, change := range changes {
logFields := log.Fields{
"record": change.ResourceRecordSet.Name,
"type": change.ResourceRecordSet.Type,
"action": change.Action,
"zone": zoneID,
}
log.WithFields(logFields).Info("Changing record.")
if p.DryRun {
continue
}
recordID := p.getRecordID(records, change.ResourceRecordSet)
switch change.Action {
case cloudFlareCreate:
_, err := p.Client.CreateDNSRecord(zoneID, change.ResourceRecordSet)
if err != nil {
log.WithFields(logFields).Errorf("failed to create record: %v", err)
}
case cloudFlareDelete:
err := p.Client.DeleteDNSRecord(zoneID, recordID)
if err != nil {
log.WithFields(logFields).Errorf("failed to delete record: %v", err)
}
case cloudFlareUpdate:
err := p.Client.UpdateDNSRecord(zoneID, recordID, change.ResourceRecordSet)
if err != nil {
log.WithFields(logFields).Errorf("failed to update record: %v", err)
}
}
}
}
return nil
}
// changesByZone separates a multi-zone change into a single change per zone.
func cloudflareChangesByZone(zones []cloudflare.Zone, changeSet []*cloudFlareChange) map[string][]*cloudFlareChange {
changes := make(map[string][]*cloudFlareChange)
for _, z := range zones {
changes[z.ID] = []*cloudFlareChange{}
}
for _, c := range changeSet {
zone := cloudflareSuitableZone(c.ResourceRecordSet.Name, zones)
if zone == nil {
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected ", c.ResourceRecordSet.Name)
continue
}
changes[zone.ID] = append(changes[zone.ID], c)
}
return changes
}
// cloudflareSuitableZone returns the most suitable zone for a given hostname
// and a set of zones.
func cloudflareSuitableZone(hostname string, zones []cloudflare.Zone) *cloudflare.Zone {
var result *cloudflare.Zone
for i := range zones {
zone := &zones[i]
if strings.HasSuffix(hostname, zone.Name) {
if result == nil || len(zone.Name) > len(result.Name) {
result = zone
}
}
}
return result
}
func (p *CloudFlareProvider) getRecordID(records []cloudflare.DNSRecord, record cloudflare.DNSRecord) string {
for _, zoneRecord := range records {
if zoneRecord.Name == record.Name && zoneRecord.Type == record.Type {
return zoneRecord.ID
}
}
return ""
}
// newCloudFlareChanges returns a collection of Changes based on the given records and action.
func newCloudFlareChanges(action string, endpoints []*endpoint.Endpoint) []*cloudFlareChange {
changes := make([]*cloudFlareChange, 0, len(endpoints))
for _, endpoint := range endpoints {
changes = append(changes, newCloudFlareChange(action, endpoint))
}
return changes
}
func newCloudFlareChange(action string, endpoint *endpoint.Endpoint) *cloudFlareChange {
typ := suitableType(endpoint)
return &cloudFlareChange{
Action: action,
ResourceRecordSet: cloudflare.DNSRecord{
Name: endpoint.DNSName,
// TTL Value of 1 is 'automatic'
TTL: 1,
Proxied: false,
Type: typ,
Content: endpoint.Target,
},
}
}

475
provider/cloudflare_test.go Normal file
View File

@ -0,0 +1,475 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"fmt"
"os"
"testing"
"github.com/cloudflare/cloudflare-go"
"github.com/kubernetes-incubator/external-dns/endpoint"
"github.com/kubernetes-incubator/external-dns/plan"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type mockCloudFlareClient struct{}
func (m *mockCloudFlareClient) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
return nil, nil
}
func (m *mockCloudFlareClient) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
if zoneID == "1234567890" {
return []cloudflare.DNSRecord{
{ID: "1234567890", Name: "foobar.ext-dns-test.zalando.to.", Type: "A"},
{ID: "1231231233", Name: "foo.bar.com"}},
nil
}
return nil, nil
}
func (m *mockCloudFlareClient) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
return nil
}
func (m *mockCloudFlareClient) DeleteDNSRecord(zoneID, recordID string) error {
return nil
}
func (m *mockCloudFlareClient) UserDetails() (cloudflare.User, error) {
return cloudflare.User{ID: "xxxxxxxxxxxxxxxxxxx"}, nil
}
func (m *mockCloudFlareClient) ZoneIDByName(zoneName string) (string, error) {
return "1234567890", nil
}
func (m *mockCloudFlareClient) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{{ID: "1234567890", Name: "ext-dns-test.zalando.to."}, {ID: "1234567891", Name: "foo.com."}}, nil
}
type mockCloudFlareUserDetailsFail struct{}
func (m *mockCloudFlareUserDetailsFail) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
return nil, nil
}
func (m *mockCloudFlareUserDetailsFail) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
return []cloudflare.DNSRecord{}, nil
}
func (m *mockCloudFlareUserDetailsFail) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
return nil
}
func (m *mockCloudFlareUserDetailsFail) DeleteDNSRecord(zoneID, recordID string) error {
return nil
}
func (m *mockCloudFlareUserDetailsFail) UserDetails() (cloudflare.User, error) {
return cloudflare.User{}, fmt.Errorf("could not get ID from zone name")
}
func (m *mockCloudFlareUserDetailsFail) ZoneIDByName(zoneName string) (string, error) {
return "", nil
}
func (m *mockCloudFlareUserDetailsFail) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{{Name: "ext-dns-test.zalando.to."}}, nil
}
type mockCloudFlareCreateZoneFail struct{}
func (m *mockCloudFlareCreateZoneFail) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
return nil, nil
}
func (m *mockCloudFlareCreateZoneFail) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
return []cloudflare.DNSRecord{}, nil
}
func (m *mockCloudFlareCreateZoneFail) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
return nil
}
func (m *mockCloudFlareCreateZoneFail) DeleteDNSRecord(zoneID, recordID string) error {
return nil
}
func (m *mockCloudFlareCreateZoneFail) UserDetails() (cloudflare.User, error) {
return cloudflare.User{ID: "xxxxxxxxxxxxxxxxxxx"}, nil
}
func (m *mockCloudFlareCreateZoneFail) ZoneIDByName(zoneName string) (string, error) {
return "", nil
}
func (m *mockCloudFlareCreateZoneFail) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{{Name: "ext-dns-test.zalando.to."}}, nil
}
type mockCloudFlareDNSRecordsFail struct{}
func (m *mockCloudFlareDNSRecordsFail) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
return nil, nil
}
func (m *mockCloudFlareDNSRecordsFail) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
return []cloudflare.DNSRecord{}, fmt.Errorf("can not get records from zone")
}
func (m *mockCloudFlareDNSRecordsFail) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
return nil
}
func (m *mockCloudFlareDNSRecordsFail) DeleteDNSRecord(zoneID, recordID string) error {
return nil
}
func (m *mockCloudFlareDNSRecordsFail) UserDetails() (cloudflare.User, error) {
return cloudflare.User{ID: "xxxxxxxxxxxxxxxxxxx"}, nil
}
func (m *mockCloudFlareDNSRecordsFail) ZoneIDByName(zoneName string) (string, error) {
return "", nil
}
func (m *mockCloudFlareDNSRecordsFail) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{{Name: "ext-dns-test.zalando.to."}}, nil
}
type mockCloudFlareZoneIDByNameFail struct{}
func (m *mockCloudFlareZoneIDByNameFail) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
return nil, nil
}
func (m *mockCloudFlareZoneIDByNameFail) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
return []cloudflare.DNSRecord{}, nil
}
func (m *mockCloudFlareZoneIDByNameFail) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
return nil
}
func (m *mockCloudFlareZoneIDByNameFail) DeleteDNSRecord(zoneID, recordID string) error {
return nil
}
func (m *mockCloudFlareZoneIDByNameFail) UserDetails() (cloudflare.User, error) {
return cloudflare.User{}, nil
}
func (m *mockCloudFlareZoneIDByNameFail) ZoneIDByName(zoneName string) (string, error) {
return "", fmt.Errorf("no ID for zone found")
}
func (m *mockCloudFlareZoneIDByNameFail) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{{Name: "ext-dns-test.zalando.to."}}, nil
}
type mockCloudFlareDeleteZoneFail struct{}
func (m *mockCloudFlareDeleteZoneFail) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
return nil, nil
}
func (m *mockCloudFlareDeleteZoneFail) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
return []cloudflare.DNSRecord{}, nil
}
func (m *mockCloudFlareDeleteZoneFail) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
return nil
}
func (m *mockCloudFlareDeleteZoneFail) DeleteDNSRecord(zoneID, recordID string) error {
return nil
}
func (m *mockCloudFlareDeleteZoneFail) UserDetails() (cloudflare.User, error) {
return cloudflare.User{}, nil
}
func (m *mockCloudFlareDeleteZoneFail) ZoneIDByName(zoneName string) (string, error) {
return "1234567890", nil
}
func (m *mockCloudFlareDeleteZoneFail) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{{Name: "ext-dns-test.zalando.to."}}, nil
}
type mockCloudFlareListZonesFail struct{}
func (m *mockCloudFlareListZonesFail) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
return nil, nil
}
func (m *mockCloudFlareListZonesFail) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
return []cloudflare.DNSRecord{}, nil
}
func (m *mockCloudFlareListZonesFail) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
return nil
}
func (m *mockCloudFlareListZonesFail) DeleteDNSRecord(zoneID, recordID string) error {
return nil
}
func (m *mockCloudFlareListZonesFail) UserDetails() (cloudflare.User, error) {
return cloudflare.User{}, nil
}
func (m *mockCloudFlareListZonesFail) ZoneIDByName(zoneName string) (string, error) {
return "1234567890", nil
}
func (m *mockCloudFlareListZonesFail) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{{}}, fmt.Errorf("no zones available")
}
type mockCloudFlareCreateRecordsFail struct{}
func (m *mockCloudFlareCreateRecordsFail) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
return nil, fmt.Errorf("could not create record")
}
func (m *mockCloudFlareCreateRecordsFail) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
return []cloudflare.DNSRecord{{ID: "1234567890", Name: "foobar.ext-dns-test.zalando.to."}}, nil
}
func (m *mockCloudFlareCreateRecordsFail) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
return nil
}
func (m *mockCloudFlareCreateRecordsFail) DeleteDNSRecord(zoneID, recordID string) error {
return nil
}
func (m *mockCloudFlareCreateRecordsFail) UserDetails() (cloudflare.User, error) {
return cloudflare.User{ID: "xxxxxxxxxxxxxxxxxxx"}, nil
}
func (m *mockCloudFlareCreateRecordsFail) ZoneIDByName(zoneName string) (string, error) {
return "1234567890", nil
}
func (m *mockCloudFlareCreateRecordsFail) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{{}}, fmt.Errorf("no zones available")
}
type mockCloudFlareDeleteRecordsFail struct{}
func (m *mockCloudFlareDeleteRecordsFail) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
return nil, nil
}
func (m *mockCloudFlareDeleteRecordsFail) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
return []cloudflare.DNSRecord{{ID: "1234567890", Name: "foobar.ext-dns-test.zalando.to."}}, nil
}
func (m *mockCloudFlareDeleteRecordsFail) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
return nil
}
func (m *mockCloudFlareDeleteRecordsFail) DeleteDNSRecord(zoneID, recordID string) error {
return fmt.Errorf("could not delete record")
}
func (m *mockCloudFlareDeleteRecordsFail) UserDetails() (cloudflare.User, error) {
return cloudflare.User{ID: "xxxxxxxxxxxxxxxxxxx"}, nil
}
func (m *mockCloudFlareDeleteRecordsFail) ZoneIDByName(zoneName string) (string, error) {
return "1234567890", nil
}
func (m *mockCloudFlareDeleteRecordsFail) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{{Name: "ext-dns-test.zalando.to."}}, nil
}
type mockCloudFlareUpdateRecordsFail struct{}
func (m *mockCloudFlareUpdateRecordsFail) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
return nil, nil
}
func (m *mockCloudFlareUpdateRecordsFail) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
return []cloudflare.DNSRecord{{ID: "1234567890", Name: "foobar.ext-dns-test.zalando.to."}}, nil
}
func (m *mockCloudFlareUpdateRecordsFail) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
return fmt.Errorf("could not update record")
}
func (m *mockCloudFlareUpdateRecordsFail) DeleteDNSRecord(zoneID, recordID string) error {
return nil
}
func (m *mockCloudFlareUpdateRecordsFail) UserDetails() (cloudflare.User, error) {
return cloudflare.User{ID: "xxxxxxxxxxxxxxxxxxx"}, nil
}
func (m *mockCloudFlareUpdateRecordsFail) ZoneIDByName(zoneName string) (string, error) {
return "1234567890", nil
}
func (m *mockCloudFlareUpdateRecordsFail) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{{Name: "ext-dns-test.zalando.to."}}, nil
}
func TestNewCloudFlareChanges(t *testing.T) {
action := cloudFlareCreate
endpoints := []*endpoint.Endpoint{{DNSName: "new", Target: "target"}}
_ = newCloudFlareChanges(action, endpoints)
}
func TestCloudFlareZones(t *testing.T) {
provider := &CloudFlareProvider{
Client: &mockCloudFlareClient{},
domainFilter: "zalando.to.",
}
zones, err := provider.Zones()
if err != nil {
t.Fatal(err)
}
validateCloudFlareZones(t, zones, []cloudflare.Zone{
{Name: "ext-dns-test.zalando.to."},
})
}
func TestRecords(t *testing.T) {
provider := &CloudFlareProvider{
Client: &mockCloudFlareClient{},
}
records, err := provider.Records()
if err != nil {
t.Errorf("should not fail, %s", err)
}
assert.Equal(t, 1, len(records))
provider.Client = &mockCloudFlareDNSRecordsFail{}
_, err = provider.Records()
if err == nil {
t.Errorf("expected to fail")
}
provider.Client = &mockCloudFlareListZonesFail{}
_, err = provider.Records()
if err == nil {
t.Errorf("expected to fail")
}
}
func TestNewCloudFlareProvider(t *testing.T) {
_ = os.Setenv("CF_API_KEY", "xxxxxxxxxxxxxxxxx")
_ = os.Setenv("CF_API_EMAIL", "test@test.com")
_, err := NewCloudFlareProvider("ext-dns-test.zalando.to.", true)
if err != nil {
t.Errorf("should not fail, %s", err)
}
_ = os.Unsetenv("CF_API_KEY")
_ = os.Unsetenv("CF_API_EMAIL")
_, err = NewCloudFlareProvider("ext-dns-test.zalando.to.", true)
if err == nil {
t.Errorf("expected to fail")
}
}
func TestApplyChanges(t *testing.T) {
changes := &plan.Changes{}
provider := &CloudFlareProvider{
Client: &mockCloudFlareClient{},
}
changes.Create = []*endpoint.Endpoint{{DNSName: "new.ext-dns-test.zalando.to.", Target: "target"}, {DNSName: "new.ext-dns-test.unrelated.to.", Target: "target"}}
changes.Delete = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Target: "target"}}
changes.UpdateOld = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Target: "target-old"}}
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Target: "target-new"}}
err := provider.ApplyChanges(changes)
if err != nil {
t.Errorf("should not fail, %s", err)
}
// empty changes
changes.Create = []*endpoint.Endpoint{}
changes.Delete = []*endpoint.Endpoint{}
changes.UpdateOld = []*endpoint.Endpoint{}
changes.UpdateNew = []*endpoint.Endpoint{}
err = provider.ApplyChanges(changes)
if err != nil {
t.Errorf("should not fail, %s", err)
}
}
func TestCloudFlareGetRecordID(t *testing.T) {
p := &CloudFlareProvider{}
records := []cloudflare.DNSRecord{
{
Name: "foo.com",
Type: "CNAME",
ID: "1",
},
{
Name: "bar.de",
Type: "A",
ID: "2",
},
}
assert.Equal(t, "", p.getRecordID(records, cloudflare.DNSRecord{
Name: "foo.com",
Type: "A",
}))
assert.Equal(t, "2", p.getRecordID(records, cloudflare.DNSRecord{
Name: "bar.de",
Type: "A",
}))
}
func TestCloudflareSuitableZone(t *testing.T) {
zones := []cloudflare.Zone{
{
ID: "1",
Name: "foo.com",
},
{
ID: "2",
Name: "foo.bar.com",
},
{
ID: "3",
Name: "bar.com",
},
}
hostname := "a.foo.bar.com"
zone := cloudflareSuitableZone(hostname, zones)
assert.NotNil(t, zone)
assert.Equal(t, "2", zone.ID)
}
func validateCloudFlareZones(t *testing.T, zones []cloudflare.Zone, expected []cloudflare.Zone) {
require.Len(t, zones, len(expected))
for i, zone := range zones {
assert.Equal(t, expected[i].Name, zone.Name)
}
}

View File

@ -384,7 +384,7 @@ func (m *mockDigitalOceanCreateRecordsFail) Records(ctx context.Context, domain
return []godo.DomainRecord{{ID: 1, Name: "foobar.ext-dns-test.zalando.to."}, {ID: 2}}, nil, nil
}
func TestNewCloudFlareChanges(t *testing.T) {
func TestNewDigitalOceanChanges(t *testing.T) {
action := DigitalOceanCreate
endpoints := []*endpoint.Endpoint{{DNSName: "new", Target: "target"}}
_ = newDigitalOceanChanges(action, endpoints)

28
vendor/github.com/cloudflare/cloudflare-go/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,28 @@
language: go
sudo: false
matrix:
include:
- go: 1.4
- go: 1.5
- go: 1.6
- go: 1.7
- go: tip
allow_failures:
- go: tip
install:
- if [[ $TRAVIS_GO_VERSION == 1.7* ]]; then go get github.com/golang/lint/golint; fi
script:
- go get -t -v $(go list ./... | grep -v '/vendor/')
- if [[ $TRAVIS_GO_VERSION == 1.7* ]]; then diff -u <(echo -n) <(gofmt -d .); fi
- if [[ $TRAVIS_GO_VERSION == 1.7* ]]; then go vet $(go list ./... | grep -v '/vendor/'); fi
- if [[ $TRAVIS_GO_VERSION == 1.7* ]]; then for package in $(go list ./... | grep -v '/vendor/'); do golint -set_exit_status $package; done; fi
- go test -v -race $(go list ./... | grep -v '/vendor/')
notifications:
email:
recipients:
- jamesog@cloudflare.com
- msilverlock@cloudflare.com

26
vendor/github.com/cloudflare/cloudflare-go/LICENSE generated vendored Normal file
View File

@ -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.

96
vendor/github.com/cloudflare/cloudflare-go/README.md generated vendored Normal file
View File

@ -0,0 +1,96 @@
# cloudflare-go
[![GoDoc](https://img.shields.io/badge/godoc-reference-5673AF.svg?style=flat-square)](https://godoc.org/github.com/cloudflare/cloudflare-go)
[![Build Status](https://img.shields.io/travis/cloudflare/cloudflare-go/master.svg?style=flat-square)](https://travis-ci.org/cloudflare/cloudflare-go)
[![Go Report Card](https://goreportcard.com/badge/github.com/cloudflare/cloudflare-go?style=flat-square)](https://goreportcard.com/report/github.com/cloudflare/cloudflare-go)
> **Note**: This library is under active development as we expand it to cover our (expanding!) API.
Consider the public API of this package a little unstable as we work towards a v1.0.
A Go library for interacting with [Cloudflare's API v4](https://api.cloudflare.com/). This library
allows you to:
* Manage and automate changes to your DNS records within Cloudflare
* Manage and automate changes to your zones (domains) on Cloudflare, including adding new zones to
your account
* List and modify the status of WAF (Web Application Firewall) rules for your zones
* Fetch Cloudflare's IP ranges for automating your firewall whitelisting
A command-line client, [flarectl](cmd/flarectl), is also available as part of this project.
## Features
The current feature list includes:
- [x] DNS Records
- [x] Zones
- [x] Web Application Firewall (WAF)
- [x] Cloudflare IPs
- [x] User Administration (partial)
- [x] Virtual DNS Management
- [ ] Organization Administration
- [ ] [Railgun](https://www.cloudflare.com/railgun/) administration
- [ ] [Keyless SSL](https://blog.cloudflare.com/keyless-ssl-the-nitty-gritty-technical-details/)
- [ ] [Origin CA](https://blog.cloudflare.com/universal-ssl-encryption-all-the-way-to-the-origin-for-free/)
Pull Requests are welcome, but please open an issue (or comment in an existing issue) to discuss any
non-trivial changes before submitting code.
## Installation
You need a working Go environment.
```
go get github.com/cloudflare/cloudflare-go
```
## Getting Started
```go
package main
import (
"fmt"
"log"
"os"
"github.com/cloudflare/cloudflare-go"
)
func main() {
// Construct a new API object
api, err := cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL"))
if err != nil {
log.Fatal(err)
}
// Fetch user details on the account
u, err := api.UserDetails()
if err != nil {
log.Fatal(err)
}
// Print user details
fmt.Println(u)
// Fetch the zone ID
id, err := api.ZoneIDByName("example.com") // Assuming example.com exists in your Cloudflare account already
if err != nil {
log.Fatal(err)
}
// Fetch zone details
zone, err := api.ZoneDetails(id)
if err != nil {
log.Fatal(err)
}
// Print zone details
fmt.Println(zone)
}
```
Also refer to the [API documentation](https://godoc.org/github.com/cloudflare/cloudflare-go) for how
to use this package in-depth.
# License
BSD licensed. See the [LICENSE](LICENSE) file for details.

View File

@ -0,0 +1,168 @@
// Package cloudflare implements the Cloudflare v4 API.
package cloudflare
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"github.com/pkg/errors"
)
const apiURL = "https://api.cloudflare.com/client/v4"
// API holds the configuration for the current API client. A client should not
// be modified concurrently.
type API struct {
APIKey string
APIEmail string
BaseURL string
headers http.Header
httpClient *http.Client
}
// New creates a new Cloudflare v4 API client.
func New(key, email string, opts ...Option) (*API, error) {
if key == "" || email == "" {
return nil, errors.New(errEmptyCredentials)
}
api := &API{
APIKey: key,
APIEmail: email,
BaseURL: apiURL,
headers: make(http.Header),
}
err := api.parseOptions(opts...)
if err != nil {
return nil, errors.Wrap(err, "options parsing failed")
}
// Fall back to http.DefaultClient if the package user does not provide
// their own.
if api.httpClient == nil {
api.httpClient = http.DefaultClient
}
return api, nil
}
// 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")
}
// makeRequest makes a HTTP request and returns the body as a byte slice,
// closing it before returnng. params will be serialized to JSON.
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
}
resp, err := api.request(method, uri, reqBody)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "could not read response body")
}
switch resp.StatusCode {
case http.StatusOK:
break
case http.StatusUnauthorized:
return nil, errors.Errorf("HTTP status %d: invalid credentials", resp.StatusCode)
case http.StatusForbidden:
return nil, errors.Errorf("HTTP status %d: insufficient permissions", resp.StatusCode)
case http.StatusServiceUnavailable, http.StatusBadGateway, http.StatusGatewayTimeout,
522, 523, 524:
return nil, errors.Errorf("HTTP status %d: service failure", resp.StatusCode)
default:
var s string
if body != nil {
s = string(body)
}
return nil, errors.Errorf("HTTP status %d: content %q", resp.StatusCode, s)
}
return body, nil
}
// request makes a HTTP request to the given API endpoint, returning the raw
// *http.Response, or an error if one occurred. The caller is responsible for
// closing the response body.
func (api *API) request(method, uri string, reqBody io.Reader) (*http.Response, error) {
req, err := http.NewRequest(method, api.BaseURL+uri, reqBody)
if err != nil {
return nil, errors.Wrap(err, "HTTP request creation failed")
}
// Apply any user-defined headers first.
req.Header = cloneHeader(api.headers)
req.Header.Set("X-Auth-Key", api.APIKey)
req.Header.Set("X-Auth-Email", api.APIEmail)
resp, err := api.httpClient.Do(req)
if err != nil {
return nil, errors.Wrap(err, "HTTP request failed")
}
return resp, nil
}
// cloneHeader returns a shallow copy of the header.
// copied from https://godoc.org/github.com/golang/gddo/httputil/header#Copy
func cloneHeader(header http.Header) http.Header {
h := make(http.Header)
for k, vs := range header {
h[k] = vs
}
return h
}
// ResponseInfo contains a code and message returned by the API as errors or
// informational messages inside the response.
type ResponseInfo struct {
Code int `json:"code"`
Message string `json:"message"`
}
// Response 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 []ResponseInfo `json:"errors"`
Messages []ResponseInfo `json:"messages"`
}
// ResultInfo contains metadata about the Response.
type ResultInfo struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
TotalPages int `json:"total_pages"`
Count int `json:"count"`
Total int `json:"total_count"`
}

View File

@ -0,0 +1,60 @@
package cloudflare
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
var (
// mux is the HTTP request multiplexer used with the test server.
mux *http.ServeMux
// client is the API client being tested
client *API
// server is a test HTTP server used to provide mock API responses
server *httptest.Server
)
func setup() {
// test server
mux = http.NewServeMux()
server = httptest.NewServer(mux)
// Cloudflare client configured to use test server
client, _ = New("cloudflare@example.org", "deadbeef")
client.BaseURL = server.URL
}
func teardown() {
server.Close()
}
func TestClient_Auth(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/ips", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method, "Expected method 'GET', got %s", r.Method)
assert.Equal(t, "cloudflare@example.com", r.Header.Get("X-Auth-Email"))
assert.Equal(t, "deadbeef", r.Header.Get("X-Auth-Token"))
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"response": {
"ipv4_cidrs": ["199.27.128.0/21"],
"ipv6_cidrs": ["199.27.128.0/21"]
}
}`)
})
_, err := IPs()
assert.NoError(t, err)
}

View File

@ -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
```

View File

@ -0,0 +1,822 @@
package main
import (
"errors"
"fmt"
"log"
"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 == nil {
var err error
api, err = cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL"))
if err != nil {
log.Fatal(err)
}
}
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 zoneCreate(c *cli.Context) {
if err := checkEnv(); err != nil {
fmt.Println(err)
return
}
if err := checkFlags(c, "zone"); err != nil {
return
}
zone := c.String("zone")
jumpstart := c.Bool("jumpstart")
orgID := c.String("org-id")
var org cloudflare.Organization
if orgID != "" {
org.ID = orgID
}
api.CreateZone(zone, jumpstart, org)
}
func zoneCheck(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
}
res, err := api.ZoneActivationCheck(zoneID)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%s\n", res.Messages[0].Message)
}
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 {
var nameservers []string
if len(z.VanityNS) > 0 {
nameservers = z.VanityNS
} else {
nameservers = z.NameServers
}
output = append(output, table{
"ID": z.ID,
"Zone": z.Name,
"Plan": z.Plan.LegacyID,
"Status": z.Status,
"Name Servers": strings.Join(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,
}
// TODO: Print the result.
_, 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
// TODO: Print the response.
_, 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")
name := c.String("name")
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,
Name: name,
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() {
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: "create",
Aliases: []string{"c"},
Action: zoneCreate,
Usage: "Create a new zone",
Flags: []cli.Flag{
cli.StringFlag{
Name: "zone",
Usage: "zone name",
},
cli.BoolFlag{
Name: "jumpstart",
Usage: "automatically fetch DNS records",
},
cli.StringFlag{
Name: "org-id",
Usage: "organization ID",
},
},
},
{
Name: "check",
Action: zoneCheck,
Usage: "Initiate a zone activation check",
Flags: []cli.Flag{
cli.StringFlag{
Name: "zone",
Usage: "zone name",
},
},
},
{
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: "name",
Usage: "record name",
},
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)
}

29
vendor/github.com/cloudflare/cloudflare-go/cpage.go generated vendored Normal file
View File

@ -0,0 +1,29 @@
package cloudflare
import "time"
// CustomPage represents a custom page configuration.
type CustomPage struct {
CreatedOn string `json:"created_on"`
ModifiedOn time.Time `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"`
}
// CustomPageResponse represents the response from the custom pages endpoint.
type CustomPageResponse struct {
Response
Result []CustomPage `json:"result"`
}
// 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

177
vendor/github.com/cloudflare/cloudflare-go/dns.go generated vendored Normal file
View File

@ -0,0 +1,177 @@
package cloudflare
import (
"encoding/json"
"net/url"
"strconv"
"time"
"github.com/pkg/errors"
)
// DNSRecord represents a DNS record in 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 time.Time `json:"created_on,omitempty"`
ModifiedOn time.Time `json:"modified_on,omitempty"`
Data interface{} `json:"data,omitempty"` // data returned by: SRV, LOC
Meta interface{} `json:"meta,omitempty"`
Priority int `json:"priority,omitempty"`
}
// DNSRecordResponse represents the response from the DNS endpoint.
type DNSRecordResponse struct {
Result DNSRecord `json:"result"`
Response
ResultInfo `json:"result_info"`
}
// DNSListResponse represents the response from the list DNS records endpoint.
type DNSListResponse struct {
Result []DNSRecord `json:"result"`
Response
ResultInfo `json:"result_info"`
}
// CreateDNSRecord creates a DNS record for the zone identifier.
// 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) (*DNSRecordResponse, error) {
uri := "/zones/" + zoneID + "/dns_records"
res, err := api.makeRequest("POST", uri, rr)
if err != nil {
return nil, errors.Wrap(err, errMakeRequestError)
}
var recordResp *DNSRecordResponse
err = json.Unmarshal(res, &recordResp)
if err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}
return recordResp, nil
}
// DNSRecords returns a slice of DNS records for the given zone identifier.
// 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{}
// Request as many records as possible per page - API max is 50
v.Set("per_page", "50")
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
var records []DNSRecord
page := 1
// Loop over makeRequest until what we've fetched all records
for {
v.Set("page", strconv.Itoa(page))
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)
}
records = append(records, r.Result...)
if r.ResultInfo.Page >= r.ResultInfo.TotalPages {
break
}
// Loop around and fetch the next page
page++
}
return records, nil
}
// DNSRecord returns a single DNS record for the given zone & record
// identifiers.
// 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
}
// UpdateDNSRecord updates a single DNS record for the given zone & record
// identifiers.
// 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
}
// Populate the record name from the existing one if the update didn't
// specify it.
if rr.Name == "" {
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
}
// DeleteDNSRecord deletes a single DNS record for the given zone & record
// identifiers.
// 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
}

47
vendor/github.com/cloudflare/cloudflare-go/errors.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package cloudflare
// Error messages
const (
errEmptyCredentials = "invalid credentials: key & email must not be empty"
errMakeRequestError = "error from makeRequest"
errUnmarshalError = "error unmarshalling the JSON response"
)
var _ Error = &UserError{}
// Error represents an error returned from this library.
type Error interface {
error
// Raised when user credentials or configuration is invalid.
User() bool
// Raised when a parsing error (e.g. JSON) occurs.
Parse() bool
// Raised when a network error occurs.
Network() bool
// Contains the most recent error.
}
// UserError represents a user-generated error.
type UserError struct {
Err error
}
// User is a user-caused error.
func (e *UserError) User() bool {
return true
}
// Network error.
func (e *UserError) Network() bool {
return false
}
// Parse error.
func (e *UserError) Parse() bool {
return true
}
// Error wraps the underlying error.
func (e *UserError) Error() string {
return e.Err.Error()
}

48
vendor/github.com/cloudflare/cloudflare-go/ips.go generated vendored Normal file
View File

@ -0,0 +1,48 @@
package cloudflare
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/pkg/errors"
)
// IPRanges contains lists 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 {
Response
Result IPRanges `json:"result"`
}
/*
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
}

57
vendor/github.com/cloudflare/cloudflare-go/keyless.go generated vendored Normal file
View File

@ -0,0 +1,57 @@
package cloudflare
import "time"
// KeylessSSL represents Keyless SSL configuration.
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 time.Time `json:"created_on"`
ModifiedOn time.Time `json:"modifed_on"`
}
// KeylessSSLResponse represents the response from the Keyless SSL endpoint.
type KeylessSSLResponse struct {
Response
Result []KeylessSSL `json:"result"`
}
// CreateKeyless creates a new Keyless SSL configuration for the zone.
// API reference:
// https://api.cloudflare.com/#keyless-ssl-for-a-zone-create-a-keyless-ssl-configuration
// POST /zones/:zone_identifier/keyless_certificates
func (api *API) CreateKeyless() {
}
// ListKeyless lists Keyless SSL configurations for a zone.
// API reference:
// https://api.cloudflare.com/#keyless-ssl-for-a-zone-list-keyless-ssls
// GET /zones/:zone_identifier/keyless_certificates
func (api *API) ListKeyless() {
}
// Keyless provides the configuration for a given Keyless SSL identifier.
// API reference:
// https://api.cloudflare.com/#keyless-ssl-for-a-zone-keyless-ssl-details
// GET /zones/:zone_identifier/keyless_certificates/:identifier
func (api *API) Keyless() {
}
// UpdateKeyless updates an existing Keyless SSL configuration.
// API reference:
// https://api.cloudflare.com/#keyless-ssl-for-a-zone-update-keyless-configuration
// PATCH /zones/:zone_identifier/keyless_certificates/:identifier
func (api *API) UpdateKeyless() {
}
// DeleteKeyless deletes an existing Keyless SSL configuration.
// API reference:
// https://api.cloudflare.com/#keyless-ssl-for-a-zone-delete-keyless-configuration
// DELETE /zones/:zone_identifier/keyless_certificates/:identifier
func (api *API) DeleteKeyless() {
}

39
vendor/github.com/cloudflare/cloudflare-go/options.go generated vendored Normal file
View File

@ -0,0 +1,39 @@
package cloudflare
import "net/http"
// Option is a functional option for configuring the API client.
type Option func(*API) error
// HTTPClient accepts a custom *http.Client for making API calls.
func HTTPClient(client *http.Client) Option {
return func(api *API) error {
api.httpClient = client
return nil
}
}
// Headers allows you to set custom HTTP headers when making API calls (e.g. for
// satisfying HTTP proxies, or for debugging).
func Headers(headers http.Header) Option {
return func(api *API) error {
api.headers = headers
return nil
}
}
// parseOptions parses the supplied options functions and returns a configured
// *API instance.
func (api *API) parseOptions(opts ...Option) error {
// Range over each options function and apply it to our API type to
// configure it. Options functions are applied in order, with any
// conflicting options overriding earlier calls.
for _, option := range opts {
err := option(api)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,42 @@
package cloudflare
import (
"encoding/json"
"github.com/pkg/errors"
)
// Organization represents a multi-user organization.
type Organization struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Status string `json:"status,omitempty"`
Permissions []string `json:"permissions,omitempty"`
Roles []string `json:"roles,omitempty"`
}
// organizationResponse represents the response from the Organization endpoint.
type organizationResponse struct {
Response
Result []Organization `json:"result"`
ResultInfo `json:"result_info"`
}
// ListOrganizations lists organizations of the logged-in user.
// API reference:
// https://api.cloudflare.com/#user-s-organizations-list-organizations
// GET /user/organizations
func (api *API) ListOrganizations() ([]Organization, ResultInfo, error) {
var r organizationResponse
res, err := api.makeRequest("GET", "/user/organizations", nil)
if err != nil {
return []Organization{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
}
err = json.Unmarshal(res, &r)
if err != nil {
return []Organization{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, r.ResultInfo, nil
}

View File

@ -0,0 +1,66 @@
package cloudflare
import (
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestOrganizations_ListOrganizations(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/user/organizations", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method, "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": [
{
"id": "01a7362d577a6c3019a474fd6f485823",
"name": "Cloudflare, Inc.",
"status": "member",
"permissions": [
"#zones:read"
],
"roles": [
"All Privileges - Super Administrator"
]
}
],
"result_info": {
"page": 1,
"per_page": 20,
"count": 1,
"total_count": 2000
}
}`)
})
user, paginator, err := client.ListOrganizations()
want := []Organization{{
ID: "01a7362d577a6c3019a474fd6f485823",
Name: "Cloudflare, Inc.",
Status: "member",
Permissions: []string{"#zones:read"},
Roles: []string{"All Privileges - Super Administrator"},
}}
if assert.NoError(t, err) {
assert.Equal(t, user, want)
}
want_pagination := ResultInfo{
Page: 1,
PerPage: 20,
Count: 1,
Total: 2000,
}
assert.Equal(t, paginator, want_pagination)
}

232
vendor/github.com/cloudflare/cloudflare-go/pagerules.go generated vendored Normal file
View File

@ -0,0 +1,232 @@
package cloudflare
import (
"encoding/json"
"time"
"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_railgun
disable_security
edge_cache_ttl
email_obfuscation
forwarding_url
ip_geolocation
mirage
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_railgun": "Disable Railgun", // Value of type string
"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
"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 time.Time `json:"modified_on,omitempty"`
CreatedOn time.Time `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
}

311
vendor/github.com/cloudflare/cloudflare-go/railgun.go generated vendored Normal file
View File

@ -0,0 +1,311 @@
package cloudflare
import (
"encoding/json"
"net/url"
"time"
"github.com/pkg/errors"
)
// Railgun represents a Railgun's properties.
type Railgun struct {
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
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 time.Time `json:"activated_on"`
CreatedOn time.Time `json:"created_on"`
ModifiedOn time.Time `json:"modified_on"`
UpgradeInfo struct {
LatestVersion string `json:"latest_version"`
DownloadLink string `json:"download_link"`
} `json:"upgrade_info"`
}
// RailgunListOptions represents the parameters used to list railguns.
type RailgunListOptions struct {
Direction string
}
// railgunResponse represents the response from the Create Railgun and the Railgun Details endpoints.
type railgunResponse struct {
Response
Result Railgun `json:"result"`
}
// railgunsResponse represents the response from the List Railguns endpoint.
type railgunsResponse struct {
Response
Result []Railgun `json:"result"`
}
// CreateRailgun creates a new Railgun.
// API reference:
// https://api.cloudflare.com/#railgun-create-railgun
// POST /railguns
func (api *API) CreateRailgun(name string) (Railgun, error) {
uri := "/railguns"
params := struct {
Name string `json:"name"`
}{
Name: name,
}
res, err := api.makeRequest("POST", uri, params)
if err != nil {
return Railgun{}, errors.Wrap(err, errMakeRequestError)
}
var r railgunResponse
if err := json.Unmarshal(res, &r); err != nil {
return Railgun{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// ListRailguns lists Railguns connected to an account.
// API reference:
// https://api.cloudflare.com/#railgun-list-railguns
// GET /railguns
func (api *API) ListRailguns(options RailgunListOptions) ([]Railgun, error) {
v := url.Values{}
if options.Direction != "" {
v.Set("direction", options.Direction)
}
uri := "/railguns" + "?" + v.Encode()
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return nil, errors.Wrap(err, errMakeRequestError)
}
var r railgunsResponse
if err := json.Unmarshal(res, &r); err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// RailgunDetails returns the details for a Railgun.
// API reference:
// https://api.cloudflare.com/#railgun-railgun-details
// GET /railguns/:identifier
func (api *API) RailgunDetails(railgunID string) (Railgun, error) {
uri := "/railguns/" + railgunID
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return Railgun{}, errors.Wrap(err, errMakeRequestError)
}
var r railgunResponse
if err := json.Unmarshal(res, &r); err != nil {
return Railgun{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// RailgunZones returns the zones that are currently using a Railgun.
// API reference:
// https://api.cloudflare.com/#railgun-get-zones-connected-to-a-railgun
// GET /railguns/:identifier/zones
func (api *API) RailgunZones(railgunID string) ([]Zone, error) {
uri := "/railguns/" + railgunID + "/zones"
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return nil, errors.Wrap(err, errMakeRequestError)
}
var r ZonesResponse
if err := json.Unmarshal(res, &r); err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// enableRailgun enables (true) or disables (false) a Railgun for all zones connected to it.
// API reference:
// https://api.cloudflare.com/#railgun-enable-or-disable-a-railgun
// PATCH /railguns/:identifier
func (api *API) enableRailgun(railgunID string, enable bool) (Railgun, error) {
uri := "/railguns/" + railgunID
params := struct {
Enabled bool `json:"enabled"`
}{
Enabled: enable,
}
res, err := api.makeRequest("PATCH", uri, params)
if err != nil {
return Railgun{}, errors.Wrap(err, errMakeRequestError)
}
var r railgunResponse
if err := json.Unmarshal(res, &r); err != nil {
return Railgun{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// EnableRailgun enables a Railgun for all zones connected to it.
// API reference:
// https://api.cloudflare.com/#railgun-enable-or-disable-a-railgun
// PATCH /railguns/:identifier
func (api *API) EnableRailgun(railgunID string) (Railgun, error) {
return api.enableRailgun(railgunID, true)
}
// DisableRailgun enables a Railgun for all zones connected to it.
// API reference:
// https://api.cloudflare.com/#railgun-enable-or-disable-a-railgun
// PATCH /railguns/:identifier
func (api *API) DisableRailgun(railgunID string) (Railgun, error) {
return api.enableRailgun(railgunID, false)
}
// DeleteRailgun disables and deletes a Railgun.
// API reference:
// https://api.cloudflare.com/#railgun-delete-railgun
// DELETE /railguns/:identifier
func (api *API) DeleteRailgun(railgunID string) error {
uri := "/railguns/" + railgunID
if _, err := api.makeRequest("DELETE", uri, nil); err != nil {
return errors.Wrap(err, errMakeRequestError)
}
return nil
}
// ZoneRailgun represents the status of a Railgun on a zone.
type ZoneRailgun struct {
ID string `json:"id"`
Name string `json:"name"`
Enabled bool `json:"enabled"`
Connected bool `json:"connected"`
}
// zoneRailgunResponse represents the response from the Zone Railgun Details endpoint.
type zoneRailgunResponse struct {
Response
Result ZoneRailgun `json:"result"`
}
// zoneRailgunsResponse represents the response from the Zone Railgun endpoint.
type zoneRailgunsResponse struct {
Response
Result []ZoneRailgun `json:"result"`
}
// RailgunDiagnosis represents the test results from testing railgun connections
// to a zone.
type RailgunDiagnosis struct {
Method string `json:"method"`
HostName string `json:"host_name"`
HTTPStatus int `json:"http_status"`
Railgun string `json:"railgun"`
URL string `json:"url"`
ResponseStatus string `json:"response_status"`
Protocol string `json:"protocol"`
ElapsedTime string `json:"elapsed_time"`
BodySize string `json:"body_size"`
BodyHash string `json:"body_hash"`
MissingHeaders string `json:"missing_headers"`
ConnectionClose bool `json:"connection_close"`
Cloudflare string `json:"cloudflare"`
CFRay string `json:"cf-ray"`
// NOTE: Cloudflare's online API documentation does not yet have definitions
// for the following fields. See: https://api.cloudflare.com/#railgun-connections-for-a-zone-test-railgun-connection/
CFWANError string `json:"cf-wan-error"`
CFCacheStatus string `json:"cf-cache-status"`
}
// railgunDiagnosisResponse represents the response from the Test Railgun Connection enpoint.
type railgunDiagnosisResponse struct {
Response
Result RailgunDiagnosis `json:"result"`
}
// ZoneRailguns returns the available Railguns for a zone.
// API reference:
// https://api.cloudflare.com/#railguns-for-a-zone-get-available-railguns
// GET /zones/:zone_identifier/railguns
func (api *API) ZoneRailguns(zoneID string) ([]ZoneRailgun, error) {
uri := "/zones/" + zoneID + "/railguns"
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return nil, errors.Wrap(err, errMakeRequestError)
}
var r zoneRailgunsResponse
if err := json.Unmarshal(res, &r); err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// ZoneRailgunDetails returns the configuration for a given Railgun.
// API reference:
// https://api.cloudflare.com/#railguns-for-a-zone-get-railgun-details
// GET /zones/:zone_identifier/railguns/:identifier
func (api *API) ZoneRailgunDetails(zoneID, railgunID string) (ZoneRailgun, error) {
uri := "/zones/" + zoneID + "/railguns/" + railgunID
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return ZoneRailgun{}, errors.Wrap(err, errMakeRequestError)
}
var r zoneRailgunResponse
if err := json.Unmarshal(res, &r); err != nil {
return ZoneRailgun{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// TestRailgunConnection tests a Railgun connection for a given zone.
// API reference:
// https://api.cloudflare.com/#railgun-connections-for-a-zone-test-railgun-connection
// GET /zones/:zone_identifier/railguns/:identifier/diagnose
func (api *API) TestRailgunConnection(zoneID, railgunID string) (RailgunDiagnosis, error) {
uri := "/zones/" + zoneID + "/railguns/" + railgunID + "/diagnose"
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return RailgunDiagnosis{}, errors.Wrap(err, errMakeRequestError)
}
var r railgunDiagnosisResponse
if err := json.Unmarshal(res, &r); err != nil {
return RailgunDiagnosis{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// connectZoneRailgun connects (true) or disconnects (false) a Railgun for a given zone.
// API reference:
// https://api.cloudflare.com/#railguns-for-a-zone-connect-or-disconnect-a-railgun
// PATCH /zones/:zone_identifier/railguns/:identifier
func (api *API) connectZoneRailgun(zoneID, railgunID string, connect bool) (ZoneRailgun, error) {
uri := "/zones/" + zoneID + "/railguns/" + railgunID
params := struct {
Connected bool `json:"connected"`
}{
Connected: connect,
}
res, err := api.makeRequest("PATCH", uri, params)
if err != nil {
return ZoneRailgun{}, errors.Wrap(err, errMakeRequestError)
}
var r zoneRailgunResponse
if err := json.Unmarshal(res, &r); err != nil {
return ZoneRailgun{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// ConnectZoneRailgun connects a Railgun for a given zone.
// API reference:
// https://api.cloudflare.com/#railguns-for-a-zone-connect-or-disconnect-a-railgun
// PATCH /zones/:zone_identifier/railguns/:identifier
func (api *API) ConnectZoneRailgun(zoneID, railgunID string) (ZoneRailgun, error) {
return api.connectZoneRailgun(zoneID, railgunID, true)
}
// DisconnectZoneRailgun disconnects a Railgun for a given zone.
// API reference:
// https://api.cloudflare.com/#railguns-for-a-zone-connect-or-disconnect-a-railgun
// PATCH /zones/:zone_identifier/railguns/:identifier
func (api *API) DisconnectZoneRailgun(zoneID, railgunID string) (ZoneRailgun, error) {
return api.connectZoneRailgun(zoneID, railgunID, false)
}

View File

@ -0,0 +1,621 @@
package cloudflare
import (
"fmt"
"io/ioutil"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCreateRailgun(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "POST", "Expected method 'POST', got %s", r.Method)
w.Header().Set("content-type", "application/json")
b, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if assert.NoError(t, err) {
assert.JSONEq(t, `{"name":"My Railgun"}`, string(b))
}
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "e928d310693a83094309acf9ead50448",
"name": "My Railgun",
"status": "active",
"enabled": true,
"zones_connected": 2,
"build": "b1234",
"version": "2.1",
"revision": "123",
"activation_key": "e4edc00281cb56ebac22c81be9bac8f3",
"activated_on": "2014-01-02T02:20:00Z",
"created_on": "2014-01-01T05:20:00Z",
"modified_on": "2014-01-01T05:20:00Z"
}
}`)
}
mux.HandleFunc("/railguns", handler)
activatedOn, _ := time.Parse(time.RFC3339, "2014-01-02T02:20:00Z")
createdOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
want := Railgun{
ID: "e928d310693a83094309acf9ead50448",
Name: "My Railgun",
Status: "active",
Enabled: true,
ZonesConnected: 2,
Build: "b1234",
Version: "2.1",
Revision: "123",
ActivationKey: "e4edc00281cb56ebac22c81be9bac8f3",
ActivatedOn: activatedOn,
CreatedOn: createdOn,
ModifiedOn: modifiedOn,
}
actual, err := client.CreateRailgun("My Railgun")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}
func TestListRailguns(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "GET", "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": [
{
"id": "e928d310693a83094309acf9ead50448",
"name": "My Railgun",
"status": "active",
"enabled": true,
"zones_connected": 2,
"build": "b1234",
"version": "2.1",
"revision": "123",
"activation_key": "e4edc00281cb56ebac22c81be9bac8f3",
"activated_on": "2014-01-02T02:20:00Z",
"created_on": "2014-01-01T05:20:00Z",
"modified_on": "2014-01-01T05:20:00Z"
}
],
"result_info": {
"page": 1,
"per_page": 20,
"count": 1,
"total_count": 2000
}
}`)
}
mux.HandleFunc("/railguns", handler)
activatedOn, _ := time.Parse(time.RFC3339, "2014-01-02T02:20:00Z")
createdOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
want := []Railgun{
{
ID: "e928d310693a83094309acf9ead50448",
Name: "My Railgun",
Status: "active",
Enabled: true,
ZonesConnected: 2,
Build: "b1234",
Version: "2.1",
Revision: "123",
ActivationKey: "e4edc00281cb56ebac22c81be9bac8f3",
ActivatedOn: activatedOn,
CreatedOn: createdOn,
ModifiedOn: modifiedOn,
},
}
actual, err := client.ListRailguns(RailgunListOptions{Direction: "desc"})
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}
func TestRailgunDetails(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "GET", "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "e928d310693a83094309acf9ead50448",
"name": "My Railgun",
"status": "active",
"enabled": true,
"zones_connected": 2,
"build": "b1234",
"version": "2.1",
"revision": "123",
"activation_key": "e4edc00281cb56ebac22c81be9bac8f3",
"activated_on": "2014-01-02T02:20:00Z",
"created_on": "2014-01-01T05:20:00Z",
"modified_on": "2014-01-01T05:20:00Z"
}
}`)
}
mux.HandleFunc("/railguns/e928d310693a83094309acf9ead50448", handler)
activatedOn, _ := time.Parse(time.RFC3339, "2014-01-02T02:20:00Z")
createdOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
want := Railgun{
ID: "e928d310693a83094309acf9ead50448",
Name: "My Railgun",
Status: "active",
Enabled: true,
ZonesConnected: 2,
Build: "b1234",
Version: "2.1",
Revision: "123",
ActivationKey: "e4edc00281cb56ebac22c81be9bac8f3",
ActivatedOn: activatedOn,
CreatedOn: createdOn,
ModifiedOn: modifiedOn,
}
actual, err := client.RailgunDetails("e928d310693a83094309acf9ead50448")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
_, err = client.RailgunDetails("bar")
assert.Error(t, err)
}
func TestRailgunZones(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "GET", "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": [
{
"id": "023e105f4ecef8ad9ca31a8372d0c353",
"name": "example.com",
"development_mode": 7200,
"original_name_servers": [
"ns1.originaldnshost.com",
"ns2.originaldnshost.com"
],
"original_registrar": "GoDaddy",
"original_dnshost": "NameCheap",
"created_on": "2014-01-01T05:20:00.12345Z",
"modified_on": "2014-01-01T05:20:00.12345Z"
}
],
"result_info": {
"page": 1,
"per_page": 20,
"count": 1,
"total_count": 2000
}
}`)
}
mux.HandleFunc("/railguns/e928d310693a83094309acf9ead50448/zones", handler)
createdOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
want := []Zone{
{
ID: "023e105f4ecef8ad9ca31a8372d0c353",
Name: "example.com",
DevMode: 7200,
OriginalNS: []string{"ns1.originaldnshost.com", "ns2.originaldnshost.com"},
OriginalRegistrar: "GoDaddy",
OriginalDNSHost: "NameCheap",
CreatedOn: createdOn,
ModifiedOn: modifiedOn,
},
}
actual, err := client.RailgunZones("e928d310693a83094309acf9ead50448")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
_, err = client.RailgunZones("bar")
assert.Error(t, err)
}
func TestEnableRailgun(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "PATCH", "Expected method 'PATCH', got %s", r.Method)
b, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if assert.NoError(t, err) {
assert.JSONEq(t, `{"enabled":true}`, string(b))
}
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "e928d310693a83094309acf9ead50448",
"name": "My Railgun",
"status": "active",
"enabled": true,
"zones_connected": 2,
"build": "b1234",
"version": "2.1",
"revision": "123",
"activation_key": "e4edc00281cb56ebac22c81be9bac8f3",
"activated_on": "2014-01-02T02:20:00Z",
"created_on": "2014-01-01T05:20:00Z",
"modified_on": "2014-01-01T05:20:00Z"
}
}`)
}
mux.HandleFunc("/railguns/e928d310693a83094309acf9ead50448", handler)
activatedOn, _ := time.Parse(time.RFC3339, "2014-01-02T02:20:00Z")
createdOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
want := Railgun{
ID: "e928d310693a83094309acf9ead50448",
Name: "My Railgun",
Status: "active",
Enabled: true,
ZonesConnected: 2,
Build: "b1234",
Version: "2.1",
Revision: "123",
ActivationKey: "e4edc00281cb56ebac22c81be9bac8f3",
ActivatedOn: activatedOn,
CreatedOn: createdOn,
ModifiedOn: modifiedOn,
}
actual, err := client.EnableRailgun("e928d310693a83094309acf9ead50448")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
_, err = client.EnableRailgun("bar")
assert.Error(t, err)
}
func TestDisbleRailgun(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "PATCH", "Expected method 'PATCH', got %s", r.Method)
b, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if assert.NoError(t, err) {
assert.JSONEq(t, `{"enabled":false}`, string(b))
}
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "e928d310693a83094309acf9ead50448",
"name": "My Railgun",
"status": "active",
"enabled": false,
"zones_connected": 2,
"build": "b1234",
"version": "2.1",
"revision": "123",
"activation_key": "e4edc00281cb56ebac22c81be9bac8f3",
"activated_on": "2014-01-02T02:20:00Z",
"created_on": "2014-01-01T05:20:00Z",
"modified_on": "2014-01-01T05:20:00Z"
}
}`)
}
mux.HandleFunc("/railguns/e928d310693a83094309acf9ead50448", handler)
activatedOn, _ := time.Parse(time.RFC3339, "2014-01-02T02:20:00Z")
createdOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
want := Railgun{
ID: "e928d310693a83094309acf9ead50448",
Name: "My Railgun",
Status: "active",
Enabled: false,
ZonesConnected: 2,
Build: "b1234",
Version: "2.1",
Revision: "123",
ActivationKey: "e4edc00281cb56ebac22c81be9bac8f3",
ActivatedOn: activatedOn,
CreatedOn: createdOn,
ModifiedOn: modifiedOn,
}
actual, err := client.DisableRailgun("e928d310693a83094309acf9ead50448")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
_, err = client.DisableRailgun("bar")
assert.Error(t, err)
}
func TestDeleteRailgun(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "DELETE", "Expected method 'DELETE', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "e928d310693a83094309acf9ead50448"
}
}`)
}
mux.HandleFunc("/railguns/e928d310693a83094309acf9ead50448", handler)
assert.NoError(t, client.DeleteRailgun("e928d310693a83094309acf9ead50448"))
assert.Error(t, client.DeleteRailgun("bar"))
}
func TestZoneRailguns(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "GET", "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": [
{
"id": "e928d310693a83094309acf9ead50448",
"name": "My Railgun",
"enabled": true,
"connected": true
}
],
"result_info": {
"page": 1,
"per_page": 20,
"count": 1,
"total_count": 2000
}
}`)
}
mux.HandleFunc("/zones/023e105f4ecef8ad9ca31a8372d0c353/railguns", handler)
want := []ZoneRailgun{
{
ID: "e928d310693a83094309acf9ead50448",
Name: "My Railgun",
Enabled: true,
Connected: true,
},
}
actual, err := client.ZoneRailguns("023e105f4ecef8ad9ca31a8372d0c353")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
_, err = client.ZoneRailguns("bar")
assert.Error(t, err)
}
func TestZoneRailgunDetails(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "GET", "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "e928d310693a83094309acf9ead50448",
"name": "My Railgun",
"enabled": true,
"connected": true
}
}`)
}
mux.HandleFunc("/zones/023e105f4ecef8ad9ca31a8372d0c353/railguns/e928d310693a83094309acf9ead50448", handler)
want := ZoneRailgun{
ID: "e928d310693a83094309acf9ead50448",
Name: "My Railgun",
Enabled: true,
Connected: true,
}
actual, err := client.ZoneRailgunDetails("023e105f4ecef8ad9ca31a8372d0c353", "e928d310693a83094309acf9ead50448")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
_, err = client.ZoneRailgunDetails("bar", "baz")
assert.Error(t, err)
}
func TestTestRailgunConnection(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "GET", "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"method": "GET",
"host_name": "www.example.com",
"http_status": 200,
"railgun": "on",
"url": "https://www.cloudflare.com",
"response_status": "200 OK",
"protocol": "HTTP/1.1",
"elapsed_time": "0.239013s",
"body_size": "63910 bytes",
"body_hash": "be27f2429421e12f200cab1da43ba301bdc70e1d",
"missing_headers": "No Content-Length or Transfer-Encoding",
"connection_close": false,
"cloudflare": "on",
"cf-ray": "1ddd7570575207d9-LAX",
"cf-wan-error": null,
"cf-cache-status": null
}
}`)
}
mux.HandleFunc("/zones/023e105f4ecef8ad9ca31a8372d0c353/railguns/e928d310693a83094309acf9ead50448/diagnose", handler)
want := RailgunDiagnosis{
Method: "GET",
HostName: "www.example.com",
HTTPStatus: 200,
Railgun: "on",
URL: "https://www.cloudflare.com",
ResponseStatus: "200 OK",
Protocol: "HTTP/1.1",
ElapsedTime: "0.239013s",
BodySize: "63910 bytes",
BodyHash: "be27f2429421e12f200cab1da43ba301bdc70e1d",
MissingHeaders: "No Content-Length or Transfer-Encoding",
ConnectionClose: false,
Cloudflare: "on",
CFRay: "1ddd7570575207d9-LAX",
CFWANError: "",
CFCacheStatus: "",
}
actual, err := client.TestRailgunConnection("023e105f4ecef8ad9ca31a8372d0c353", "e928d310693a83094309acf9ead50448")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
_, err = client.TestRailgunConnection("bar", "baz")
assert.Error(t, err)
}
func TestConnectRailgun(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "PATCH", "Expected method 'PATCH', got %s", r.Method)
b, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if assert.NoError(t, err) {
assert.JSONEq(t, `{"connected":true}`, string(b))
}
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "e928d310693a83094309acf9ead50448",
"name": "My Railgun",
"enabled": true,
"connected": true
}
}`)
}
mux.HandleFunc("/zones/023e105f4ecef8ad9ca31a8372d0c353/railguns/e928d310693a83094309acf9ead50448", handler)
want := ZoneRailgun{
ID: "e928d310693a83094309acf9ead50448",
Name: "My Railgun",
Enabled: true,
Connected: true,
}
actual, err := client.ConnectZoneRailgun("023e105f4ecef8ad9ca31a8372d0c353", "e928d310693a83094309acf9ead50448")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
_, err = client.ConnectZoneRailgun("bar", "baz")
assert.Error(t, err)
}
func TestDisconnectRailgun(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "PATCH", "Expected method 'PATCH', got %s", r.Method)
b, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if assert.NoError(t, err) {
assert.JSONEq(t, `{"connected":false}`, string(b))
}
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "e928d310693a83094309acf9ead50448",
"name": "My Railgun",
"enabled": true,
"connected": false
}
}`)
}
mux.HandleFunc("/zones/023e105f4ecef8ad9ca31a8372d0c353/railguns/e928d310693a83094309acf9ead50448", handler)
want := ZoneRailgun{
ID: "e928d310693a83094309acf9ead50448",
Name: "My Railgun",
Enabled: true,
Connected: false,
}
actual, err := client.DisconnectZoneRailgun("023e105f4ecef8ad9ca31a8372d0c353", "e928d310693a83094309acf9ead50448")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
_, err = client.DisconnectZoneRailgun("bar", "baz")
assert.Error(t, err)
}

154
vendor/github.com/cloudflare/cloudflare-go/ssl.go generated vendored Normal file
View File

@ -0,0 +1,154 @@
package cloudflare
import (
"encoding/json"
"time"
"github.com/pkg/errors"
)
// ZoneCustomSSL represents custom SSL certificate metadata.
type ZoneCustomSSL struct {
ID string `json:"id"`
Hosts []string `json:"hosts"`
Issuer string `json:"issuer"`
Signature string `json:"signature"`
Status string `json:"status"`
BundleMethod string `json:"bundle_method"`
ZoneID string `json:"zone_id"`
UploadedOn time.Time `json:"uploaded_on"`
ModifiedOn time.Time `json:"modified_on"`
ExpiresOn time.Time `json:"expires_on"`
Priority int `json:"priority"`
KeylessServer KeylessSSL `json:"keyless_server"`
}
// zoneCustomSSLResponse represents the response from the zone SSL details endpoint.
type zoneCustomSSLResponse struct {
Response
Result ZoneCustomSSL `json:"result"`
}
// zoneCustomSSLsResponse represents the response from the zone SSL list endpoint.
type zoneCustomSSLsResponse struct {
Response
Result []ZoneCustomSSL `json:"result"`
}
// ZoneCustomSSLOptions represents the parameters to create or update an existing
// custom SSL configuration.
type ZoneCustomSSLOptions struct {
Certificate string `json:"certificate"`
PrivateKey string `json:"private_key"`
BundleMethod string `json:"bundle_method,omitempty"`
}
// ZoneCustomSSLPriority represents a certificate's ID and priority. It is a
// subset of ZoneCustomSSL used for patch requests.
type ZoneCustomSSLPriority struct {
ID string `json:"ID"`
Priority int `json:"priority"`
}
// CreateSSL allows you to add a custom SSL certificate to the given zone.
// API reference:
// https://api.cloudflare.com/#custom-ssl-for-a-zone-create-ssl-configuration
// POST /zones/:zone_identifier/custom_certificates
func (api *API) CreateSSL(zoneID string, options ZoneCustomSSLOptions) (ZoneCustomSSL, error) {
uri := "/zones/" + zoneID + "/custom_certificates"
res, err := api.makeRequest("POST", uri, options)
if err != nil {
return ZoneCustomSSL{}, errors.Wrap(err, errMakeRequestError)
}
var r zoneCustomSSLResponse
if err := json.Unmarshal(res, &r); err != nil {
return ZoneCustomSSL{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// ListSSL lists the custom certificates for the given zone.
// API reference:
// https://api.cloudflare.com/#custom-ssl-for-a-zone-list-ssl-configurations
// GET /zones/:zone_identifier/custom_certificates
func (api *API) ListSSL(zoneID string) ([]ZoneCustomSSL, error) {
uri := "/zones/" + zoneID + "/custom_certificates"
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return nil, errors.Wrap(err, errMakeRequestError)
}
var r zoneCustomSSLsResponse
if err := json.Unmarshal(res, &r); err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// SSLDetails returns the configuration details for a custom SSL certificate.
// API reference:
// https://api.cloudflare.com/#custom-ssl-for-a-zone-ssl-configuration-details
// GET /zones/:zone_identifier/custom_certificates/:identifier
func (api *API) SSLDetails(zoneID, certificateID string) (ZoneCustomSSL, error) {
uri := "/zones/" + zoneID + "/custom_certificates/" + certificateID
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return ZoneCustomSSL{}, errors.Wrap(err, errMakeRequestError)
}
var r zoneCustomSSLResponse
if err := json.Unmarshal(res, &r); err != nil {
return ZoneCustomSSL{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// UpdateSSL updates (replaces) a custom SSL certificate.
// API reference:
// https://api.cloudflare.com/#custom-ssl-for-a-zone-update-ssl-configuration
// PATCH /zones/:zone_identifier/custom_certificates/:identifier
func (api *API) UpdateSSL(zoneID, certificateID string, options ZoneCustomSSLOptions) (ZoneCustomSSL, error) {
uri := "/zones/" + zoneID + "/custom_certificates/" + certificateID
res, err := api.makeRequest("PATCH", uri, options)
if err != nil {
return ZoneCustomSSL{}, errors.Wrap(err, errMakeRequestError)
}
var r zoneCustomSSLResponse
if err := json.Unmarshal(res, &r); err != nil {
return ZoneCustomSSL{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// ReprioritizeSSL allows you to change the priority (which is served for a given
// request) of custom SSL certificates associated with the given zone.
// API reference:
// https://api.cloudflare.com/#custom-ssl-for-a-zone-re-prioritize-ssl-certificates
// PUT /zones/:zone_identifier/custom_certificates/prioritize
func (api *API) ReprioritizeSSL(zoneID string, p []ZoneCustomSSLPriority) ([]ZoneCustomSSL, error) {
uri := "/zones/" + zoneID + "/custom_certificates/prioritize"
params := struct {
Certificates []ZoneCustomSSLPriority `json:"certificates"`
}{
Certificates: p,
}
res, err := api.makeRequest("PUT", uri, params)
if err != nil {
return nil, errors.Wrap(err, errMakeRequestError)
}
var r zoneCustomSSLsResponse
if err := json.Unmarshal(res, &r); err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// DeleteSSL deletes a custom SSL certificate from the given zone.
// API reference:
// https://api.cloudflare.com/#custom-ssl-for-a-zone-delete-an-ssl-certificate
// DELETE /zones/:zone_identifier/custom_certificates/:identifier
func (api *API) DeleteSSL(zoneID, certificateID string) error {
uri := "/zones/" + zoneID + "/custom_certificates/" + certificateID
if _, err := api.makeRequest("DELETE", uri, nil); err != nil {
return errors.Wrap(err, errMakeRequestError)
}
return nil
}

377
vendor/github.com/cloudflare/cloudflare-go/ssl_test.go generated vendored Normal file
View File

@ -0,0 +1,377 @@
package cloudflare
import (
"fmt"
"io/ioutil"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCreateSSL(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "POST", r.Method, "Expected method 'POST', got %s", r.Method)
b, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if assert.NoError(t, err) {
assert.JSONEq(t, `{"certificate":"-----BEGIN CERTIFICATE----- MIIDtTCCAp2gAwIBAgIJAM15n7fdxhRtMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV BAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQwHhcNMTQwMzExMTkyMTU5WhcNMTQwNDEwMTkyMTU5WjBF MQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAvq3sKsHpeduJHimOK+fvQdKsI8z8A05MZyyLp2/R/GE8FjNv+hkVY1WQ LIyTNNQH7CJecE1nbTfo8Y56S7x/rhxC6/DJ8MIulapFPnorq46KU6yRxiM0MQ3N nTJHlHA2ozZta6YBBfVfhHWl1F0IfNbXCLKvGwWWMbCx43OfW6KTkbRnE6gFWKuO fSO5h2u5TaWVuSIzBvYs7Vza6m+gtYAvKAJV2nSZ+eSEFPDo29corOy8+huEOUL8 5FAw4BFPsr1TlrlGPFitduQUHGrSL7skk1ESGza0to3bOtrodKei2s9bk5MXm7lZ qI+WZJX4Zu9+mzZhc9pCVi8r/qlXuQIDAQABo4GnMIGkMB0GA1UdDgQWBBRvavf+ sWM4IwKiH9X9w1vl6nUVRDB1BgNVHSMEbjBsgBRvavf+sWM4IwKiH9X9w1vl6nUV RKFJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAM15n7fdxhRtMAwGA1UdEwQF MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBABY2ZzBaW0dMsAAT7tPJzrVWVzQx6KU4 UEBLudIlWPlkAwTnINCWR/8eNjCCmGA4heUdHmazdpPa8RzwOmc0NT1NQqzSyktt vTqb4iHD7+8f9MqJ9/FssCfTtqr/Qst/hGH4Wmdf1EJ/6FqYAAb5iRlPgshFZxU8 uXtA8hWn6fK6eISD9HBdcAFToUvKNZ1BIDPvh9f95Ine8ar6yGd56TUNrHR8eHBs ESxz5ddVR/oWRysNJ+aGAyYqHS8S/ttmC7r4XCAHqXptkHPCGRqkAhsterYhd4I8 /cBzejUobNCjjHFbtkAL/SjxZOLW+pNkZwfeYdM8iPkD54Uua1v2tdw= -----END CERTIFICATE-----","private_key":"-----BEGIN RSA PRIVATE KEY-----MIIEowIBAAKCAQEAl 1cSc0vfcJLI4ZdWjiZZqy86Eof4czCwilyjXdvHqbdgDjz9H6K/0FX78EzVdfyExESptPCDl5YYjvcZyAWlgNfYEpFpGeoh/pTFW3hlyKImh4EgBXbDrR251J Ew2Nf56X3duibI6X20gKZA6cvdmWeKh MOOXuh1bSPU3dkb4YOF/fng5iGrx0q3txdMQXTPMZ1uXHFcBH7idgViYesXUBhdll3GP1N Y8laq0yrqh 8HMsZK m27MebqonbNmjOqE218lVEvjCdRO6xvNXrO6vNJBoGn2eGwZ8BVd0mTA3Tj43/2cmxQFY9FLq56cCXqYI1fbRRib ZLrjSNkwIDAQABAoIBABfAjjsjjxc0NxcYvKOMUb9Rpj8Sx6U/o/tDC5u XmsGX37aaJmC5yw9BQiAxgvXtQryEl5uoNoqOdsxzKV6yM0vPcwKEJVBd4G6yx6AjVJZnc2qf72erR7BbA2CQh scMDRBKE041HhgTBRNP6roim0SOgYP5JZIrGAQXNIkyE0fZc5gZNUt388ne/mjWM6Xi08BDGurLC68nsdt7Nd UYqeBVxo2EqChp5vKYZYEcG8h9XBj4u4NIwg1Mty2JqX30uBjoHvF5w/pMs8lG uvj6JR9I 19wtCuccbAJl 4cUq03UQoIDmwejea oC8A8WJr3vVpODDWrvAsjllGPBECgYEAyQRa6edYO6bsSvgbM13qXW9OQTn9YmgzfN24Ux1D66TQU6sBSLdfSHshDhTCi Ax 698aJNRWujAakA2DDgspSx98aRnHbF zvY7i7iWGesN6uN0zL 6/MK5uWoieGZRjgk230fLk00l4/FK1mJIp0apr0Lis9xmDjP5AaUPTUUCgYEAwXuhTHZWPT6v8YwOksjbuK UDkIIvyMux53kb73vrkgMboS4DB1zMLNyG 9EghS414CFROUwGl4ZUKboH1Jo5G34y8VgDuHjirTqL2H6 zNpML iMrWCXjpFKkxwPbeQnEAZ 5Rud4d PTyXAt71blZHE9tZ4KHy8cU1iKc9APcCgYAIqKZd4vg7AZK2G//X85iv06aUSrIudfyZyVcyRVVyphPPNtOEVVnGXn9rAtvqeIrOo52BR68 cj4vlXp hkDuEH QVBuY/NdQhOzFtPrKPQTJdGjIlQ2x65Vidj7r3sRukNkLPyV2v D885zcpTkp83JFuWTYiIrg275DIuAI3QKBgAglM0IrzS g3vlVQxvM1ussgRgkkYeybHq82 wUW 3DXLqeXb0s1DedplUkuoabZriz0Wh4GZFSmtA5ZpZC uV697lkYsndmp2xRhaekllW7bu pY5q88URwO2p8CO5AZ6CWFWuBwSDML5VOapGRqDRgwaD oGpb7fb7IgHOls7AoGBAJnL6Q8t35uYJ8J8hY7wso88IE04z6VaT8WganxcndesWER9eFQDHDDy//ZYeyt6M41uIY CL Vkm9Kwl/bHLJKdnOE1a9NdE6mtfah0Bk2u/YOuzyu5mmcgZiX X/OZuEbGmmbZOR1FCuIyrNYfwYohhcZP7/r0Ia/1GpkHc3Bi-----END RSA PRIVATE KEY-----","bundle_method":"ubiquitous"}`, string(b))
}
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "7e7b8deba8538af625850b7b2530034c",
"hosts": [
"example.com"
],
"issuer": "GlobalSign",
"signature": "SHA256WithRSA",
"status": "active",
"bundle_method": "ubiquitous",
"zone_id": "023e105f4ecef8ad9ca31a8372d0c353",
"uploaded_on": "2014-01-01T05:20:00Z",
"modified_on": "2014-01-01T05:20:00Z",
"expires_on": "2016-01-01T05:20:00Z",
"priority": 1
}
}`)
}
mux.HandleFunc("/zones/023e105f4ecef8ad9ca31a8372d0c353/custom_certificates", handler)
hosts := make([]string, 1, 4)
hosts[0] = "example.com"
uploadedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
expiresOn, _ := time.Parse(time.RFC3339, "2016-01-01T05:20:00Z")
want := ZoneCustomSSL{
ID: "7e7b8deba8538af625850b7b2530034c",
Hosts: hosts,
Issuer: "GlobalSign",
Signature: "SHA256WithRSA",
Status: "active",
BundleMethod: "ubiquitous",
ZoneID: "023e105f4ecef8ad9ca31a8372d0c353",
UploadedOn: uploadedOn,
ModifiedOn: modifiedOn,
ExpiresOn: expiresOn,
Priority: 1,
}
actual, err := client.CreateSSL("023e105f4ecef8ad9ca31a8372d0c353", ZoneCustomSSLOptions{
Certificate: "-----BEGIN CERTIFICATE----- MIIDtTCCAp2gAwIBAgIJAM15n7fdxhRtMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV BAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQwHhcNMTQwMzExMTkyMTU5WhcNMTQwNDEwMTkyMTU5WjBF MQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAvq3sKsHpeduJHimOK+fvQdKsI8z8A05MZyyLp2/R/GE8FjNv+hkVY1WQ LIyTNNQH7CJecE1nbTfo8Y56S7x/rhxC6/DJ8MIulapFPnorq46KU6yRxiM0MQ3N nTJHlHA2ozZta6YBBfVfhHWl1F0IfNbXCLKvGwWWMbCx43OfW6KTkbRnE6gFWKuO fSO5h2u5TaWVuSIzBvYs7Vza6m+gtYAvKAJV2nSZ+eSEFPDo29corOy8+huEOUL8 5FAw4BFPsr1TlrlGPFitduQUHGrSL7skk1ESGza0to3bOtrodKei2s9bk5MXm7lZ qI+WZJX4Zu9+mzZhc9pCVi8r/qlXuQIDAQABo4GnMIGkMB0GA1UdDgQWBBRvavf+ sWM4IwKiH9X9w1vl6nUVRDB1BgNVHSMEbjBsgBRvavf+sWM4IwKiH9X9w1vl6nUV RKFJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAM15n7fdxhRtMAwGA1UdEwQF MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBABY2ZzBaW0dMsAAT7tPJzrVWVzQx6KU4 UEBLudIlWPlkAwTnINCWR/8eNjCCmGA4heUdHmazdpPa8RzwOmc0NT1NQqzSyktt vTqb4iHD7+8f9MqJ9/FssCfTtqr/Qst/hGH4Wmdf1EJ/6FqYAAb5iRlPgshFZxU8 uXtA8hWn6fK6eISD9HBdcAFToUvKNZ1BIDPvh9f95Ine8ar6yGd56TUNrHR8eHBs ESxz5ddVR/oWRysNJ+aGAyYqHS8S/ttmC7r4XCAHqXptkHPCGRqkAhsterYhd4I8 /cBzejUobNCjjHFbtkAL/SjxZOLW+pNkZwfeYdM8iPkD54Uua1v2tdw= -----END CERTIFICATE-----",
PrivateKey: "-----BEGIN RSA PRIVATE KEY-----MIIEowIBAAKCAQEAl 1cSc0vfcJLI4ZdWjiZZqy86Eof4czCwilyjXdvHqbdgDjz9H6K/0FX78EzVdfyExESptPCDl5YYjvcZyAWlgNfYEpFpGeoh/pTFW3hlyKImh4EgBXbDrR251J Ew2Nf56X3duibI6X20gKZA6cvdmWeKh MOOXuh1bSPU3dkb4YOF/fng5iGrx0q3txdMQXTPMZ1uXHFcBH7idgViYesXUBhdll3GP1N Y8laq0yrqh 8HMsZK m27MebqonbNmjOqE218lVEvjCdRO6xvNXrO6vNJBoGn2eGwZ8BVd0mTA3Tj43/2cmxQFY9FLq56cCXqYI1fbRRib ZLrjSNkwIDAQABAoIBABfAjjsjjxc0NxcYvKOMUb9Rpj8Sx6U/o/tDC5u XmsGX37aaJmC5yw9BQiAxgvXtQryEl5uoNoqOdsxzKV6yM0vPcwKEJVBd4G6yx6AjVJZnc2qf72erR7BbA2CQh scMDRBKE041HhgTBRNP6roim0SOgYP5JZIrGAQXNIkyE0fZc5gZNUt388ne/mjWM6Xi08BDGurLC68nsdt7Nd UYqeBVxo2EqChp5vKYZYEcG8h9XBj4u4NIwg1Mty2JqX30uBjoHvF5w/pMs8lG uvj6JR9I 19wtCuccbAJl 4cUq03UQoIDmwejea oC8A8WJr3vVpODDWrvAsjllGPBECgYEAyQRa6edYO6bsSvgbM13qXW9OQTn9YmgzfN24Ux1D66TQU6sBSLdfSHshDhTCi Ax 698aJNRWujAakA2DDgspSx98aRnHbF zvY7i7iWGesN6uN0zL 6/MK5uWoieGZRjgk230fLk00l4/FK1mJIp0apr0Lis9xmDjP5AaUPTUUCgYEAwXuhTHZWPT6v8YwOksjbuK UDkIIvyMux53kb73vrkgMboS4DB1zMLNyG 9EghS414CFROUwGl4ZUKboH1Jo5G34y8VgDuHjirTqL2H6 zNpML iMrWCXjpFKkxwPbeQnEAZ 5Rud4d PTyXAt71blZHE9tZ4KHy8cU1iKc9APcCgYAIqKZd4vg7AZK2G//X85iv06aUSrIudfyZyVcyRVVyphPPNtOEVVnGXn9rAtvqeIrOo52BR68 cj4vlXp hkDuEH QVBuY/NdQhOzFtPrKPQTJdGjIlQ2x65Vidj7r3sRukNkLPyV2v D885zcpTkp83JFuWTYiIrg275DIuAI3QKBgAglM0IrzS g3vlVQxvM1ussgRgkkYeybHq82 wUW 3DXLqeXb0s1DedplUkuoabZriz0Wh4GZFSmtA5ZpZC uV697lkYsndmp2xRhaekllW7bu pY5q88URwO2p8CO5AZ6CWFWuBwSDML5VOapGRqDRgwaD oGpb7fb7IgHOls7AoGBAJnL6Q8t35uYJ8J8hY7wso88IE04z6VaT8WganxcndesWER9eFQDHDDy//ZYeyt6M41uIY CL Vkm9Kwl/bHLJKdnOE1a9NdE6mtfah0Bk2u/YOuzyu5mmcgZiX X/OZuEbGmmbZOR1FCuIyrNYfwYohhcZP7/r0Ia/1GpkHc3Bi-----END RSA PRIVATE KEY-----",
BundleMethod: "ubiquitous",
})
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
_, err = client.CreateSSL("bar", ZoneCustomSSLOptions{})
assert.Error(t, err)
}
func TestListSSL(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method, "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": [
{
"id": "7e7b8deba8538af625850b7b2530034c",
"hosts": [
"example.com"
],
"issuer": "GlobalSign",
"signature": "SHA256WithRSA",
"status": "active",
"bundle_method": "ubiquitous",
"zone_id": "023e105f4ecef8ad9ca31a8372d0c353",
"uploaded_on": "2014-01-01T05:20:00Z",
"modified_on": "2014-01-01T05:20:00Z",
"expires_on": "2016-01-01T05:20:00Z",
"priority": 1
}
],
"result_info": {
"page": 1,
"per_page": 20,
"count": 1,
"total_count": 2000
}
}`)
}
mux.HandleFunc("/zones/023e105f4ecef8ad9ca31a8372d0c353/custom_certificates", handler)
hosts := make([]string, 1, 4)
hosts[0] = "example.com"
uploadedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
expiresOn, _ := time.Parse(time.RFC3339, "2016-01-01T05:20:00Z")
want := make([]ZoneCustomSSL, 1, 4)
want[0] = ZoneCustomSSL{
ID: "7e7b8deba8538af625850b7b2530034c",
Hosts: hosts,
Issuer: "GlobalSign",
Signature: "SHA256WithRSA",
Status: "active",
BundleMethod: "ubiquitous",
ZoneID: "023e105f4ecef8ad9ca31a8372d0c353",
UploadedOn: uploadedOn,
ModifiedOn: modifiedOn,
ExpiresOn: expiresOn,
Priority: 1,
}
actual, err := client.ListSSL("023e105f4ecef8ad9ca31a8372d0c353")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
_, err = client.ListSSL("bar")
assert.Error(t, err)
}
func TestSSLDetails(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "7e7b8deba8538af625850b7b2530034c",
"hosts": [
"example.com"
],
"issuer": "GlobalSign",
"signature": "SHA256WithRSA",
"status": "active",
"bundle_method": "ubiquitous",
"zone_id": "023e105f4ecef8ad9ca31a8372d0c353",
"uploaded_on": "2014-01-01T05:20:00Z",
"modified_on": "2014-01-01T05:20:00Z",
"expires_on": "2016-01-01T05:20:00Z",
"priority": 1
}
}`)
}
mux.HandleFunc("/zones/023e105f4ecef8ad9ca31a8372d0c353/custom_certificates/7e7b8deba8538af625850b7b2530034c", handler)
hosts := make([]string, 1, 4)
hosts[0] = "example.com"
uploadedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
expiresOn, _ := time.Parse(time.RFC3339, "2016-01-01T05:20:00Z")
want := ZoneCustomSSL{
ID: "7e7b8deba8538af625850b7b2530034c",
Hosts: hosts,
Issuer: "GlobalSign",
Signature: "SHA256WithRSA",
Status: "active",
BundleMethod: "ubiquitous",
ZoneID: "023e105f4ecef8ad9ca31a8372d0c353",
UploadedOn: uploadedOn,
ModifiedOn: modifiedOn,
ExpiresOn: expiresOn,
Priority: 1,
}
actual, err := client.SSLDetails("023e105f4ecef8ad9ca31a8372d0c353", "7e7b8deba8538af625850b7b2530034c")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
_, err = client.SSLDetails("023e105f4ecef8ad9ca31a8372d0c353", "bar")
assert.Error(t, err)
}
func TestUpdateSSL(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "PATCH", r.Method, "Expected method 'PATCH', got %s", r.Method)
b, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if assert.NoError(t, err) {
assert.JSONEq(t, `{"certificate":"-----BEGIN CERTIFICATE----- MIIDtTCCAp2gAwIBAgIJAM15n7fdxhRtMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV BAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQwHhcNMTQwMzExMTkyMTU5WhcNMTQwNDEwMTkyMTU5WjBF MQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAvq3sKsHpeduJHimOK+fvQdKsI8z8A05MZyyLp2/R/GE8FjNv+hkVY1WQ LIyTNNQH7CJecE1nbTfo8Y56S7x/rhxC6/DJ8MIulapFPnorq46KU6yRxiM0MQ3N nTJHlHA2ozZta6YBBfVfhHWl1F0IfNbXCLKvGwWWMbCx43OfW6KTkbRnE6gFWKuO fSO5h2u5TaWVuSIzBvYs7Vza6m+gtYAvKAJV2nSZ+eSEFPDo29corOy8+huEOUL8 5FAw4BFPsr1TlrlGPFitduQUHGrSL7skk1ESGza0to3bOtrodKei2s9bk5MXm7lZ qI+WZJX4Zu9+mzZhc9pCVi8r/qlXuQIDAQABo4GnMIGkMB0GA1UdDgQWBBRvavf+ sWM4IwKiH9X9w1vl6nUVRDB1BgNVHSMEbjBsgBRvavf+sWM4IwKiH9X9w1vl6nUV RKFJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAM15n7fdxhRtMAwGA1UdEwQF MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBABY2ZzBaW0dMsAAT7tPJzrVWVzQx6KU4 UEBLudIlWPlkAwTnINCWR/8eNjCCmGA4heUdHmazdpPa8RzwOmc0NT1NQqzSyktt vTqb4iHD7+8f9MqJ9/FssCfTtqr/Qst/hGH4Wmdf1EJ/6FqYAAb5iRlPgshFZxU8 uXtA8hWn6fK6eISD9HBdcAFToUvKNZ1BIDPvh9f95Ine8ar6yGd56TUNrHR8eHBs ESxz5ddVR/oWRysNJ+aGAyYqHS8S/ttmC7r4XCAHqXptkHPCGRqkAhsterYhd4I8 /cBzejUobNCjjHFbtkAL/SjxZOLW+pNkZwfeYdM8iPkD54Uua1v2tdw= -----END CERTIFICATE-----","private_key":"-----BEGIN RSA PRIVATE KEY-----MIIEowIBAAKCAQEAl 1cSc0vfcJLI4ZdWjiZZqy86Eof4czCwilyjXdvHqbdgDjz9H6K/0FX78EzVdfyExESptPCDl5YYjvcZyAWlgNfYEpFpGeoh/pTFW3hlyKImh4EgBXbDrR251J Ew2Nf56X3duibI6X20gKZA6cvdmWeKh MOOXuh1bSPU3dkb4YOF/fng5iGrx0q3txdMQXTPMZ1uXHFcBH7idgViYesXUBhdll3GP1N Y8laq0yrqh 8HMsZK m27MebqonbNmjOqE218lVEvjCdRO6xvNXrO6vNJBoGn2eGwZ8BVd0mTA3Tj43/2cmxQFY9FLq56cCXqYI1fbRRib ZLrjSNkwIDAQABAoIBABfAjjsjjxc0NxcYvKOMUb9Rpj8Sx6U/o/tDC5u XmsGX37aaJmC5yw9BQiAxgvXtQryEl5uoNoqOdsxzKV6yM0vPcwKEJVBd4G6yx6AjVJZnc2qf72erR7BbA2CQh scMDRBKE041HhgTBRNP6roim0SOgYP5JZIrGAQXNIkyE0fZc5gZNUt388ne/mjWM6Xi08BDGurLC68nsdt7Nd UYqeBVxo2EqChp5vKYZYEcG8h9XBj4u4NIwg1Mty2JqX30uBjoHvF5w/pMs8lG uvj6JR9I 19wtCuccbAJl 4cUq03UQoIDmwejea oC8A8WJr3vVpODDWrvAsjllGPBECgYEAyQRa6edYO6bsSvgbM13qXW9OQTn9YmgzfN24Ux1D66TQU6sBSLdfSHshDhTCi Ax 698aJNRWujAakA2DDgspSx98aRnHbF zvY7i7iWGesN6uN0zL 6/MK5uWoieGZRjgk230fLk00l4/FK1mJIp0apr0Lis9xmDjP5AaUPTUUCgYEAwXuhTHZWPT6v8YwOksjbuK UDkIIvyMux53kb73vrkgMboS4DB1zMLNyG 9EghS414CFROUwGl4ZUKboH1Jo5G34y8VgDuHjirTqL2H6 zNpML iMrWCXjpFKkxwPbeQnEAZ 5Rud4d PTyXAt71blZHE9tZ4KHy8cU1iKc9APcCgYAIqKZd4vg7AZK2G//X85iv06aUSrIudfyZyVcyRVVyphPPNtOEVVnGXn9rAtvqeIrOo52BR68 cj4vlXp hkDuEH QVBuY/NdQhOzFtPrKPQTJdGjIlQ2x65Vidj7r3sRukNkLPyV2v D885zcpTkp83JFuWTYiIrg275DIuAI3QKBgAglM0IrzS g3vlVQxvM1ussgRgkkYeybHq82 wUW 3DXLqeXb0s1DedplUkuoabZriz0Wh4GZFSmtA5ZpZC uV697lkYsndmp2xRhaekllW7bu pY5q88URwO2p8CO5AZ6CWFWuBwSDML5VOapGRqDRgwaD oGpb7fb7IgHOls7AoGBAJnL6Q8t35uYJ8J8hY7wso88IE04z6VaT8WganxcndesWER9eFQDHDDy//ZYeyt6M41uIY CL Vkm9Kwl/bHLJKdnOE1a9NdE6mtfah0Bk2u/YOuzyu5mmcgZiX X/OZuEbGmmbZOR1FCuIyrNYfwYohhcZP7/r0Ia/1GpkHc3Bi-----END RSA PRIVATE KEY-----","bundle_method":"ubiquitous"}`, string(b))
}
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "7e7b8deba8538af625850b7b2530034c",
"hosts": [
"example.com"
],
"issuer": "GlobalSign",
"signature": "SHA256WithRSA",
"status": "active",
"bundle_method": "ubiquitous",
"zone_id": "023e105f4ecef8ad9ca31a8372d0c353",
"uploaded_on": "2014-01-01T05:20:00Z",
"modified_on": "2014-01-01T05:20:00Z",
"expires_on": "2016-01-01T05:20:00Z",
"priority": 1
}
}`)
}
mux.HandleFunc("/zones/023e105f4ecef8ad9ca31a8372d0c353/custom_certificates/7e7b8deba8538af625850b7b2530034c", handler)
hosts := make([]string, 1, 4)
hosts[0] = "example.com"
uploadedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
expiresOn, _ := time.Parse(time.RFC3339, "2016-01-01T05:20:00Z")
want := ZoneCustomSSL{
ID: "7e7b8deba8538af625850b7b2530034c",
Hosts: hosts,
Issuer: "GlobalSign",
Signature: "SHA256WithRSA",
Status: "active",
BundleMethod: "ubiquitous",
ZoneID: "023e105f4ecef8ad9ca31a8372d0c353",
UploadedOn: uploadedOn,
ModifiedOn: modifiedOn,
ExpiresOn: expiresOn,
Priority: 1,
}
actual, err := client.UpdateSSL("023e105f4ecef8ad9ca31a8372d0c353", "7e7b8deba8538af625850b7b2530034c", ZoneCustomSSLOptions{
Certificate: "-----BEGIN CERTIFICATE----- MIIDtTCCAp2gAwIBAgIJAM15n7fdxhRtMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV BAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQwHhcNMTQwMzExMTkyMTU5WhcNMTQwNDEwMTkyMTU5WjBF MQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAvq3sKsHpeduJHimOK+fvQdKsI8z8A05MZyyLp2/R/GE8FjNv+hkVY1WQ LIyTNNQH7CJecE1nbTfo8Y56S7x/rhxC6/DJ8MIulapFPnorq46KU6yRxiM0MQ3N nTJHlHA2ozZta6YBBfVfhHWl1F0IfNbXCLKvGwWWMbCx43OfW6KTkbRnE6gFWKuO fSO5h2u5TaWVuSIzBvYs7Vza6m+gtYAvKAJV2nSZ+eSEFPDo29corOy8+huEOUL8 5FAw4BFPsr1TlrlGPFitduQUHGrSL7skk1ESGza0to3bOtrodKei2s9bk5MXm7lZ qI+WZJX4Zu9+mzZhc9pCVi8r/qlXuQIDAQABo4GnMIGkMB0GA1UdDgQWBBRvavf+ sWM4IwKiH9X9w1vl6nUVRDB1BgNVHSMEbjBsgBRvavf+sWM4IwKiH9X9w1vl6nUV RKFJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAM15n7fdxhRtMAwGA1UdEwQF MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBABY2ZzBaW0dMsAAT7tPJzrVWVzQx6KU4 UEBLudIlWPlkAwTnINCWR/8eNjCCmGA4heUdHmazdpPa8RzwOmc0NT1NQqzSyktt vTqb4iHD7+8f9MqJ9/FssCfTtqr/Qst/hGH4Wmdf1EJ/6FqYAAb5iRlPgshFZxU8 uXtA8hWn6fK6eISD9HBdcAFToUvKNZ1BIDPvh9f95Ine8ar6yGd56TUNrHR8eHBs ESxz5ddVR/oWRysNJ+aGAyYqHS8S/ttmC7r4XCAHqXptkHPCGRqkAhsterYhd4I8 /cBzejUobNCjjHFbtkAL/SjxZOLW+pNkZwfeYdM8iPkD54Uua1v2tdw= -----END CERTIFICATE-----",
PrivateKey: "-----BEGIN RSA PRIVATE KEY-----MIIEowIBAAKCAQEAl 1cSc0vfcJLI4ZdWjiZZqy86Eof4czCwilyjXdvHqbdgDjz9H6K/0FX78EzVdfyExESptPCDl5YYjvcZyAWlgNfYEpFpGeoh/pTFW3hlyKImh4EgBXbDrR251J Ew2Nf56X3duibI6X20gKZA6cvdmWeKh MOOXuh1bSPU3dkb4YOF/fng5iGrx0q3txdMQXTPMZ1uXHFcBH7idgViYesXUBhdll3GP1N Y8laq0yrqh 8HMsZK m27MebqonbNmjOqE218lVEvjCdRO6xvNXrO6vNJBoGn2eGwZ8BVd0mTA3Tj43/2cmxQFY9FLq56cCXqYI1fbRRib ZLrjSNkwIDAQABAoIBABfAjjsjjxc0NxcYvKOMUb9Rpj8Sx6U/o/tDC5u XmsGX37aaJmC5yw9BQiAxgvXtQryEl5uoNoqOdsxzKV6yM0vPcwKEJVBd4G6yx6AjVJZnc2qf72erR7BbA2CQh scMDRBKE041HhgTBRNP6roim0SOgYP5JZIrGAQXNIkyE0fZc5gZNUt388ne/mjWM6Xi08BDGurLC68nsdt7Nd UYqeBVxo2EqChp5vKYZYEcG8h9XBj4u4NIwg1Mty2JqX30uBjoHvF5w/pMs8lG uvj6JR9I 19wtCuccbAJl 4cUq03UQoIDmwejea oC8A8WJr3vVpODDWrvAsjllGPBECgYEAyQRa6edYO6bsSvgbM13qXW9OQTn9YmgzfN24Ux1D66TQU6sBSLdfSHshDhTCi Ax 698aJNRWujAakA2DDgspSx98aRnHbF zvY7i7iWGesN6uN0zL 6/MK5uWoieGZRjgk230fLk00l4/FK1mJIp0apr0Lis9xmDjP5AaUPTUUCgYEAwXuhTHZWPT6v8YwOksjbuK UDkIIvyMux53kb73vrkgMboS4DB1zMLNyG 9EghS414CFROUwGl4ZUKboH1Jo5G34y8VgDuHjirTqL2H6 zNpML iMrWCXjpFKkxwPbeQnEAZ 5Rud4d PTyXAt71blZHE9tZ4KHy8cU1iKc9APcCgYAIqKZd4vg7AZK2G//X85iv06aUSrIudfyZyVcyRVVyphPPNtOEVVnGXn9rAtvqeIrOo52BR68 cj4vlXp hkDuEH QVBuY/NdQhOzFtPrKPQTJdGjIlQ2x65Vidj7r3sRukNkLPyV2v D885zcpTkp83JFuWTYiIrg275DIuAI3QKBgAglM0IrzS g3vlVQxvM1ussgRgkkYeybHq82 wUW 3DXLqeXb0s1DedplUkuoabZriz0Wh4GZFSmtA5ZpZC uV697lkYsndmp2xRhaekllW7bu pY5q88URwO2p8CO5AZ6CWFWuBwSDML5VOapGRqDRgwaD oGpb7fb7IgHOls7AoGBAJnL6Q8t35uYJ8J8hY7wso88IE04z6VaT8WganxcndesWER9eFQDHDDy//ZYeyt6M41uIY CL Vkm9Kwl/bHLJKdnOE1a9NdE6mtfah0Bk2u/YOuzyu5mmcgZiX X/OZuEbGmmbZOR1FCuIyrNYfwYohhcZP7/r0Ia/1GpkHc3Bi-----END RSA PRIVATE KEY-----",
BundleMethod: "ubiquitous",
})
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
_, err = client.UpdateSSL("023e105f4ecef8ad9ca31a8372d0c353", "bar", ZoneCustomSSLOptions{})
assert.Error(t, err)
}
func TestReprioritizeSSL(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "PUT", r.Method, "Expected method 'PUT', got %s", r.Method)
b, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if assert.NoError(t, err) {
assert.JSONEq(t, `{"certificates":[{"ID":"5a7805061c76ada191ed06f989cc3dac","priority":2},{"ID":"9a7806061c88ada191ed06f989cc3dac","priority":1}]}`, string(b))
}
w.Header().Set("content-type", "application/json")
// XXX: Test response flow properly.
// Current response assertion uses generic example from the documentation,
// rather than responding to the actual PUT request.
// https://api.cloudflare.com/#custom-ssl-for-a-zone-re-prioritize-ssl-certificates
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": [
{
"id": "7e7b8deba8538af625850b7b2530034c",
"hosts": [
"example.com"
],
"issuer": "GlobalSign",
"signature": "SHA256WithRSA",
"status": "active",
"bundle_method": "ubiquitous",
"zone_id": "023e105f4ecef8ad9ca31a8372d0c353",
"uploaded_on": "2014-01-01T05:20:00Z",
"modified_on": "2014-01-01T05:20:00Z",
"expires_on": "2016-01-01T05:20:00Z",
"priority": 1
}
],
"result_info": {
"page": 1,
"per_page": 20,
"count": 1,
"total_count": 2000
}
}`)
}
mux.HandleFunc("/zones/023e105f4ecef8ad9ca31a8372d0c353/custom_certificates/prioritize", handler)
hosts := make([]string, 1, 4)
hosts[0] = "example.com"
uploadedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
expiresOn, _ := time.Parse(time.RFC3339, "2016-01-01T05:20:00Z")
want := make([]ZoneCustomSSL, 1, 4)
want[0] = ZoneCustomSSL{
ID: "7e7b8deba8538af625850b7b2530034c",
Hosts: hosts,
Issuer: "GlobalSign",
Signature: "SHA256WithRSA",
Status: "active",
BundleMethod: "ubiquitous",
ZoneID: "023e105f4ecef8ad9ca31a8372d0c353",
UploadedOn: uploadedOn,
ModifiedOn: modifiedOn,
ExpiresOn: expiresOn,
Priority: 1,
}
actual, err := client.ReprioritizeSSL("023e105f4ecef8ad9ca31a8372d0c353", []ZoneCustomSSLPriority{
{ID: "5a7805061c76ada191ed06f989cc3dac", Priority: 2},
{ID: "9a7806061c88ada191ed06f989cc3dac", Priority: 1},
})
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}
func TestDeleteSSL(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "DELETE", r.Method, "Expected method 'DELETE', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"id": "7e7b8deba8538af625850b7b2530034c"
}`)
}
mux.HandleFunc("/zones/023e105f4ecef8ad9ca31a8372d0c353/custom_certificates/7e7b8deba8538af625850b7b2530034c", handler)
err := client.DeleteSSL("023e105f4ecef8ad9ca31a8372d0c353", "7e7b8deba8538af625850b7b2530034c")
assert.NoError(t, err, "Expected to successfully delete certificate ID '7e7b8deba8538af625850b7b2530034c', received error instead")
err = client.DeleteSSL("023e105f4ecef8ad9ca31a8372d0c353", "bar")
assert.Error(t, err, "Expected to error when attempting to delete certificate ID 'bar', did not receive error instead")
}

116
vendor/github.com/cloudflare/cloudflare-go/user.go generated vendored Normal file
View File

@ -0,0 +1,116 @@
package cloudflare
import (
"encoding/json"
"time"
"github.com/pkg/errors"
)
// User describes a user account.
type User struct {
ID string `json:"id,omitempty"`
Email string `json:"email,omitempty"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Username string `json:"username,omitempty"`
Telephone string `json:"telephone,omitempty"`
Country string `json:"country,omitempty"`
Zipcode string `json:"zipcode,omitempty"`
CreatedOn *time.Time `json:"created_on,omitempty"`
ModifiedOn *time.Time `json:"modified_on,omitempty"`
APIKey string `json:"api_key,omitempty"`
TwoFA bool `json:"two_factor_authentication_enabled,omitempty"`
Betas []string `json:"betas,omitempty"`
Organizations []Organization `json:"organizations,omitempty"`
}
// UserResponse wraps a response containing User accounts.
type UserResponse struct {
Response
Result User `json:"result"`
}
// userBillingProfileResponse wraps a response containing Billing Profile information.
type userBillingProfileResponse struct {
Response
Result UserBillingProfile
}
// UserBillingProfile contains Billing Profile information.
type UserBillingProfile struct {
ID string `json:"id,omitempty"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Address string `json:"address,omitempty"`
Address2 string `json:"address2,omitempty"`
Company string `json:"company,omitempty"`
City string `json:"city,omitempty"`
State string `json:"state,omitempty"`
ZipCode string `json:"zipcode,omitempty"`
Country string `json:"country,omitempty"`
Telephone string `json:"telephone,omitempty"`
CardNumber string `json:"card_number,omitempty"`
CardExpiryYear int `json:"card_expiry_year,omitempty"`
CardExpiryMonth int `json:"card_expiry_month,omitempty"`
VAT string `json:"vat,omitempty"`
CreatedOn *time.Time `json:"created_on,omitempty"`
EditedOn *time.Time `json:"edited_on,omitempty"`
}
// UserDetails provides information about the logged-in user.
// API reference:
// https://api.cloudflare.com/#user-user-details
// GET /user
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
}
// UpdateUser updates the properties of the given user.
// API reference:
// https://api.cloudflare.com/#user-update-user
// PATCH /user
func (api *API) UpdateUser(user *User) (User, error) {
var r UserResponse
res, err := api.makeRequest("PATCH", "/user", user)
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
}
// UserBillingProfile returns the billing profile of the user.
// API reference:
// https://api.cloudflare.com/#user-billing-profile
// GET /user/billing/profile
func (api *API) UserBillingProfile() (UserBillingProfile, error) {
var r userBillingProfileResponse
res, err := api.makeRequest("GET", "/user/billing/profile", nil)
if err != nil {
return UserBillingProfile{}, errors.Wrap(err, errMakeRequestError)
}
err = json.Unmarshal(res, &r)
if err != nil {
return UserBillingProfile{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}

196
vendor/github.com/cloudflare/cloudflare-go/user_test.go generated vendored Normal file
View File

@ -0,0 +1,196 @@
package cloudflare
import (
"fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"io/ioutil"
)
func TestUser_UserDetails(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method, "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "1",
"email": "cloudflare@example.com",
"first_name": "Jane",
"last_name": "Smith",
"username": "cloudflare12345",
"telephone": "+1 (650) 319 8930",
"country": "US",
"zipcode": "94107",
"created_on": "2009-07-01T00:00:00Z",
"modified_on": "2016-05-06T20:32:00Z",
"two_factor_authentication_enabled": true,
"betas": ["mirage_forever"]
}
}`)
})
user, err := client.UserDetails()
createdOn, _ := time.Parse(time.RFC3339, "2009-07-01T00:00:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2016-05-06T20:32:00Z")
want := User{
ID: "1",
Email: "cloudflare@example.com",
FirstName: "Jane",
LastName: "Smith",
Username: "cloudflare12345",
Telephone: "+1 (650) 319 8930",
Country: "US",
Zipcode: "94107",
CreatedOn: &createdOn,
ModifiedOn: &modifiedOn,
TwoFA: true,
Betas: []string{"mirage_forever"},
}
if assert.NoError(t, err) {
assert.Equal(t, user, want)
}
}
func TestUser_UpdateUser(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "PATCH", r.Method, "Expected method 'PATCH', got %s", r.Method)
b, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if assert.NoError(t, err) {
assert.JSONEq(t, `{"country":"US","first_name":"John","username":"cfuser12345","email":"user@example.com",
"last_name": "Appleseed","telephone": "+1 123-123-1234","zipcode": "12345"}`, string(b), "JSON not equal")
}
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "7c5dae5552338874e5053f2534d2767a",
"email": "user@example.com",
"first_name": "John",
"last_name": "Appleseed",
"username": "cfuser12345",
"telephone": "+1 123-123-1234",
"country": "US",
"zipcode": "12345",
"created_on": "2014-01-01T05:20:00Z",
"modified_on": "2014-01-01T05:20:00Z",
"two_factor_authentication_enabled": false
}
}`)
})
createdOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
userIn := User{
Email: "user@example.com",
FirstName: "John",
LastName: "Appleseed",
Username: "cfuser12345",
Telephone: "+1 123-123-1234",
Country: "US",
Zipcode: "12345",
TwoFA: false,
}
userOut, err := client.UpdateUser(&userIn)
want := User{
ID: "7c5dae5552338874e5053f2534d2767a",
Email: "user@example.com",
FirstName: "John",
LastName: "Appleseed",
Username: "cfuser12345",
Telephone: "+1 123-123-1234",
Country: "US",
Zipcode: "12345",
CreatedOn: &createdOn,
ModifiedOn: &modifiedOn,
TwoFA: false,
}
if assert.NoError(t, err) {
assert.Equal(t, userOut, want, "structs not equal")
}
}
func TestUser_UserBillingProfile(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/user/billing/profile", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method, "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "0020c268dbf54e975e7fe8563df49d52",
"first_name": "Bob",
"last_name": "Smith",
"address": "123 3rd St.",
"address2": "Apt 123",
"company": "Cloudflare",
"city": "San Francisco",
"state": "CA",
"zipcode": "12345",
"country": "US",
"telephone": "+1 111-867-5309",
"card_number": "xxxx-xxxx-xxxx-1234",
"card_expiry_year": 2015,
"card_expiry_month": 4,
"vat": "aaa-123-987",
"edited_on": "2014-04-01T12:21:02.0000Z",
"created_on": "2014-03-01T12:21:02.0000Z"
}
}`)
})
createdOn, _ := time.Parse(time.RFC3339, "2014-03-01T12:21:02.0000Z")
editedOn, _ := time.Parse(time.RFC3339, "2014-04-01T12:21:02.0000Z")
userBillingProfile, err := client.UserBillingProfile()
want := UserBillingProfile{
ID: "0020c268dbf54e975e7fe8563df49d52",
FirstName: "Bob",
LastName: "Smith",
Address: "123 3rd St.",
Address2: "Apt 123",
Company: "Cloudflare",
City: "San Francisco",
State: "CA",
ZipCode: "12345",
Country: "US",
Telephone: "+1 111-867-5309",
CardNumber: "xxxx-xxxx-xxxx-1234",
CardExpiryYear: 2015,
CardExpiryMonth: 4,
VAT: "aaa-123-987",
CreatedOn: &createdOn,
EditedOn: &editedOn,
}
if assert.NoError(t, err) {
assert.Equal(t, userBillingProfile, want, "structs not equal")
}
}

View File

@ -0,0 +1,130 @@
package cloudflare
import (
"encoding/json"
"github.com/pkg/errors"
)
// VirtualDNS represents a Virtual DNS configuration.
type VirtualDNS struct {
ID string `json:"id"`
Name string `json:"name"`
OriginIPs []string `json:"origin_ips"`
VirtualDNSIPs []string `json:"virtual_dns_ips"`
MinimumCacheTTL uint `json:"minimum_cache_ttl"`
MaximumCacheTTL uint `json:"maximum_cache_ttl"`
DeprecateAnyRequests bool `json:"deprecate_any_requests"`
ModifiedOn string `json:"modified_on"`
}
// VirtualDNSResponse represents a Virtual DNS response.
type VirtualDNSResponse struct {
Response
Result *VirtualDNS `json:"result"`
}
// VirtualDNSListResponse represents an array of Virtual DNS responses.
type VirtualDNSListResponse struct {
Response
Result []*VirtualDNS `json:"result"`
}
// CreateVirtualDNS creates a new Virtual DNS cluster.
// API reference:
// https://api.cloudflare.com/#virtual-dns-users--create-a-virtual-dns-cluster
// POST /user/virtual_dns
func (api *API) CreateVirtualDNS(v *VirtualDNS) (*VirtualDNS, error) {
res, err := api.makeRequest("POST", "/user/virtual_dns", v)
if err != nil {
return nil, errors.Wrap(err, errMakeRequestError)
}
response := &VirtualDNSResponse{}
err = json.Unmarshal(res, &response)
if err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}
return response.Result, nil
}
// VirtualDNS fetches a single virtual DNS cluster.
// API reference:
// https://api.cloudflare.com/#virtual-dns-users--get-a-virtual-dns-cluster
// GET /user/virtual_dns/:identifier
func (api *API) VirtualDNS(virtualDNSID string) (*VirtualDNS, error) {
uri := "/user/virtual_dns/" + virtualDNSID
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return nil, errors.Wrap(err, errMakeRequestError)
}
response := &VirtualDNSResponse{}
err = json.Unmarshal(res, &response)
if err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}
return response.Result, nil
}
// ListVirtualDNS lists the virtual DNS clusters associated with an account.
// API reference:
// https://api.cloudflare.com/#virtual-dns-users--get-virtual-dns-clusters
// GET /user/virtual_dns
func (api *API) ListVirtualDNS() ([]*VirtualDNS, error) {
res, err := api.makeRequest("GET", "/user/virtual_dns", nil)
if err != nil {
return nil, errors.Wrap(err, errMakeRequestError)
}
response := &VirtualDNSListResponse{}
err = json.Unmarshal(res, &response)
if err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}
return response.Result, nil
}
// UpdateVirtualDNS updates a Virtual DNS cluster.
// API reference:
// https://api.cloudflare.com/#virtual-dns-users--modify-a-virtual-dns-cluster
// PATCH /user/virtual_dns/:identifier
func (api *API) UpdateVirtualDNS(virtualDNSID string, vv VirtualDNS) error {
uri := "/user/virtual_dns/" + virtualDNSID
res, err := api.makeRequest("PUT", uri, vv)
if err != nil {
return errors.Wrap(err, errMakeRequestError)
}
response := &VirtualDNSResponse{}
err = json.Unmarshal(res, &response)
if err != nil {
return errors.Wrap(err, errUnmarshalError)
}
return nil
}
// DeleteVirtualDNS deletes a Virtual DNS cluster. Note that this cannot be
// undone, and will stop all traffic to that cluster.
// API reference:
// https://api.cloudflare.com/#virtual-dns-users--delete-a-virtual-dns-cluster
// DELETE /user/virtual_dns/:identifier
func (api *API) DeleteVirtualDNS(virtualDNSID string) error {
uri := "/user/virtual_dns/" + virtualDNSID
res, err := api.makeRequest("DELETE", uri, nil)
if err != nil {
return errors.Wrap(err, errMakeRequestError)
}
response := &VirtualDNSResponse{}
err = json.Unmarshal(res, &response)
if err != nil {
return errors.Wrap(err, errUnmarshalError)
}
return nil
}

97
vendor/github.com/cloudflare/cloudflare-go/waf.go generated vendored Normal file
View File

@ -0,0 +1,97 @@
package cloudflare
import (
"encoding/json"
"github.com/pkg/errors"
)
// WAFPackage represents a WAF package configuration.
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"`
}
// WAFPackagesResponse represents the response from the WAF packages endpoint.
type WAFPackagesResponse struct {
Response
Result []WAFPackage `json:"result"`
ResultInfo ResultInfo `json:"result_info"`
}
// WAFRule represents a WAF rule.
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"`
}
// WAFRulesResponse represents the response from the WAF rule endpoint.
type WAFRulesResponse struct {
Response
Result []WAFRule `json:"result"`
ResultInfo ResultInfo `json:"result_info"`
}
// ListWAFPackages returns a slice of the WAF packages for the given zone.
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
}
// ListWAFRules returns a slice of the WAF rules for the given WAF package.
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
}

521
vendor/github.com/cloudflare/cloudflare-go/zone.go generated vendored Normal file
View File

@ -0,0 +1,521 @@
package cloudflare
import (
"encoding/json"
"fmt"
"net/url"
"time"
"github.com/pkg/errors"
)
// Owner describes the resource owner.
type Owner struct {
ID string `json:"id"`
Email string `json:"email"`
OwnerType string `json:"owner_type"`
}
// 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 time.Time `json:"created_on"`
ModifiedOn time.Time `json:"modified_on"`
NameServers []string `json:"name_servers"`
Owner Owner `json:"owner"`
Permissions []string `json:"permissions"`
Plan ZonePlan `json:"plan"`
PlanPending ZonePlan `json:"plan_pending,omitempty"`
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"`
}
// ZoneMeta 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"`
}
// ZonePlan contains the plan information for a zone.
type ZonePlan struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Price int `json:"price,omitempty"`
Currency string `json:"currency,omitempty"`
Frequency string `json:"frequency,omitempty"`
LegacyID string `json:"legacy_id,omitempty"`
IsSubscribed bool `json:"is_subscribed,omitempty"`
CanSubscribe bool `json:"can_subscribe,omitempty"`
}
// ZoneID contains only the zone ID.
type ZoneID struct {
ID string `json:"id"`
}
// ZoneResponse represents the response from the Zone endpoint containing a single zone.
type ZoneResponse struct {
Response
Result Zone `json:"result"`
}
// ZonesResponse represents the response from the Zone endpoint containing an array of zones.
type ZonesResponse struct {
Response
Result []Zone `json:"result"`
}
// ZoneIDResponse represents the response from the Zone endpoint, containing only a zone ID.
type ZoneIDResponse struct {
Response
Result ZoneID `json:"result"`
}
// AvailableZonePlansResponse represents the response from the Available Plans endpoint.
type AvailableZonePlansResponse struct {
Response
Result []ZonePlan `json:"result"`
ResultInfo
}
// ZonePlanResponse represents the response from the Plan Details endpoint.
type ZonePlanResponse struct {
Response
Result ZonePlan `json:"result"`
}
// ZoneSetting contains settings for a zone.
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"`
}
// ZoneSettingResponse represents the response from the Zone Setting endpoint.
type ZoneSettingResponse struct {
Response
Result []ZoneSetting `json:"result"`
}
// ZoneAnalyticsData contains totals and timeseries analytics data for a zone.
type ZoneAnalyticsData struct {
Totals ZoneAnalytics `json:"totals"`
Timeseries []ZoneAnalytics `json:"timeseries"`
}
// zoneAnalyticsDataResponse represents the response from the Zone Analytics Dashboard endpoint.
type zoneAnalyticsDataResponse struct {
Response
Result ZoneAnalyticsData `json:"result"`
}
// ZoneAnalyticsColocation contains analytics data by datacenter.
type ZoneAnalyticsColocation struct {
ColocationID string `json:"colo_id"`
Timeseries []ZoneAnalytics `json:"timeseries"`
}
// zoneAnalyticsColocationResponse represents the response from the Zone Analytics By Co-location endpoint.
type zoneAnalyticsColocationResponse struct {
Response
Result []ZoneAnalyticsColocation `json:"result"`
}
// ZoneAnalytics contains analytics data for a zone.
type ZoneAnalytics struct {
Since time.Time `json:"since"`
Until time.Time `json:"until"`
Requests struct {
All int `json:"all"`
Cached int `json:"cached"`
Uncached int `json:"uncached"`
ContentType map[string]int `json:"content_type"`
Country map[string]int `json:"country"`
SSL struct {
Encrypted int `json:"encrypted"`
Unencrypted int `json:"unencrypted"`
} `json:"ssl"`
HTTPStatus map[string]int `json:"http_status"`
} `json:"requests"`
Bandwidth struct {
All int `json:"all"`
Cached int `json:"cached"`
Uncached int `json:"uncached"`
ContentType map[string]int `json:"content_type"`
Country map[string]int `json:"country"`
SSL struct {
Encrypted int `json:"encrypted"`
Unencrypted int `json:"unencrypted"`
} `json:"ssl"`
} `json:"bandwidth"`
Threats struct {
All int `json:"all"`
Country map[string]int `json:"country"`
Type map[string]int `json:"type"`
} `json:"threats"`
Pageviews struct {
All int `json:"all"`
SearchEngines map[string]int `json:"search_engines"`
} `json:"pageviews"`
Uniques struct {
All int `json:"all"`
}
}
// ZoneAnalyticsOptions represents the optional parameters in Zone Analytics
// endpoint requests.
type ZoneAnalyticsOptions struct {
Since *time.Time
Until *time.Time
Continuous *bool
}
// PurgeCacheRequest represents the request format made to the purge endpoint.
type PurgeCacheRequest struct {
Everything bool `json:"purge_everything,omitempty"`
Files []string `json:"files,omitempty"`
Tags []string `json:"tags,omitempty"`
}
// PurgeCacheResponse represents the response from the purge endpoint.
type PurgeCacheResponse struct {
Response
}
// newZone describes a new zone.
type newZone struct {
Name string `json:"name"`
JumpStart bool `json:"jump_start"`
// We use a pointer to get a nil type when the field is empty.
// This allows us to completely omit this with json.Marshal().
Organization *Organization `json:"organization,omitempty"`
}
// CreateZone creates a zone on an account.
//
// API reference: https://api.cloudflare.com/#zone-create-a-zone
func (api *API) CreateZone(name string, jumpstart bool, org Organization) (Zone, error) {
var newzone newZone
newzone.Name = name
newzone.JumpStart = jumpstart
if org.ID != "" {
newzone.Organization = &org
}
res, err := api.makeRequest("POST", "/zones", newzone)
if err != nil {
return Zone{}, errors.Wrap(err, errMakeRequestError)
}
var r ZoneResponse
err = json.Unmarshal(res, &r)
if err != nil {
return Zone{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// ZoneActivationCheck initiates another zone activation check for newly-created zones.
//
// API reference: https://api.cloudflare.com/#zone-initiate-another-zone-activation-check
func (api *API) ZoneActivationCheck(zoneID string) (Response, error) {
res, err := api.makeRequest("PUT", "/zones/"+zoneID+"/activation_check", nil)
if err != nil {
return Response{}, errors.Wrap(err, errMakeRequestError)
}
var r Response
err = json.Unmarshal(res, &r)
if err != nil {
return Response{}, errors.Wrap(err, errUnmarshalError)
}
return r, nil
}
// ListZones lists zones on an account. Optionally takes a list of zone names
// to filter against.
//
// 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 ZonesResponse
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 {
// TODO: Paginate here. We only grab the first page of results.
// Could do this concurrently after the first request by creating a
// sync.WaitGroup or just a channel + workers.
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
}
// ZoneDetails fetches information about a zone.
//
// API reference: https://api.cloudflare.com/#zone-zone-details
func (api *API) ZoneDetails(zoneID string) (Zone, error) {
res, err := api.makeRequest("GET", "/zones/"+zoneID, nil)
if err != nil {
return Zone{}, errors.Wrap(err, errMakeRequestError)
}
var r ZoneResponse
err = json.Unmarshal(res, &r)
if err != nil {
return Zone{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// ZoneOptions is a subset of Zone, for editable options.
type ZoneOptions struct {
// FIXME(jamesog): Using omitempty here means we can't disable Paused.
// Currently unsure how to work around this.
Paused bool `json:"paused,omitempty"`
VanityNS []string `json:"vanity_name_servers,omitempty"`
Plan *ZonePlan `json:"plan,omitempty"`
}
// ZoneSetPaused pauses Cloudflare service for the entire zone, sending all
// traffic direct to the origin.
func (api *API) ZoneSetPaused(zoneID string, paused bool) (Zone, error) {
zoneopts := ZoneOptions{Paused: paused}
zone, err := api.EditZone(zoneID, zoneopts)
if err != nil {
return Zone{}, err
}
return zone, nil
}
// ZoneSetVanityNS sets custom nameservers for the zone.
// These names must be within the same zone.
func (api *API) ZoneSetVanityNS(zoneID string, ns []string) (Zone, error) {
zoneopts := ZoneOptions{VanityNS: ns}
zone, err := api.EditZone(zoneID, zoneopts)
if err != nil {
return Zone{}, err
}
return zone, nil
}
// ZoneSetPlan changes the zone plan.
func (api *API) ZoneSetPlan(zoneID string, plan ZonePlan) (Zone, error) {
zoneopts := ZoneOptions{Plan: &plan}
zone, err := api.EditZone(zoneID, zoneopts)
if err != nil {
return Zone{}, err
}
return zone, nil
}
// EditZone edits the given zone.
// This is usually called by ZoneSetPaused, ZoneSetVanityNS or ZoneSetPlan.
//
// API reference: https://api.cloudflare.com/#zone-edit-zone-properties
func (api *API) EditZone(zoneID string, zoneOpts ZoneOptions) (Zone, error) {
res, err := api.makeRequest("PATCH", "/zones/"+zoneID, zoneOpts)
if err != nil {
return Zone{}, errors.Wrap(err, errMakeRequestError)
}
var r ZoneResponse
err = json.Unmarshal(res, &r)
if err != nil {
return Zone{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// PurgeEverything purges the cache for the given zone.
// Note: this will substantially increase load on the origin server for that
// zone if there is a high cached vs. uncached request ratio.
//
// API reference: https://api.cloudflare.com/#zone-purge-all-files
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
}
// PurgeCache purges the cache using the given PurgeCacheRequest (zone/url/tag).
//
// API reference: https://api.cloudflare.com/#zone-purge-individual-files-by-url-and-cache-tags
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
}
// DeleteZone deletes the given zone.
//
// API reference: https://api.cloudflare.com/#zone-delete-a-zone
func (api *API) DeleteZone(zoneID string) (ZoneID, error) {
res, err := api.makeRequest("DELETE", "/zones/"+zoneID, nil)
if err != nil {
return ZoneID{}, errors.Wrap(err, errMakeRequestError)
}
var r ZoneIDResponse
err = json.Unmarshal(res, &r)
if err != nil {
return ZoneID{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// AvailableZonePlans returns information about all plans available to the specified zone.
//
// API reference: https://api.cloudflare.com/#zone-plan-available-plans
func (api *API) AvailableZonePlans(zoneID string) ([]ZonePlan, error) {
uri := "/zones/" + zoneID + "/available_plans"
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return []ZonePlan{}, errors.Wrap(err, errMakeRequestError)
}
var r AvailableZonePlansResponse
err = json.Unmarshal(res, &r)
if err != nil {
return []ZonePlan{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// ZonePlanDetails returns information about a zone plan.
//
// API reference: https://api.cloudflare.com/#zone-plan-plan-details
func (api *API) ZonePlanDetails(zoneID, planID string) (ZonePlan, error) {
uri := "/zones/" + zoneID + "/available_plans/" + planID
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return ZonePlan{}, errors.Wrap(err, errMakeRequestError)
}
var r ZonePlanResponse
err = json.Unmarshal(res, &r)
if err != nil {
return ZonePlan{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// encode encodes non-nil fields into URL encoded form.
func (o ZoneAnalyticsOptions) encode() string {
v := url.Values{}
if o.Since != nil {
v.Set("since", (*o.Since).Format(time.RFC3339))
}
if o.Until != nil {
v.Set("until", (*o.Until).Format(time.RFC3339))
}
if o.Continuous != nil {
v.Set("continuous", fmt.Sprintf("%t", *o.Continuous))
}
return v.Encode()
}
// ZoneAnalyticsDashboard returns zone analytics information.
//
// API reference:
// https://api.cloudflare.com/#zone-analytics-dashboard
// GET /zones/:zone_identifier/analytics/dashboard
func (api *API) ZoneAnalyticsDashboard(zoneID string, options ZoneAnalyticsOptions) (ZoneAnalyticsData, error) {
uri := "/zones/" + zoneID + "/analytics/dashboard" + "?" + options.encode()
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return ZoneAnalyticsData{}, errors.Wrap(err, errMakeRequestError)
}
var r zoneAnalyticsDataResponse
err = json.Unmarshal(res, &r)
if err != nil {
return ZoneAnalyticsData{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// ZoneAnalyticsByColocation returns zone analytics information by datacenter.
//
// API reference:
// https://api.cloudflare.com/#zone-analytics-analytics-by-co-locations
// GET /zones/:zone_identifier/analytics/colos
func (api *API) ZoneAnalyticsByColocation(zoneID string, options ZoneAnalyticsOptions) ([]ZoneAnalyticsColocation, error) {
uri := "/zones/" + zoneID + "/analytics/colos" + "?" + options.encode()
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return nil, errors.Wrap(err, errMakeRequestError)
}
var r zoneAnalyticsColocationResponse
err = json.Unmarshal(res, &r)
if err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}
// 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

584
vendor/github.com/cloudflare/cloudflare-go/zone_test.go generated vendored Normal file
View File

@ -0,0 +1,584 @@
package cloudflare
import (
"fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestZoneAnalyticsDashboard(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method, "Expected method 'GET', got %s", r.Method)
assert.Equal(t, "2015-01-01T12:23:00Z", r.URL.Query().Get("since"))
assert.Equal(t, "2015-01-02T12:23:00Z", r.URL.Query().Get("until"))
assert.Equal(t, "true", r.URL.Query().Get("continuous"))
w.Header().Set("content-type", "application/json")
// JSON data from: https://api.cloudflare.com/#zone-analytics-properties
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"totals": {
"since": "2015-01-01T12:23:00Z",
"until": "2015-01-02T12:23:00Z",
"requests": {
"all": 1234085328,
"cached": 1234085328,
"uncached": 13876154,
"content_type": {
"css": 15343,
"html": 1234213,
"javascript": 318236,
"gif": 23178,
"jpeg": 1982048
},
"country": {
"US": 4181364,
"AG": 37298,
"GI": 293846
},
"ssl": {
"encrypted": 12978361,
"unencrypted": 781263
},
"http_status": {
"200": 13496983,
"301": 283,
"400": 187936,
"402": 1828,
"404": 1293
}
},
"bandwidth": {
"all": 213867451,
"cached": 113205063,
"uncached": 113205063,
"content_type": {
"css": 237421,
"html": 1231290,
"javascript": 123245,
"gif": 1234242,
"jpeg": 784278
},
"country": {
"US": 123145433,
"AG": 2342483,
"GI": 984753
},
"ssl": {
"encrypted": 37592942,
"unencrypted": 237654192
}
},
"threats": {
"all": 23423873,
"country": {
"US": 123,
"CN": 523423,
"AU": 91
},
"type": {
"user.ban.ip": 123,
"hot.ban.unknown": 5324,
"macro.chl.captchaErr": 1341,
"macro.chl.jschlErr": 5323
}
},
"pageviews": {
"all": 5724723,
"search_engines": {
"googlebot": 35272,
"pingdom": 13435,
"bingbot": 5372,
"baidubot": 1345
}
},
"uniques": {
"all": 12343
}
},
"timeseries": [
{
"since": "2015-01-01T12:23:00Z",
"until": "2015-01-02T12:23:00Z",
"requests": {
"all": 1234085328,
"cached": 1234085328,
"uncached": 13876154,
"content_type": {
"css": 15343,
"html": 1234213,
"javascript": 318236,
"gif": 23178,
"jpeg": 1982048
},
"country": {
"US": 4181364,
"AG": 37298,
"GI": 293846
},
"ssl": {
"encrypted": 12978361,
"unencrypted": 781263
},
"http_status": {
"200": 13496983,
"301": 283,
"400": 187936,
"402": 1828,
"404": 1293
}
},
"bandwidth": {
"all": 213867451,
"cached": 113205063,
"uncached": 113205063,
"content_type": {
"css": 237421,
"html": 1231290,
"javascript": 123245,
"gif": 1234242,
"jpeg": 784278
},
"country": {
"US": 123145433,
"AG": 2342483,
"GI": 984753
},
"ssl": {
"encrypted": 37592942,
"unencrypted": 237654192
}
},
"threats": {
"all": 23423873,
"country": {
"US": 123,
"CN": 523423,
"AU": 91
},
"type": {
"user.ban.ip": 123,
"hot.ban.unknown": 5324,
"macro.chl.captchaErr": 1341,
"macro.chl.jschlErr": 5323
}
},
"pageviews": {
"all": 5724723,
"search_engines": {
"googlebot": 35272,
"pingdom": 13435,
"bingbot": 5372,
"baidubot": 1345
}
},
"uniques": {
"all": 12343
}
}
]
},
"query": {
"since": "2015-01-01T12:23:00Z",
"until": "2015-01-02T12:23:00Z",
"time_delta": 60
}
}`)
}
mux.HandleFunc("/zones/foo/analytics/dashboard", handler)
since, _ := time.Parse(time.RFC3339, "2015-01-01T12:23:00Z")
until, _ := time.Parse(time.RFC3339, "2015-01-02T12:23:00Z")
data := ZoneAnalytics{
Since: since,
Until: until,
Requests: struct {
All int `json:"all"`
Cached int `json:"cached"`
Uncached int `json:"uncached"`
ContentType map[string]int `json:"content_type"`
Country map[string]int `json:"country"`
SSL struct {
Encrypted int `json:"encrypted"`
Unencrypted int `json:"unencrypted"`
} `json:"ssl"`
HTTPStatus map[string]int `json:"http_status"`
}{
All: 1234085328,
Cached: 1234085328,
Uncached: 13876154,
ContentType: map[string]int{
"css": 15343,
"html": 1234213,
"javascript": 318236,
"gif": 23178,
"jpeg": 1982048,
},
Country: map[string]int{
"US": 4181364,
"AG": 37298,
"GI": 293846,
},
SSL: struct {
Encrypted int `json:"encrypted"`
Unencrypted int `json:"unencrypted"`
}{
Encrypted: 12978361,
Unencrypted: 781263,
},
HTTPStatus: map[string]int{
"200": 13496983,
"301": 283,
"400": 187936,
"402": 1828,
"404": 1293,
},
},
Bandwidth: struct {
All int `json:"all"`
Cached int `json:"cached"`
Uncached int `json:"uncached"`
ContentType map[string]int `json:"content_type"`
Country map[string]int `json:"country"`
SSL struct {
Encrypted int `json:"encrypted"`
Unencrypted int `json:"unencrypted"`
} `json:"ssl"`
}{
All: 213867451,
Cached: 113205063,
Uncached: 113205063,
ContentType: map[string]int{
"css": 237421,
"html": 1231290,
"javascript": 123245,
"gif": 1234242,
"jpeg": 784278,
},
Country: map[string]int{
"US": 123145433,
"AG": 2342483,
"GI": 984753,
},
SSL: struct {
Encrypted int `json:"encrypted"`
Unencrypted int `json:"unencrypted"`
}{
Encrypted: 37592942,
Unencrypted: 237654192,
},
},
Threats: struct {
All int `json:"all"`
Country map[string]int `json:"country"`
Type map[string]int `json:"type"`
}{
All: 23423873,
Country: map[string]int{
"US": 123,
"CN": 523423,
"AU": 91,
},
Type: map[string]int{
"user.ban.ip": 123,
"hot.ban.unknown": 5324,
"macro.chl.captchaErr": 1341,
"macro.chl.jschlErr": 5323,
},
},
Pageviews: struct {
All int `json:"all"`
SearchEngines map[string]int `json:"search_engines"`
}{
All: 5724723,
SearchEngines: map[string]int{
"googlebot": 35272,
"pingdom": 13435,
"bingbot": 5372,
"baidubot": 1345,
},
},
Uniques: struct {
All int `json:"all"`
}{
All: 12343,
},
}
want := ZoneAnalyticsData{
Totals: data,
Timeseries: []ZoneAnalytics{data},
}
continuous := true
d, err := client.ZoneAnalyticsDashboard("foo", ZoneAnalyticsOptions{
Since: &since,
Until: &until,
Continuous: &continuous,
})
if assert.NoError(t, err) {
assert.Equal(t, want, d)
}
_, err = client.ZoneAnalyticsDashboard("bar", ZoneAnalyticsOptions{})
assert.Error(t, err)
}
func TestZoneAnalyticsByColocation(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method, "Expected method 'GET', got %s", r.Method)
assert.Equal(t, "2015-01-01T12:23:00Z", r.URL.Query().Get("since"))
assert.Equal(t, "2015-01-02T12:23:00Z", r.URL.Query().Get("until"))
assert.Equal(t, "true", r.URL.Query().Get("continuous"))
w.Header().Set("content-type", "application/json")
// JSON data from: https://api.cloudflare.com/#zone-analytics-analytics-by-co-locations
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": [
{
"colo_id": "SFO",
"timeseries": [
{
"since": "2015-01-01T12:23:00Z",
"until": "2015-01-02T12:23:00Z",
"requests": {
"all": 1234085328,
"cached": 1234085328,
"uncached": 13876154,
"content_type": {
"css": 15343,
"html": 1234213,
"javascript": 318236,
"gif": 23178,
"jpeg": 1982048
},
"country": {
"US": 4181364,
"AG": 37298,
"GI": 293846
},
"ssl": {
"encrypted": 12978361,
"unencrypted": 781263
},
"http_status": {
"200": 13496983,
"301": 283,
"400": 187936,
"402": 1828,
"404": 1293
}
},
"bandwidth": {
"all": 213867451,
"cached": 113205063,
"uncached": 113205063,
"content_type": {
"css": 237421,
"html": 1231290,
"javascript": 123245,
"gif": 1234242,
"jpeg": 784278
},
"country": {
"US": 123145433,
"AG": 2342483,
"GI": 984753
},
"ssl": {
"encrypted": 37592942,
"unencrypted": 237654192
}
},
"threats": {
"all": 23423873,
"country": {
"US": 123,
"CN": 523423,
"AU": 91
},
"type": {
"user.ban.ip": 123,
"hot.ban.unknown": 5324,
"macro.chl.captchaErr": 1341,
"macro.chl.jschlErr": 5323
}
},
"pageviews": {
"all": 5724723,
"search_engines": {
"googlebot": 35272,
"pingdom": 13435,
"bingbot": 5372,
"baidubot": 1345
}
},
"uniques": {
"all": 12343
}
}
]
}
],
"query": {
"since": "2015-01-01T12:23:00Z",
"until": "2015-01-02T12:23:00Z",
"time_delta": 60
}
}`)
}
mux.HandleFunc("/zones/foo/analytics/colos", handler)
since, _ := time.Parse(time.RFC3339, "2015-01-01T12:23:00Z")
until, _ := time.Parse(time.RFC3339, "2015-01-02T12:23:00Z")
data := ZoneAnalytics{
Since: since,
Until: until,
Requests: struct {
All int `json:"all"`
Cached int `json:"cached"`
Uncached int `json:"uncached"`
ContentType map[string]int `json:"content_type"`
Country map[string]int `json:"country"`
SSL struct {
Encrypted int `json:"encrypted"`
Unencrypted int `json:"unencrypted"`
} `json:"ssl"`
HTTPStatus map[string]int `json:"http_status"`
}{
All: 1234085328,
Cached: 1234085328,
Uncached: 13876154,
ContentType: map[string]int{
"css": 15343,
"html": 1234213,
"javascript": 318236,
"gif": 23178,
"jpeg": 1982048,
},
Country: map[string]int{
"US": 4181364,
"AG": 37298,
"GI": 293846,
},
SSL: struct {
Encrypted int `json:"encrypted"`
Unencrypted int `json:"unencrypted"`
}{
Encrypted: 12978361,
Unencrypted: 781263,
},
HTTPStatus: map[string]int{
"200": 13496983,
"301": 283,
"400": 187936,
"402": 1828,
"404": 1293,
},
},
Bandwidth: struct {
All int `json:"all"`
Cached int `json:"cached"`
Uncached int `json:"uncached"`
ContentType map[string]int `json:"content_type"`
Country map[string]int `json:"country"`
SSL struct {
Encrypted int `json:"encrypted"`
Unencrypted int `json:"unencrypted"`
} `json:"ssl"`
}{
All: 213867451,
Cached: 113205063,
Uncached: 113205063,
ContentType: map[string]int{
"css": 237421,
"html": 1231290,
"javascript": 123245,
"gif": 1234242,
"jpeg": 784278,
},
Country: map[string]int{
"US": 123145433,
"AG": 2342483,
"GI": 984753,
},
SSL: struct {
Encrypted int `json:"encrypted"`
Unencrypted int `json:"unencrypted"`
}{
Encrypted: 37592942,
Unencrypted: 237654192,
},
},
Threats: struct {
All int `json:"all"`
Country map[string]int `json:"country"`
Type map[string]int `json:"type"`
}{
All: 23423873,
Country: map[string]int{
"US": 123,
"CN": 523423,
"AU": 91,
},
Type: map[string]int{
"user.ban.ip": 123,
"hot.ban.unknown": 5324,
"macro.chl.captchaErr": 1341,
"macro.chl.jschlErr": 5323,
},
},
Pageviews: struct {
All int `json:"all"`
SearchEngines map[string]int `json:"search_engines"`
}{
All: 5724723,
SearchEngines: map[string]int{
"googlebot": 35272,
"pingdom": 13435,
"bingbot": 5372,
"baidubot": 1345,
},
},
Uniques: struct {
All int `json:"all"`
}{
All: 12343,
},
}
want := []ZoneAnalyticsColocation{
{
ColocationID: "SFO",
Timeseries: []ZoneAnalytics{data},
},
}
continuous := true
d, err := client.ZoneAnalyticsByColocation("foo", ZoneAnalyticsOptions{
Since: &since,
Until: &until,
Continuous: &continuous,
})
if assert.NoError(t, err) {
assert.Equal(t, want, d)
}
_, err = client.ZoneAnalyticsDashboard("bar", ZoneAnalyticsOptions{})
assert.Error(t, err)
}

24
vendor/github.com/pkg/errors/.gitignore generated vendored Normal file
View File

@ -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

11
vendor/github.com/pkg/errors/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,11 @@
language: go
go_import_path: github.com/pkg/errors
go:
- 1.4.3
- 1.5.4
- 1.6.2
- 1.7.1
- tip
script:
- go test -v ./...

23
vendor/github.com/pkg/errors/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
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.

52
vendor/github.com/pkg/errors/README.md generated vendored Normal file
View File

@ -0,0 +1,52 @@
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![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 provides simple error handling primitives.
`go get github.com/pkg/errors`
The traditional error handling idiom in Go is roughly akin to
```go
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
```go
_, err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrap(err, "read failed")
}
```
## 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 can be inspected by `errors.Cause`.
```go
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:
```go
switch err := errors.Cause(err).(type) {
case *MyError:
// handle specifically
default:
// unknown error
}
```
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
## 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
BSD-2-Clause

32
vendor/github.com/pkg/errors/appveyor.yml generated vendored Normal file
View File

@ -0,0 +1,32 @@
version: build-{build}.{branch}
clone_folder: C:\gopath\src\github.com\pkg\errors
shallow_clone: true # for startup speed
environment:
GOPATH: C:\gopath
platform:
- x64
# http://www.appveyor.com/docs/installed-software
install:
# some helpful output for debugging builds
- go version
- go env
# pre-installed MinGW at C:\MinGW is 32bit only
# but MSYS2 at C:\msys64 has mingw64
- set PATH=C:\msys64\mingw64\bin;%PATH%
- gcc --version
- g++ --version
build_script:
- go install -v ./...
test_script:
- set PATH=C:\gopath\bin;%PATH%
- go test -v ./...
#artifacts:
# - path: '%GOPATH%\bin\*.exe'
deploy: off

59
vendor/github.com/pkg/errors/bench_test.go generated vendored Normal file
View File

@ -0,0 +1,59 @@
// +build go1.7
package errors
import (
"fmt"
"testing"
stderrors "errors"
)
func noErrors(at, depth int) error {
if at >= depth {
return stderrors.New("no error")
}
return noErrors(at+1, depth)
}
func yesErrors(at, depth int) error {
if at >= depth {
return New("ye error")
}
return yesErrors(at+1, depth)
}
func BenchmarkErrors(b *testing.B) {
var toperr error
type run struct {
stack int
std bool
}
runs := []run{
{10, false},
{10, true},
{100, false},
{100, true},
{1000, false},
{1000, true},
}
for _, r := range runs {
part := "pkg/errors"
if r.std {
part = "errors"
}
name := fmt.Sprintf("%s-stack-%d", part, r.stack)
b.Run(name, func(b *testing.B) {
var err error
f := yesErrors
if r.std {
f = noErrors
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
err = f(0, r.stack)
}
b.StopTimer()
toperr = err
})
}
}

269
vendor/github.com/pkg/errors/errors.go generated vendored Normal file
View File

@ -0,0 +1,269 @@
// Package errors provides simple error handling primitives.
//
// 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 by recording a stack trace at the point Wrap is called,
// and the supplied message. For example
//
// _, err := ioutil.ReadAll(r)
// if err != nil {
// return errors.Wrap(err, "read failed")
// }
//
// If additional control is required the errors.WithStack and errors.WithMessage
// functions destructure errors.Wrap into its component operations of annotating
// an error with a stack trace and an a message, respectively.
//
// 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 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
// }
//
// causer interface is not exported by this package, but is considered a part
// of stable public API.
//
// Formatted printing of errors
//
// All error values returned from this package implement fmt.Formatter and can
// be formatted by the fmt package. The following verbs are supported
//
// %s print the error. If the error has a Cause it will be
// printed recursively
// %v see %s
// %+v extended format. Each Frame of the error's StackTrace will
// be printed in detail.
//
// Retrieving the stack trace of an error or wrapper
//
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
// invoked. This information can be retrieved with the following interface.
//
// type stackTracer interface {
// StackTrace() errors.StackTrace
// }
//
// Where errors.StackTrace is defined as
//
// type StackTrace []Frame
//
// The Frame type represents a call site in the stack trace. Frame supports
// the fmt.Formatter interface that can be used for printing information about
// the stack trace of this error. For example:
//
// if err, ok := err.(stackTracer); ok {
// for _, f := range err.StackTrace() {
// fmt.Printf("%+s:%d", f)
// }
// }
//
// stackTracer interface is not exported by this package, but is considered a part
// of stable public API.
//
// See the documentation for Frame.Format for more details.
package errors
import (
"fmt"
"io"
)
// New returns an error with the supplied message.
// New also records the stack trace at the point it was called.
func New(message string) error {
return &fundamental{
msg: message,
stack: callers(),
}
}
// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
// Errorf also records the stack trace at the point it was called.
func Errorf(format string, args ...interface{}) error {
return &fundamental{
msg: fmt.Sprintf(format, args...),
stack: callers(),
}
}
// fundamental is an error that has a message and a stack, but no caller.
type fundamental struct {
msg string
*stack
}
func (f *fundamental) Error() string { return f.msg }
func (f *fundamental) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, f.msg)
f.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, f.msg)
case 'q':
fmt.Fprintf(s, "%q", f.msg)
}
}
// WithStack annotates err with a stack trace at the point WithStack was called.
// If err is nil, WithStack returns nil.
func WithStack(err error) error {
if err == nil {
return nil
}
return &withStack{
err,
callers(),
}
}
type withStack struct {
error
*stack
}
func (w *withStack) Cause() error { return w.error }
func (w *withStack) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v", w.Cause())
w.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, w.Error())
case 'q':
fmt.Fprintf(s, "%q", w.Error())
}
}
// Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: message,
}
return &withStack{
err,
callers(),
}
}
// Wrapf returns an error annotating err with a stack trace
// at the point Wrapf is call, and the format specifier.
// If err is nil, Wrapf returns nil.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: fmt.Sprintf(format, args...),
}
return &withStack{
err,
callers(),
}
}
// WithMessage annotates err with a new message.
// If err is nil, WithMessage returns nil.
func WithMessage(err error, message string) error {
if err == nil {
return nil
}
return &withMessage{
cause: err,
msg: message,
}
}
type withMessage struct {
cause error
msg string
}
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
func (w *withMessage) Cause() error { return w.cause }
func (w *withMessage) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v\n", w.Cause())
io.WriteString(s, w.msg)
return
}
fallthrough
case 's', 'q':
io.WriteString(s, w.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 {
type causer interface {
Cause() error
}
for err != nil {
cause, ok := err.(causer)
if !ok {
break
}
err = cause.Cause()
}
return err
}

226
vendor/github.com/pkg/errors/errors_test.go generated vendored Normal file
View File

@ -0,0 +1,226 @@
package errors
import (
"errors"
"fmt"
"io"
"reflect"
"testing"
)
func TestNew(t *testing.T) {
tests := []struct {
err string
want error
}{
{"", fmt.Errorf("")},
{"foo", fmt.Errorf("foo")},
{"foo", New("foo")},
{"string with format specifiers: %v", errors.New("string with format specifiers: %v")},
}
for _, tt := range tests {
got := New(tt.err)
if got.Error() != tt.want.Error() {
t.Errorf("New.Error(): got: %q, want %q", got, tt.want)
}
}
}
func TestWrapNil(t *testing.T) {
got := Wrap(nil, "no error")
if got != nil {
t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got)
}
}
func TestWrap(t *testing.T) {
tests := []struct {
err error
message string
want string
}{
{io.EOF, "read error", "read error: EOF"},
{Wrap(io.EOF, "read error"), "client error", "client error: read error: EOF"},
}
for _, tt := range tests {
got := Wrap(tt.err, tt.message).Error()
if got != tt.want {
t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want)
}
}
}
type nilError struct{}
func (nilError) Error() string { return "nil error" }
func TestCause(t *testing.T) {
x := New("error")
tests := []struct {
err error
want error
}{{
// nil error is nil
err: nil,
want: nil,
}, {
// explicit nil error is nil
err: (error)(nil),
want: nil,
}, {
// typed nil is nil
err: (*nilError)(nil),
want: (*nilError)(nil),
}, {
// uncaused error is unaffected
err: io.EOF,
want: io.EOF,
}, {
// caused error returns cause
err: Wrap(io.EOF, "ignored"),
want: io.EOF,
}, {
err: x, // return from errors.New
want: x,
}, {
WithMessage(nil, "whoops"),
nil,
}, {
WithMessage(io.EOF, "whoops"),
io.EOF,
}, {
WithStack(nil),
nil,
}, {
WithStack(io.EOF),
io.EOF,
}}
for i, tt := range tests {
got := Cause(tt.err)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want)
}
}
}
func TestWrapfNil(t *testing.T) {
got := Wrapf(nil, "no error")
if got != nil {
t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got)
}
}
func TestWrapf(t *testing.T) {
tests := []struct {
err error
message string
want string
}{
{io.EOF, "read error", "read error: EOF"},
{Wrapf(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"},
{Wrapf(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"},
}
for _, tt := range tests {
got := Wrapf(tt.err, tt.message).Error()
if got != tt.want {
t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want)
}
}
}
func TestErrorf(t *testing.T) {
tests := []struct {
err error
want string
}{
{Errorf("read error without format specifiers"), "read error without format specifiers"},
{Errorf("read error with %d format specifier", 1), "read error with 1 format specifier"},
}
for _, tt := range tests {
got := tt.err.Error()
if got != tt.want {
t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want)
}
}
}
func TestWithStackNil(t *testing.T) {
got := WithStack(nil)
if got != nil {
t.Errorf("WithStack(nil): got %#v, expected nil", got)
}
}
func TestWithStack(t *testing.T) {
tests := []struct {
err error
want string
}{
{io.EOF, "EOF"},
{WithStack(io.EOF), "EOF"},
}
for _, tt := range tests {
got := WithStack(tt.err).Error()
if got != tt.want {
t.Errorf("WithStack(%v): got: %v, want %v", tt.err, got, tt.want)
}
}
}
func TestWithMessageNil(t *testing.T) {
got := WithMessage(nil, "no error")
if got != nil {
t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got)
}
}
func TestWithMessage(t *testing.T) {
tests := []struct {
err error
message string
want string
}{
{io.EOF, "read error", "read error: EOF"},
{WithMessage(io.EOF, "read error"), "client error", "client error: read error: EOF"},
}
for _, tt := range tests {
got := WithMessage(tt.err, tt.message).Error()
if got != tt.want {
t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want)
}
}
}
// errors.New, etc values are not expected to be compared by value
// but the change in errors#27 made them incomparable. Assert that
// various kinds of errors have a functional equality operator, even
// if the result of that equality is always false.
func TestErrorEquality(t *testing.T) {
vals := []error{
nil,
io.EOF,
errors.New("EOF"),
New("EOF"),
Errorf("EOF"),
Wrap(io.EOF, "EOF"),
Wrapf(io.EOF, "EOF%d", 2),
WithMessage(nil, "whoops"),
WithMessage(io.EOF, "whoops"),
WithStack(io.EOF),
WithStack(nil),
}
for i := range vals {
for j := range vals {
_ = vals[i] == vals[j] // mustn't panic
}
}
}

205
vendor/github.com/pkg/errors/example_test.go generated vendored Normal file
View File

@ -0,0 +1,205 @@
package errors_test
import (
"fmt"
"github.com/pkg/errors"
)
func ExampleNew() {
err := errors.New("whoops")
fmt.Println(err)
// Output: whoops
}
func ExampleNew_printf() {
err := errors.New("whoops")
fmt.Printf("%+v", err)
// Example output:
// whoops
// github.com/pkg/errors_test.ExampleNew_printf
// /home/dfc/src/github.com/pkg/errors/example_test.go:17
// testing.runExample
// /home/dfc/go/src/testing/example.go:114
// testing.RunExamples
// /home/dfc/go/src/testing/example.go:38
// testing.(*M).Run
// /home/dfc/go/src/testing/testing.go:744
// main.main
// /github.com/pkg/errors/_test/_testmain.go:106
// runtime.main
// /home/dfc/go/src/runtime/proc.go:183
// runtime.goexit
// /home/dfc/go/src/runtime/asm_amd64.s:2059
}
func ExampleWithMessage() {
cause := errors.New("whoops")
err := errors.WithMessage(cause, "oh noes")
fmt.Println(err)
// Output: oh noes: whoops
}
func ExampleWithStack() {
cause := errors.New("whoops")
err := errors.WithStack(cause)
fmt.Println(err)
// Output: whoops
}
func ExampleWithStack_printf() {
cause := errors.New("whoops")
err := errors.WithStack(cause)
fmt.Printf("%+v", err)
// Example Output:
// whoops
// github.com/pkg/errors_test.ExampleWithStack_printf
// /home/fabstu/go/src/github.com/pkg/errors/example_test.go:55
// testing.runExample
// /usr/lib/go/src/testing/example.go:114
// testing.RunExamples
// /usr/lib/go/src/testing/example.go:38
// testing.(*M).Run
// /usr/lib/go/src/testing/testing.go:744
// main.main
// github.com/pkg/errors/_test/_testmain.go:106
// runtime.main
// /usr/lib/go/src/runtime/proc.go:183
// runtime.goexit
// /usr/lib/go/src/runtime/asm_amd64.s:2086
// github.com/pkg/errors_test.ExampleWithStack_printf
// /home/fabstu/go/src/github.com/pkg/errors/example_test.go:56
// testing.runExample
// /usr/lib/go/src/testing/example.go:114
// testing.RunExamples
// /usr/lib/go/src/testing/example.go:38
// testing.(*M).Run
// /usr/lib/go/src/testing/testing.go:744
// main.main
// github.com/pkg/errors/_test/_testmain.go:106
// runtime.main
// /usr/lib/go/src/runtime/proc.go:183
// runtime.goexit
// /usr/lib/go/src/runtime/asm_amd64.s:2086
}
func ExampleWrap() {
cause := errors.New("whoops")
err := errors.Wrap(cause, "oh noes")
fmt.Println(err)
// Output: oh noes: whoops
}
func fn() error {
e1 := errors.New("error")
e2 := errors.Wrap(e1, "inner")
e3 := errors.Wrap(e2, "middle")
return errors.Wrap(e3, "outer")
}
func ExampleCause() {
err := fn()
fmt.Println(err)
fmt.Println(errors.Cause(err))
// Output: outer: middle: inner: error
// error
}
func ExampleWrap_extended() {
err := fn()
fmt.Printf("%+v\n", err)
// Example output:
// error
// github.com/pkg/errors_test.fn
// /home/dfc/src/github.com/pkg/errors/example_test.go:47
// github.com/pkg/errors_test.ExampleCause_printf
// /home/dfc/src/github.com/pkg/errors/example_test.go:63
// testing.runExample
// /home/dfc/go/src/testing/example.go:114
// testing.RunExamples
// /home/dfc/go/src/testing/example.go:38
// testing.(*M).Run
// /home/dfc/go/src/testing/testing.go:744
// main.main
// /github.com/pkg/errors/_test/_testmain.go:104
// runtime.main
// /home/dfc/go/src/runtime/proc.go:183
// runtime.goexit
// /home/dfc/go/src/runtime/asm_amd64.s:2059
// github.com/pkg/errors_test.fn
// /home/dfc/src/github.com/pkg/errors/example_test.go:48: inner
// github.com/pkg/errors_test.fn
// /home/dfc/src/github.com/pkg/errors/example_test.go:49: middle
// github.com/pkg/errors_test.fn
// /home/dfc/src/github.com/pkg/errors/example_test.go:50: outer
}
func ExampleWrapf() {
cause := errors.New("whoops")
err := errors.Wrapf(cause, "oh noes #%d", 2)
fmt.Println(err)
// Output: oh noes #2: whoops
}
func ExampleErrorf_extended() {
err := errors.Errorf("whoops: %s", "foo")
fmt.Printf("%+v", err)
// Example output:
// whoops: foo
// github.com/pkg/errors_test.ExampleErrorf
// /home/dfc/src/github.com/pkg/errors/example_test.go:101
// testing.runExample
// /home/dfc/go/src/testing/example.go:114
// testing.RunExamples
// /home/dfc/go/src/testing/example.go:38
// testing.(*M).Run
// /home/dfc/go/src/testing/testing.go:744
// main.main
// /github.com/pkg/errors/_test/_testmain.go:102
// runtime.main
// /home/dfc/go/src/runtime/proc.go:183
// runtime.goexit
// /home/dfc/go/src/runtime/asm_amd64.s:2059
}
func Example_stackTrace() {
type stackTracer interface {
StackTrace() errors.StackTrace
}
err, ok := errors.Cause(fn()).(stackTracer)
if !ok {
panic("oops, err does not implement stackTracer")
}
st := err.StackTrace()
fmt.Printf("%+v", st[0:2]) // top two frames
// Example output:
// github.com/pkg/errors_test.fn
// /home/dfc/src/github.com/pkg/errors/example_test.go:47
// github.com/pkg/errors_test.Example_stackTrace
// /home/dfc/src/github.com/pkg/errors/example_test.go:127
}
func ExampleCause_printf() {
err := errors.Wrap(func() error {
return func() error {
return errors.Errorf("hello %s", fmt.Sprintf("world"))
}()
}(), "failed")
fmt.Printf("%v", err)
// Output: failed: hello world
}

535
vendor/github.com/pkg/errors/format_test.go generated vendored Normal file
View File

@ -0,0 +1,535 @@
package errors
import (
"errors"
"fmt"
"io"
"regexp"
"strings"
"testing"
)
func TestFormatNew(t *testing.T) {
tests := []struct {
error
format string
want string
}{{
New("error"),
"%s",
"error",
}, {
New("error"),
"%v",
"error",
}, {
New("error"),
"%+v",
"error\n" +
"github.com/pkg/errors.TestFormatNew\n" +
"\t.+/github.com/pkg/errors/format_test.go:26",
}, {
New("error"),
"%q",
`"error"`,
}}
for i, tt := range tests {
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
}
}
func TestFormatErrorf(t *testing.T) {
tests := []struct {
error
format string
want string
}{{
Errorf("%s", "error"),
"%s",
"error",
}, {
Errorf("%s", "error"),
"%v",
"error",
}, {
Errorf("%s", "error"),
"%+v",
"error\n" +
"github.com/pkg/errors.TestFormatErrorf\n" +
"\t.+/github.com/pkg/errors/format_test.go:56",
}}
for i, tt := range tests {
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
}
}
func TestFormatWrap(t *testing.T) {
tests := []struct {
error
format string
want string
}{{
Wrap(New("error"), "error2"),
"%s",
"error2: error",
}, {
Wrap(New("error"), "error2"),
"%v",
"error2: error",
}, {
Wrap(New("error"), "error2"),
"%+v",
"error\n" +
"github.com/pkg/errors.TestFormatWrap\n" +
"\t.+/github.com/pkg/errors/format_test.go:82",
}, {
Wrap(io.EOF, "error"),
"%s",
"error: EOF",
}, {
Wrap(io.EOF, "error"),
"%v",
"error: EOF",
}, {
Wrap(io.EOF, "error"),
"%+v",
"EOF\n" +
"error\n" +
"github.com/pkg/errors.TestFormatWrap\n" +
"\t.+/github.com/pkg/errors/format_test.go:96",
}, {
Wrap(Wrap(io.EOF, "error1"), "error2"),
"%+v",
"EOF\n" +
"error1\n" +
"github.com/pkg/errors.TestFormatWrap\n" +
"\t.+/github.com/pkg/errors/format_test.go:103\n",
}, {
Wrap(New("error with space"), "context"),
"%q",
`"context: error with space"`,
}}
for i, tt := range tests {
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
}
}
func TestFormatWrapf(t *testing.T) {
tests := []struct {
error
format string
want string
}{{
Wrapf(io.EOF, "error%d", 2),
"%s",
"error2: EOF",
}, {
Wrapf(io.EOF, "error%d", 2),
"%v",
"error2: EOF",
}, {
Wrapf(io.EOF, "error%d", 2),
"%+v",
"EOF\n" +
"error2\n" +
"github.com/pkg/errors.TestFormatWrapf\n" +
"\t.+/github.com/pkg/errors/format_test.go:134",
}, {
Wrapf(New("error"), "error%d", 2),
"%s",
"error2: error",
}, {
Wrapf(New("error"), "error%d", 2),
"%v",
"error2: error",
}, {
Wrapf(New("error"), "error%d", 2),
"%+v",
"error\n" +
"github.com/pkg/errors.TestFormatWrapf\n" +
"\t.+/github.com/pkg/errors/format_test.go:149",
}}
for i, tt := range tests {
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
}
}
func TestFormatWithStack(t *testing.T) {
tests := []struct {
error
format string
want []string
}{{
WithStack(io.EOF),
"%s",
[]string{"EOF"},
}, {
WithStack(io.EOF),
"%v",
[]string{"EOF"},
}, {
WithStack(io.EOF),
"%+v",
[]string{"EOF",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:175"},
}, {
WithStack(New("error")),
"%s",
[]string{"error"},
}, {
WithStack(New("error")),
"%v",
[]string{"error"},
}, {
WithStack(New("error")),
"%+v",
[]string{"error",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:189",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:189"},
}, {
WithStack(WithStack(io.EOF)),
"%+v",
[]string{"EOF",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:197",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:197"},
}, {
WithStack(WithStack(Wrapf(io.EOF, "message"))),
"%+v",
[]string{"EOF",
"message",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:205",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:205",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:205"},
}, {
WithStack(Errorf("error%d", 1)),
"%+v",
[]string{"error1",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:216",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:216"},
}}
for i, tt := range tests {
testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
}
}
func TestFormatWithMessage(t *testing.T) {
tests := []struct {
error
format string
want []string
}{{
WithMessage(New("error"), "error2"),
"%s",
[]string{"error2: error"},
}, {
WithMessage(New("error"), "error2"),
"%v",
[]string{"error2: error"},
}, {
WithMessage(New("error"), "error2"),
"%+v",
[]string{
"error",
"github.com/pkg/errors.TestFormatWithMessage\n" +
"\t.+/github.com/pkg/errors/format_test.go:244",
"error2"},
}, {
WithMessage(io.EOF, "addition1"),
"%s",
[]string{"addition1: EOF"},
}, {
WithMessage(io.EOF, "addition1"),
"%v",
[]string{"addition1: EOF"},
}, {
WithMessage(io.EOF, "addition1"),
"%+v",
[]string{"EOF", "addition1"},
}, {
WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
"%v",
[]string{"addition2: addition1: EOF"},
}, {
WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
"%+v",
[]string{"EOF", "addition1", "addition2"},
}, {
Wrap(WithMessage(io.EOF, "error1"), "error2"),
"%+v",
[]string{"EOF", "error1", "error2",
"github.com/pkg/errors.TestFormatWithMessage\n" +
"\t.+/github.com/pkg/errors/format_test.go:272"},
}, {
WithMessage(Errorf("error%d", 1), "error2"),
"%+v",
[]string{"error1",
"github.com/pkg/errors.TestFormatWithMessage\n" +
"\t.+/github.com/pkg/errors/format_test.go:278",
"error2"},
}, {
WithMessage(WithStack(io.EOF), "error"),
"%+v",
[]string{
"EOF",
"github.com/pkg/errors.TestFormatWithMessage\n" +
"\t.+/github.com/pkg/errors/format_test.go:285",
"error"},
}, {
WithMessage(Wrap(WithStack(io.EOF), "inside-error"), "outside-error"),
"%+v",
[]string{
"EOF",
"github.com/pkg/errors.TestFormatWithMessage\n" +
"\t.+/github.com/pkg/errors/format_test.go:293",
"inside-error",
"github.com/pkg/errors.TestFormatWithMessage\n" +
"\t.+/github.com/pkg/errors/format_test.go:293",
"outside-error"},
}}
for i, tt := range tests {
testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
}
}
func TestFormatGeneric(t *testing.T) {
starts := []struct {
err error
want []string
}{
{New("new-error"), []string{
"new-error",
"github.com/pkg/errors.TestFormatGeneric\n" +
"\t.+/github.com/pkg/errors/format_test.go:315"},
}, {Errorf("errorf-error"), []string{
"errorf-error",
"github.com/pkg/errors.TestFormatGeneric\n" +
"\t.+/github.com/pkg/errors/format_test.go:319"},
}, {errors.New("errors-new-error"), []string{
"errors-new-error"},
},
}
wrappers := []wrapper{
{
func(err error) error { return WithMessage(err, "with-message") },
[]string{"with-message"},
}, {
func(err error) error { return WithStack(err) },
[]string{
"github.com/pkg/errors.(func·002|TestFormatGeneric.func2)\n\t" +
".+/github.com/pkg/errors/format_test.go:333",
},
}, {
func(err error) error { return Wrap(err, "wrap-error") },
[]string{
"wrap-error",
"github.com/pkg/errors.(func·003|TestFormatGeneric.func3)\n\t" +
".+/github.com/pkg/errors/format_test.go:339",
},
}, {
func(err error) error { return Wrapf(err, "wrapf-error%d", 1) },
[]string{
"wrapf-error1",
"github.com/pkg/errors.(func·004|TestFormatGeneric.func4)\n\t" +
".+/github.com/pkg/errors/format_test.go:346",
},
},
}
for s := range starts {
err := starts[s].err
want := starts[s].want
testFormatCompleteCompare(t, s, err, "%+v", want, false)
testGenericRecursive(t, err, want, wrappers, 3)
}
}
func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) {
got := fmt.Sprintf(format, arg)
gotLines := strings.SplitN(got, "\n", -1)
wantLines := strings.SplitN(want, "\n", -1)
if len(wantLines) > len(gotLines) {
t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want)
return
}
for i, w := range wantLines {
match, err := regexp.MatchString(w, gotLines[i])
if err != nil {
t.Fatal(err)
}
if !match {
t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want)
}
}
}
var stackLineR = regexp.MustCompile(`\.`)
// parseBlocks parses input into a slice, where:
// - incase entry contains a newline, its a stacktrace
// - incase entry contains no newline, its a solo line.
//
// Detecting stack boundaries only works incase the WithStack-calls are
// to be found on the same line, thats why it is optionally here.
//
// Example use:
//
// for _, e := range blocks {
// if strings.ContainsAny(e, "\n") {
// // Match as stack
// } else {
// // Match as line
// }
// }
//
func parseBlocks(input string, detectStackboundaries bool) ([]string, error) {
var blocks []string
stack := ""
wasStack := false
lines := map[string]bool{} // already found lines
for _, l := range strings.Split(input, "\n") {
isStackLine := stackLineR.MatchString(l)
switch {
case !isStackLine && wasStack:
blocks = append(blocks, stack, l)
stack = ""
lines = map[string]bool{}
case isStackLine:
if wasStack {
// Detecting two stacks after another, possible cause lines match in
// our tests due to WithStack(WithStack(io.EOF)) on same line.
if detectStackboundaries {
if lines[l] {
if len(stack) == 0 {
return nil, errors.New("len of block must not be zero here")
}
blocks = append(blocks, stack)
stack = l
lines = map[string]bool{l: true}
continue
}
}
stack = stack + "\n" + l
} else {
stack = l
}
lines[l] = true
case !isStackLine && !wasStack:
blocks = append(blocks, l)
default:
return nil, errors.New("must not happen")
}
wasStack = isStackLine
}
// Use up stack
if stack != "" {
blocks = append(blocks, stack)
}
return blocks, nil
}
func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) {
gotStr := fmt.Sprintf(format, arg)
got, err := parseBlocks(gotStr, detectStackBoundaries)
if err != nil {
t.Fatal(err)
}
if len(got) != len(want) {
t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q",
n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr)
}
for i := range got {
if strings.ContainsAny(want[i], "\n") {
// Match as stack
match, err := regexp.MatchString(want[i], got[i])
if err != nil {
t.Fatal(err)
}
if !match {
t.Fatalf("test %d: block %d: fmt.Sprintf(%q, err):\ngot:\n%q\nwant:\n%q\nall-got:\n%s\nall-want:\n%s\n",
n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want))
}
} else {
// Match as message
if got[i] != want[i] {
t.Fatalf("test %d: fmt.Sprintf(%s, err) at block %d got != want:\n got: %q\nwant: %q", n+1, format, i+1, got[i], want[i])
}
}
}
}
type wrapper struct {
wrap func(err error) error
want []string
}
func prettyBlocks(blocks []string, prefix ...string) string {
var out []string
for _, b := range blocks {
out = append(out, fmt.Sprintf("%v", b))
}
return " " + strings.Join(out, "\n ")
}
func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) {
if len(beforeWant) == 0 {
panic("beforeWant must not be empty")
}
for _, w := range list {
if len(w.want) == 0 {
panic("want must not be empty")
}
err := w.wrap(beforeErr)
// Copy required cause append(beforeWant, ..) modified beforeWant subtly.
beforeCopy := make([]string, len(beforeWant))
copy(beforeCopy, beforeWant)
beforeWant := beforeCopy
last := len(beforeWant) - 1
var want []string
// Merge two stacks behind each other.
if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") {
want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...)
} else {
want = append(beforeWant, w.want...)
}
testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false)
if maxDepth > 0 {
testGenericRecursive(t, err, want, list, maxDepth-1)
}
}
}

178
vendor/github.com/pkg/errors/stack.go generated vendored Normal file
View File

@ -0,0 +1,178 @@
package errors
import (
"fmt"
"io"
"path"
"runtime"
"strings"
)
// Frame represents a program counter inside a stack frame.
type Frame uintptr
// pc returns the program counter for this frame;
// multiple frames may have the same PC value.
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
// file returns the full path to the file that contains the
// function for this Frame's pc.
func (f Frame) file() string {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return "unknown"
}
file, _ := fn.FileLine(f.pc())
return file
}
// line returns the line number of source code of the
// function for this Frame's pc.
func (f Frame) line() int {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return 0
}
_, line := fn.FileLine(f.pc())
return line
}
// Format formats the frame according to the fmt.Formatter interface.
//
// %s source file
// %d source line
// %n function name
// %v equivalent to %s:%d
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+s path of source file relative to the compile time GOPATH
// %+v equivalent to %+s:%d
func (f Frame) Format(s fmt.State, verb rune) {
switch verb {
case 's':
switch {
case s.Flag('+'):
pc := f.pc()
fn := runtime.FuncForPC(pc)
if fn == nil {
io.WriteString(s, "unknown")
} else {
file, _ := fn.FileLine(pc)
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
}
default:
io.WriteString(s, path.Base(f.file()))
}
case 'd':
fmt.Fprintf(s, "%d", f.line())
case 'n':
name := runtime.FuncForPC(f.pc()).Name()
io.WriteString(s, funcname(name))
case 'v':
f.Format(s, 's')
io.WriteString(s, ":")
f.Format(s, 'd')
}
}
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
type StackTrace []Frame
func (st StackTrace) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case s.Flag('+'):
for _, f := range st {
fmt.Fprintf(s, "\n%+v", f)
}
case s.Flag('#'):
fmt.Fprintf(s, "%#v", []Frame(st))
default:
fmt.Fprintf(s, "%v", []Frame(st))
}
case 's':
fmt.Fprintf(s, "%s", []Frame(st))
}
}
// stack represents a stack of program counters.
type stack []uintptr
func (s *stack) Format(st fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case st.Flag('+'):
for _, pc := range *s {
f := Frame(pc)
fmt.Fprintf(st, "\n%+v", f)
}
}
}
}
func (s *stack) StackTrace() StackTrace {
f := make([]Frame, len(*s))
for i := 0; i < len(f); i++ {
f[i] = Frame((*s)[i])
}
return f
}
func callers() *stack {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(3, pcs[:])
var st stack = pcs[0:n]
return &st
}
// funcname removes the path prefix component of a function's name reported by func.Name().
func funcname(name string) string {
i := strings.LastIndex(name, "/")
name = name[i+1:]
i = strings.Index(name, ".")
return name[i+1:]
}
func trimGOPATH(name, file string) string {
// 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(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
}

292
vendor/github.com/pkg/errors/stack_test.go generated vendored Normal file
View File

@ -0,0 +1,292 @@
package errors
import (
"fmt"
"runtime"
"testing"
)
var initpc, _, _, _ = runtime.Caller(0)
func TestFrameLine(t *testing.T) {
var tests = []struct {
Frame
want int
}{{
Frame(initpc),
9,
}, {
func() Frame {
var pc, _, _, _ = runtime.Caller(0)
return Frame(pc)
}(),
20,
}, {
func() Frame {
var pc, _, _, _ = runtime.Caller(1)
return Frame(pc)
}(),
28,
}, {
Frame(0), // invalid PC
0,
}}
for _, tt := range tests {
got := tt.Frame.line()
want := tt.want
if want != got {
t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got)
}
}
}
type X struct{}
func (x X) val() Frame {
var pc, _, _, _ = runtime.Caller(0)
return Frame(pc)
}
func (x *X) ptr() Frame {
var pc, _, _, _ = runtime.Caller(0)
return Frame(pc)
}
func TestFrameFormat(t *testing.T) {
var tests = []struct {
Frame
format string
want string
}{{
Frame(initpc),
"%s",
"stack_test.go",
}, {
Frame(initpc),
"%+s",
"github.com/pkg/errors.init\n" +
"\t.+/github.com/pkg/errors/stack_test.go",
}, {
Frame(0),
"%s",
"unknown",
}, {
Frame(0),
"%+s",
"unknown",
}, {
Frame(initpc),
"%d",
"9",
}, {
Frame(0),
"%d",
"0",
}, {
Frame(initpc),
"%n",
"init",
}, {
func() Frame {
var x X
return x.ptr()
}(),
"%n",
`\(\*X\).ptr`,
}, {
func() Frame {
var x X
return x.val()
}(),
"%n",
"X.val",
}, {
Frame(0),
"%n",
"",
}, {
Frame(initpc),
"%v",
"stack_test.go:9",
}, {
Frame(initpc),
"%+v",
"github.com/pkg/errors.init\n" +
"\t.+/github.com/pkg/errors/stack_test.go:9",
}, {
Frame(0),
"%v",
"unknown:0",
}}
for i, tt := range tests {
testFormatRegexp(t, i, tt.Frame, tt.format, tt.want)
}
}
func TestFuncname(t *testing.T) {
tests := []struct {
name, want string
}{
{"", ""},
{"runtime.main", "main"},
{"github.com/pkg/errors.funcname", "funcname"},
{"funcname", "funcname"},
{"io.copyBuffer", "copyBuffer"},
{"main.(*R).Write", "(*R).Write"},
}
for _, tt := range tests {
got := funcname(tt.name)
want := tt.want
if got != want {
t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got)
}
}
}
func TestTrimGOPATH(t *testing.T) {
var tests = []struct {
Frame
want string
}{{
Frame(initpc),
"github.com/pkg/errors/stack_test.go",
}}
for i, tt := range tests {
pc := tt.Frame.pc()
fn := runtime.FuncForPC(pc)
file, _ := fn.FileLine(pc)
got := trimGOPATH(fn.Name(), file)
testFormatRegexp(t, i, got, "%s", tt.want)
}
}
func TestStackTrace(t *testing.T) {
tests := []struct {
err error
want []string
}{{
New("ooh"), []string{
"github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:172",
},
}, {
Wrap(New("ooh"), "ahh"), []string{
"github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:177", // this is the stack of Wrap, not New
},
}, {
Cause(Wrap(New("ooh"), "ahh")), []string{
"github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:182", // this is the stack of New
},
}, {
func() error { return New("ooh") }(), []string{
`github.com/pkg/errors.(func·009|TestStackTrace.func1)` +
"\n\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New
"github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New's caller
},
}, {
Cause(func() error {
return func() error {
return Errorf("hello %s", fmt.Sprintf("world"))
}()
}()), []string{
`github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` +
"\n\t.+/github.com/pkg/errors/stack_test.go:196", // this is the stack of Errorf
`github.com/pkg/errors.(func·011|TestStackTrace.func2)` +
"\n\t.+/github.com/pkg/errors/stack_test.go:197", // this is the stack of Errorf's caller
"github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:198", // this is the stack of Errorf's caller's caller
},
}}
for i, tt := range tests {
x, ok := tt.err.(interface {
StackTrace() StackTrace
})
if !ok {
t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err)
continue
}
st := x.StackTrace()
for j, want := range tt.want {
testFormatRegexp(t, i, st[j], "%+v", want)
}
}
}
func stackTrace() StackTrace {
const depth = 8
var pcs [depth]uintptr
n := runtime.Callers(1, pcs[:])
var st stack = pcs[0:n]
return st.StackTrace()
}
func TestStackTraceFormat(t *testing.T) {
tests := []struct {
StackTrace
format string
want string
}{{
nil,
"%s",
`\[\]`,
}, {
nil,
"%v",
`\[\]`,
}, {
nil,
"%+v",
"",
}, {
nil,
"%#v",
`\[\]errors.Frame\(nil\)`,
}, {
make(StackTrace, 0),
"%s",
`\[\]`,
}, {
make(StackTrace, 0),
"%v",
`\[\]`,
}, {
make(StackTrace, 0),
"%+v",
"",
}, {
make(StackTrace, 0),
"%#v",
`\[\]errors.Frame{}`,
}, {
stackTrace()[:2],
"%s",
`\[stack_test.go stack_test.go\]`,
}, {
stackTrace()[:2],
"%v",
`\[stack_test.go:225 stack_test.go:272\]`,
}, {
stackTrace()[:2],
"%+v",
"\n" +
"github.com/pkg/errors.stackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:225\n" +
"github.com/pkg/errors.TestStackTraceFormat\n" +
"\t.+/github.com/pkg/errors/stack_test.go:276",
}, {
stackTrace()[:2],
"%#v",
`\[\]errors.Frame{stack_test.go:225, stack_test.go:284}`,
}}
for i, tt := range tests {
testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want)
}
}