forked from cerc-io/plugeth
p2p/dnsdisc: add implementation of EIP-1459 (#20094)
This adds an implementation of node discovery via DNS TXT records to the go-ethereum library. The implementation doesn't match EIP-1459 exactly, the main difference being that this implementation uses separate merkle trees for tree links and ENRs. The EIP will be updated to match p2p/dnsdisc. To maintain DNS trees, cmd/devp2p provides a frontend for the p2p/dnsdisc library. The new 'dns' subcommands can be used to create, sign and deploy DNS discovery trees.
This commit is contained in:
parent
32b07e8b1f
commit
0568e81701
@ -19,10 +19,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
@ -38,24 +38,35 @@ var (
|
|||||||
discv4PingCommand,
|
discv4PingCommand,
|
||||||
discv4RequestRecordCommand,
|
discv4RequestRecordCommand,
|
||||||
discv4ResolveCommand,
|
discv4ResolveCommand,
|
||||||
|
discv4ResolveJSONCommand,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
discv4PingCommand = cli.Command{
|
discv4PingCommand = cli.Command{
|
||||||
Name: "ping",
|
Name: "ping",
|
||||||
Usage: "Sends ping to a node",
|
Usage: "Sends ping to a node",
|
||||||
Action: discv4Ping,
|
Action: discv4Ping,
|
||||||
|
ArgsUsage: "<node>",
|
||||||
}
|
}
|
||||||
discv4RequestRecordCommand = cli.Command{
|
discv4RequestRecordCommand = cli.Command{
|
||||||
Name: "requestenr",
|
Name: "requestenr",
|
||||||
Usage: "Requests a node record using EIP-868 enrRequest",
|
Usage: "Requests a node record using EIP-868 enrRequest",
|
||||||
Action: discv4RequestRecord,
|
Action: discv4RequestRecord,
|
||||||
|
ArgsUsage: "<node>",
|
||||||
}
|
}
|
||||||
discv4ResolveCommand = cli.Command{
|
discv4ResolveCommand = cli.Command{
|
||||||
Name: "resolve",
|
Name: "resolve",
|
||||||
Usage: "Finds a node in the DHT",
|
Usage: "Finds a node in the DHT",
|
||||||
Action: discv4Resolve,
|
Action: discv4Resolve,
|
||||||
|
ArgsUsage: "<node>",
|
||||||
Flags: []cli.Flag{bootnodesFlag},
|
Flags: []cli.Flag{bootnodesFlag},
|
||||||
}
|
}
|
||||||
|
discv4ResolveJSONCommand = cli.Command{
|
||||||
|
Name: "resolve-json",
|
||||||
|
Usage: "Re-resolves nodes in a nodes.json file",
|
||||||
|
Action: discv4ResolveJSON,
|
||||||
|
Flags: []cli.Flag{bootnodesFlag},
|
||||||
|
ArgsUsage: "<nodes.json file>",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var bootnodesFlag = cli.StringFlag{
|
var bootnodesFlag = cli.StringFlag{
|
||||||
@ -64,10 +75,8 @@ var bootnodesFlag = cli.StringFlag{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func discv4Ping(ctx *cli.Context) error {
|
func discv4Ping(ctx *cli.Context) error {
|
||||||
n, disc, err := getNodeArgAndStartV4(ctx)
|
n := getNodeArg(ctx)
|
||||||
if err != nil {
|
disc := startV4(ctx)
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer disc.Close()
|
defer disc.Close()
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
@ -79,10 +88,8 @@ func discv4Ping(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func discv4RequestRecord(ctx *cli.Context) error {
|
func discv4RequestRecord(ctx *cli.Context) error {
|
||||||
n, disc, err := getNodeArgAndStartV4(ctx)
|
n := getNodeArg(ctx)
|
||||||
if err != nil {
|
disc := startV4(ctx)
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer disc.Close()
|
defer disc.Close()
|
||||||
|
|
||||||
respN, err := disc.RequestENR(n)
|
respN, err := disc.RequestENR(n)
|
||||||
@ -94,33 +101,43 @@ func discv4RequestRecord(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func discv4Resolve(ctx *cli.Context) error {
|
func discv4Resolve(ctx *cli.Context) error {
|
||||||
n, disc, err := getNodeArgAndStartV4(ctx)
|
n := getNodeArg(ctx)
|
||||||
if err != nil {
|
disc := startV4(ctx)
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer disc.Close()
|
defer disc.Close()
|
||||||
|
|
||||||
fmt.Println(disc.Resolve(n).String())
|
fmt.Println(disc.Resolve(n).String())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNodeArgAndStartV4(ctx *cli.Context) (*enode.Node, *discover.UDPv4, error) {
|
func discv4ResolveJSON(ctx *cli.Context) error {
|
||||||
if ctx.NArg() != 1 {
|
if ctx.NArg() < 1 {
|
||||||
return nil, nil, fmt.Errorf("missing node as command-line argument")
|
return fmt.Errorf("need nodes file as argument")
|
||||||
}
|
}
|
||||||
n, err := parseNode(ctx.Args()[0])
|
disc := startV4(ctx)
|
||||||
|
defer disc.Close()
|
||||||
|
file := ctx.Args().Get(0)
|
||||||
|
|
||||||
|
// Load existing nodes in file.
|
||||||
|
var nodes []*enode.Node
|
||||||
|
if common.FileExist(file) {
|
||||||
|
nodes = loadNodesJSON(file).nodes()
|
||||||
|
}
|
||||||
|
// Add nodes from command line arguments.
|
||||||
|
for i := 1; i < ctx.NArg(); i++ {
|
||||||
|
n, err := parseNode(ctx.Args().Get(i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
exit(err)
|
||||||
}
|
}
|
||||||
var bootnodes []*enode.Node
|
nodes = append(nodes, n)
|
||||||
if commandHasFlag(ctx, bootnodesFlag) {
|
|
||||||
bootnodes, err = parseBootnodes(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result := make(nodeSet, len(nodes))
|
||||||
|
for _, n := range nodes {
|
||||||
|
n = disc.Resolve(n)
|
||||||
|
result[n.ID()] = nodeJSON{Seq: n.Seq(), N: n}
|
||||||
}
|
}
|
||||||
disc, err := startV4(bootnodes)
|
writeNodesJSON(file, result)
|
||||||
return n, disc, err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) {
|
func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) {
|
||||||
@ -139,28 +156,39 @@ func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) {
|
|||||||
return nodes, nil
|
return nodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// commandHasFlag returns true if the current command supports the given flag.
|
// startV4 starts an ephemeral discovery V4 node.
|
||||||
func commandHasFlag(ctx *cli.Context, flag cli.Flag) bool {
|
func startV4(ctx *cli.Context) *discover.UDPv4 {
|
||||||
flags := ctx.FlagNames()
|
socket, ln, cfg, err := listen()
|
||||||
sort.Strings(flags)
|
if err != nil {
|
||||||
i := sort.SearchStrings(flags, flag.GetName())
|
exit(err)
|
||||||
return i != len(flags) && flags[i] == flag.GetName()
|
}
|
||||||
|
if commandHasFlag(ctx, bootnodesFlag) {
|
||||||
|
bn, err := parseBootnodes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
cfg.Bootnodes = bn
|
||||||
|
}
|
||||||
|
disc, err := discover.ListenV4(socket, ln, cfg)
|
||||||
|
if err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
return disc
|
||||||
}
|
}
|
||||||
|
|
||||||
// startV4 starts an ephemeral discovery V4 node.
|
func listen() (*net.UDPConn, *enode.LocalNode, discover.Config, error) {
|
||||||
func startV4(bootnodes []*enode.Node) (*discover.UDPv4, error) {
|
|
||||||
var cfg discover.Config
|
var cfg discover.Config
|
||||||
cfg.Bootnodes = bootnodes
|
|
||||||
cfg.PrivateKey, _ = crypto.GenerateKey()
|
cfg.PrivateKey, _ = crypto.GenerateKey()
|
||||||
db, _ := enode.OpenDB("")
|
db, _ := enode.OpenDB("")
|
||||||
ln := enode.NewLocalNode(db, cfg.PrivateKey)
|
ln := enode.NewLocalNode(db, cfg.PrivateKey)
|
||||||
|
|
||||||
socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{0, 0, 0, 0}})
|
socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{0, 0, 0, 0}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
db.Close()
|
||||||
|
return nil, nil, cfg, err
|
||||||
}
|
}
|
||||||
addr := socket.LocalAddr().(*net.UDPAddr)
|
addr := socket.LocalAddr().(*net.UDPAddr)
|
||||||
ln.SetFallbackIP(net.IP{127, 0, 0, 1})
|
ln.SetFallbackIP(net.IP{127, 0, 0, 1})
|
||||||
ln.SetFallbackUDP(addr.Port)
|
ln.SetFallbackUDP(addr.Port)
|
||||||
return discover.ListenUDP(socket, ln, cfg)
|
return socket, ln, cfg, nil
|
||||||
}
|
}
|
||||||
|
163
cmd/devp2p/dns_cloudflare.go
Normal file
163
cmd/devp2p/dns_cloudflare.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflare-go"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/dnsdisc"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cloudflareTokenFlag = cli.StringFlag{
|
||||||
|
Name: "token",
|
||||||
|
Usage: "CloudFlare API token",
|
||||||
|
EnvVar: "CLOUDFLARE_API_TOKEN",
|
||||||
|
}
|
||||||
|
cloudflareZoneIDFlag = cli.StringFlag{
|
||||||
|
Name: "zoneid",
|
||||||
|
Usage: "CloudFlare Zone ID (optional)",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type cloudflareClient struct {
|
||||||
|
*cloudflare.API
|
||||||
|
zoneID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCloudflareClient sets up a CloudFlare API client from command line flags.
|
||||||
|
func newCloudflareClient(ctx *cli.Context) *cloudflareClient {
|
||||||
|
token := ctx.String(cloudflareTokenFlag.Name)
|
||||||
|
if token == "" {
|
||||||
|
exit(fmt.Errorf("need cloudflare API token to proceed"))
|
||||||
|
}
|
||||||
|
api, err := cloudflare.NewWithAPIToken(token)
|
||||||
|
if err != nil {
|
||||||
|
exit(fmt.Errorf("can't create Cloudflare client: %v", err))
|
||||||
|
}
|
||||||
|
return &cloudflareClient{
|
||||||
|
API: api,
|
||||||
|
zoneID: ctx.String(cloudflareZoneIDFlag.Name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deploy uploads the given tree to CloudFlare DNS.
|
||||||
|
func (c *cloudflareClient) deploy(name string, t *dnsdisc.Tree) error {
|
||||||
|
if err := c.checkZone(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
records := t.ToTXT(name)
|
||||||
|
return c.uploadRecords(name, records)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkZone verifies permissions on the CloudFlare DNS Zone for name.
|
||||||
|
func (c *cloudflareClient) checkZone(name string) error {
|
||||||
|
if c.zoneID == "" {
|
||||||
|
log.Info(fmt.Sprintf("Finding CloudFlare zone ID for %s", name))
|
||||||
|
id, err := c.ZoneIDByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.zoneID = id
|
||||||
|
}
|
||||||
|
log.Info(fmt.Sprintf("Checking Permissions on zone %s", c.zoneID))
|
||||||
|
zone, err := c.ZoneDetails(c.zoneID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(name, "."+zone.Name) {
|
||||||
|
return fmt.Errorf("CloudFlare zone name %q does not match name %q to be deployed", zone.Name, name)
|
||||||
|
}
|
||||||
|
needPerms := map[string]bool{"#zone:edit": false, "#zone:read": false}
|
||||||
|
for _, perm := range zone.Permissions {
|
||||||
|
if _, ok := needPerms[perm]; ok {
|
||||||
|
needPerms[perm] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ok := range needPerms {
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("wrong permissions on zone %s: %v", c.zoneID, needPerms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// uploadRecords updates the TXT records at a particular subdomain. All non-root records
|
||||||
|
// will have a TTL of "infinity" and all existing records not in the new map will be
|
||||||
|
// nuked!
|
||||||
|
func (c *cloudflareClient) uploadRecords(name string, records map[string]string) error {
|
||||||
|
// Convert all names to lowercase.
|
||||||
|
lrecords := make(map[string]string, len(records))
|
||||||
|
for name, r := range records {
|
||||||
|
lrecords[strings.ToLower(name)] = r
|
||||||
|
}
|
||||||
|
records = lrecords
|
||||||
|
|
||||||
|
log.Info(fmt.Sprintf("Retrieving existing TXT records on %s", name))
|
||||||
|
entries, err := c.DNSRecords(c.zoneID, cloudflare.DNSRecord{Type: "TXT"})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
existing := make(map[string]cloudflare.DNSRecord)
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !strings.HasSuffix(entry.Name, name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
existing[strings.ToLower(entry.Name)] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over the new records and inject anything missing.
|
||||||
|
for path, val := range records {
|
||||||
|
old, exists := existing[path]
|
||||||
|
if !exists {
|
||||||
|
// Entry is unknown, push a new one to Cloudflare.
|
||||||
|
log.Info(fmt.Sprintf("Creating %s = %q", path, val))
|
||||||
|
ttl := 1
|
||||||
|
if path != name {
|
||||||
|
ttl = 2147483647 // Max TTL permitted by Cloudflare
|
||||||
|
}
|
||||||
|
_, err = c.CreateDNSRecord(c.zoneID, cloudflare.DNSRecord{Type: "TXT", Name: path, Content: val, TTL: ttl})
|
||||||
|
} else if old.Content != val {
|
||||||
|
// Entry already exists, only change its content.
|
||||||
|
log.Info(fmt.Sprintf("Updating %s from %q to %q", path, old.Content, val))
|
||||||
|
old.Content = val
|
||||||
|
err = c.UpdateDNSRecord(c.zoneID, old.ID, old)
|
||||||
|
} else {
|
||||||
|
log.Info(fmt.Sprintf("Skipping %s = %q", path, val))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to publish %s: %v", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over the old records and delete anything stale.
|
||||||
|
for path, entry := range existing {
|
||||||
|
if _, ok := records[path]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Stale entry, nuke it.
|
||||||
|
log.Info(fmt.Sprintf("Deleting %s = %q", path, entry.Content))
|
||||||
|
if err := c.DeleteDNSRecord(c.zoneID, entry.ID); err != nil {
|
||||||
|
return fmt.Errorf("failed to delete %s: %v", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
358
cmd/devp2p/dnscmd.go
Normal file
358
cmd/devp2p/dnscmd.go
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/console"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/dnsdisc"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
cli "gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dnsCommand = cli.Command{
|
||||||
|
Name: "dns",
|
||||||
|
Usage: "DNS Discovery Commands",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
dnsSyncCommand,
|
||||||
|
dnsSignCommand,
|
||||||
|
dnsTXTCommand,
|
||||||
|
dnsCloudflareCommand,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dnsSyncCommand = cli.Command{
|
||||||
|
Name: "sync",
|
||||||
|
Usage: "Download a DNS discovery tree",
|
||||||
|
ArgsUsage: "<url> [ <directory> ]",
|
||||||
|
Action: dnsSync,
|
||||||
|
Flags: []cli.Flag{dnsTimeoutFlag},
|
||||||
|
}
|
||||||
|
dnsSignCommand = cli.Command{
|
||||||
|
Name: "sign",
|
||||||
|
Usage: "Sign a DNS discovery tree",
|
||||||
|
ArgsUsage: "<tree-directory> <key-file>",
|
||||||
|
Action: dnsSign,
|
||||||
|
Flags: []cli.Flag{dnsDomainFlag, dnsSeqFlag},
|
||||||
|
}
|
||||||
|
dnsTXTCommand = cli.Command{
|
||||||
|
Name: "to-txt",
|
||||||
|
Usage: "Create a DNS TXT records for a discovery tree",
|
||||||
|
ArgsUsage: "<tree-directory> <output-file>",
|
||||||
|
Action: dnsToTXT,
|
||||||
|
}
|
||||||
|
dnsCloudflareCommand = cli.Command{
|
||||||
|
Name: "to-cloudflare",
|
||||||
|
Usage: "Deploy DNS TXT records to cloudflare",
|
||||||
|
ArgsUsage: "<tree-directory>",
|
||||||
|
Action: dnsToCloudflare,
|
||||||
|
Flags: []cli.Flag{cloudflareTokenFlag, cloudflareZoneIDFlag},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dnsTimeoutFlag = cli.DurationFlag{
|
||||||
|
Name: "timeout",
|
||||||
|
Usage: "Timeout for DNS lookups",
|
||||||
|
}
|
||||||
|
dnsDomainFlag = cli.StringFlag{
|
||||||
|
Name: "domain",
|
||||||
|
Usage: "Domain name of the tree",
|
||||||
|
}
|
||||||
|
dnsSeqFlag = cli.UintFlag{
|
||||||
|
Name: "seq",
|
||||||
|
Usage: "New sequence number of the tree",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// dnsSync performs dnsSyncCommand.
|
||||||
|
func dnsSync(ctx *cli.Context) error {
|
||||||
|
var (
|
||||||
|
c = dnsClient(ctx)
|
||||||
|
url = ctx.Args().Get(0)
|
||||||
|
outdir = ctx.Args().Get(1)
|
||||||
|
)
|
||||||
|
domain, _, err := dnsdisc.ParseURL(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if outdir == "" {
|
||||||
|
outdir = domain
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := c.SyncTree(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
def := treeToDefinition(url, t)
|
||||||
|
def.Meta.LastModified = time.Now()
|
||||||
|
writeTreeDefinition(outdir, def)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dnsSign(ctx *cli.Context) error {
|
||||||
|
if ctx.NArg() < 2 {
|
||||||
|
return fmt.Errorf("need tree definition directory and key file as arguments")
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
defdir = ctx.Args().Get(0)
|
||||||
|
keyfile = ctx.Args().Get(1)
|
||||||
|
def = loadTreeDefinition(defdir)
|
||||||
|
domain = directoryName(defdir)
|
||||||
|
)
|
||||||
|
if def.Meta.URL != "" {
|
||||||
|
d, _, err := dnsdisc.ParseURL(def.Meta.URL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid 'url' field: %v", err)
|
||||||
|
}
|
||||||
|
domain = d
|
||||||
|
}
|
||||||
|
if ctx.IsSet(dnsDomainFlag.Name) {
|
||||||
|
domain = ctx.String(dnsDomainFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.IsSet(dnsSeqFlag.Name) {
|
||||||
|
def.Meta.Seq = ctx.Uint(dnsSeqFlag.Name)
|
||||||
|
} else {
|
||||||
|
def.Meta.Seq++ // Auto-bump sequence number if not supplied via flag.
|
||||||
|
}
|
||||||
|
t, err := dnsdisc.MakeTree(def.Meta.Seq, def.Nodes, def.Meta.Links)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
key := loadSigningKey(keyfile)
|
||||||
|
url, err := t.Sign(key, domain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't sign: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
def = treeToDefinition(url, t)
|
||||||
|
def.Meta.LastModified = time.Now()
|
||||||
|
writeTreeDefinition(defdir, def)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func directoryName(dir string) string {
|
||||||
|
abs, err := filepath.Abs(dir)
|
||||||
|
if err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
return filepath.Base(abs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dnsToTXT peforms dnsTXTCommand.
|
||||||
|
func dnsToTXT(ctx *cli.Context) error {
|
||||||
|
if ctx.NArg() < 1 {
|
||||||
|
return fmt.Errorf("need tree definition directory as argument")
|
||||||
|
}
|
||||||
|
output := ctx.Args().Get(1)
|
||||||
|
if output == "" {
|
||||||
|
output = "-" // default to stdout
|
||||||
|
}
|
||||||
|
domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
writeTXTJSON(output, t.ToTXT(domain))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dnsToCloudflare peforms dnsCloudflareCommand.
|
||||||
|
func dnsToCloudflare(ctx *cli.Context) error {
|
||||||
|
if ctx.NArg() < 1 {
|
||||||
|
return fmt.Errorf("need tree definition directory as argument")
|
||||||
|
}
|
||||||
|
domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := newCloudflareClient(ctx)
|
||||||
|
return client.deploy(domain, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadSigningKey loads a private key in Ethereum keystore format.
|
||||||
|
func loadSigningKey(keyfile string) *ecdsa.PrivateKey {
|
||||||
|
keyjson, err := ioutil.ReadFile(keyfile)
|
||||||
|
if err != nil {
|
||||||
|
exit(fmt.Errorf("failed to read the keyfile at '%s': %v", keyfile, err))
|
||||||
|
}
|
||||||
|
password, _ := console.Stdin.PromptPassword("Please enter the password for '" + keyfile + "': ")
|
||||||
|
key, err := keystore.DecryptKey(keyjson, password)
|
||||||
|
if err != nil {
|
||||||
|
exit(fmt.Errorf("error decrypting key: %v", err))
|
||||||
|
}
|
||||||
|
return key.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// dnsClient configures the DNS discovery client from command line flags.
|
||||||
|
func dnsClient(ctx *cli.Context) *dnsdisc.Client {
|
||||||
|
var cfg dnsdisc.Config
|
||||||
|
if commandHasFlag(ctx, dnsTimeoutFlag) {
|
||||||
|
cfg.Timeout = ctx.Duration(dnsTimeoutFlag.Name)
|
||||||
|
}
|
||||||
|
c, _ := dnsdisc.NewClient(cfg) // cannot fail because no URLs given
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are two file formats for DNS node trees on disk:
|
||||||
|
//
|
||||||
|
// The 'TXT' format is a single JSON file containing DNS TXT records
|
||||||
|
// as a JSON object where the keys are names and the values are objects
|
||||||
|
// containing the value of the record.
|
||||||
|
//
|
||||||
|
// The 'definition' format is a directory containing two files:
|
||||||
|
//
|
||||||
|
// enrtree-info.json -- contains sequence number & links to other trees
|
||||||
|
// nodes.json -- contains the nodes as a JSON array.
|
||||||
|
//
|
||||||
|
// This format exists because it's convenient to edit. nodes.json can be generated
|
||||||
|
// in multiple ways: it may be written by a DHT crawler or compiled by a human.
|
||||||
|
|
||||||
|
type dnsDefinition struct {
|
||||||
|
Meta dnsMetaJSON
|
||||||
|
Nodes []*enode.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
type dnsMetaJSON struct {
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
Seq uint `json:"seq"`
|
||||||
|
Sig string `json:"signature,omitempty"`
|
||||||
|
Links []string `json:"links"`
|
||||||
|
LastModified time.Time `json:"lastModified"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func treeToDefinition(url string, t *dnsdisc.Tree) *dnsDefinition {
|
||||||
|
meta := dnsMetaJSON{
|
||||||
|
URL: url,
|
||||||
|
Seq: t.Seq(),
|
||||||
|
Sig: t.Signature(),
|
||||||
|
Links: t.Links(),
|
||||||
|
}
|
||||||
|
if meta.Links == nil {
|
||||||
|
meta.Links = []string{}
|
||||||
|
}
|
||||||
|
return &dnsDefinition{Meta: meta, Nodes: t.Nodes()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTreeDefinition loads a directory in 'definition' format.
|
||||||
|
func loadTreeDefinition(directory string) *dnsDefinition {
|
||||||
|
metaFile, nodesFile := treeDefinitionFiles(directory)
|
||||||
|
var def dnsDefinition
|
||||||
|
err := common.LoadJSON(metaFile, &def.Meta)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
if def.Meta.Links == nil {
|
||||||
|
def.Meta.Links = []string{}
|
||||||
|
}
|
||||||
|
// Check link syntax.
|
||||||
|
for _, link := range def.Meta.Links {
|
||||||
|
if _, _, err := dnsdisc.ParseURL(link); err != nil {
|
||||||
|
exit(fmt.Errorf("invalid link %q: %v", link, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check/convert nodes.
|
||||||
|
nodes := loadNodesJSON(nodesFile)
|
||||||
|
if err := nodes.verify(); err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
def.Nodes = nodes.nodes()
|
||||||
|
return &def
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTreeDefinitionForExport loads a DNS tree and ensures it is signed.
|
||||||
|
func loadTreeDefinitionForExport(dir string) (domain string, t *dnsdisc.Tree, err error) {
|
||||||
|
metaFile, _ := treeDefinitionFiles(dir)
|
||||||
|
def := loadTreeDefinition(dir)
|
||||||
|
if def.Meta.URL == "" {
|
||||||
|
return "", nil, fmt.Errorf("missing 'url' field in %v", metaFile)
|
||||||
|
}
|
||||||
|
domain, pubkey, err := dnsdisc.ParseURL(def.Meta.URL)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("invalid 'url' field in %v: %v", metaFile, err)
|
||||||
|
}
|
||||||
|
if t, err = dnsdisc.MakeTree(def.Meta.Seq, def.Nodes, def.Meta.Links); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
if err := ensureValidTreeSignature(t, pubkey, def.Meta.Sig); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return domain, t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureValidTreeSignature checks that sig is valid for tree and assigns it as the
|
||||||
|
// tree's signature if valid.
|
||||||
|
func ensureValidTreeSignature(t *dnsdisc.Tree, pubkey *ecdsa.PublicKey, sig string) error {
|
||||||
|
if sig == "" {
|
||||||
|
return fmt.Errorf("missing signature, run 'devp2p dns sign' first")
|
||||||
|
}
|
||||||
|
if err := t.SetSignature(pubkey, sig); err != nil {
|
||||||
|
return fmt.Errorf("invalid signature on tree, run 'devp2p dns sign' to update it")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeTreeDefinition writes a DNS node tree definition to the given directory.
|
||||||
|
func writeTreeDefinition(directory string, def *dnsDefinition) {
|
||||||
|
metaJSON, err := json.MarshalIndent(&def.Meta, "", jsonIndent)
|
||||||
|
if err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
// Convert nodes.
|
||||||
|
nodes := make(nodeSet, len(def.Nodes))
|
||||||
|
nodes.add(def.Nodes...)
|
||||||
|
// Write.
|
||||||
|
if err := os.Mkdir(directory, 0744); err != nil && !os.IsExist(err) {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
metaFile, nodesFile := treeDefinitionFiles(directory)
|
||||||
|
writeNodesJSON(nodesFile, nodes)
|
||||||
|
if err := ioutil.WriteFile(metaFile, metaJSON, 0644); err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func treeDefinitionFiles(directory string) (string, string) {
|
||||||
|
meta := filepath.Join(directory, "enrtree-info.json")
|
||||||
|
nodes := filepath.Join(directory, "nodes.json")
|
||||||
|
return meta, nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeTXTJSON writes TXT records in JSON format.
|
||||||
|
func writeTXTJSON(file string, txt map[string]string) {
|
||||||
|
txtJSON, err := json.MarshalIndent(txt, "", jsonIndent)
|
||||||
|
if err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
if file == "-" {
|
||||||
|
os.Stdout.Write(txtJSON)
|
||||||
|
fmt.Println()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(file, txtJSON, 0644); err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
}
|
@ -20,8 +20,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
@ -57,12 +59,38 @@ func init() {
|
|||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
enrdumpCommand,
|
enrdumpCommand,
|
||||||
discv4Command,
|
discv4Command,
|
||||||
|
dnsCommand,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := app.Run(os.Args); err != nil {
|
exit(app.Run(os.Args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandHasFlag returns true if the current command supports the given flag.
|
||||||
|
func commandHasFlag(ctx *cli.Context, flag cli.Flag) bool {
|
||||||
|
flags := ctx.FlagNames()
|
||||||
|
sort.Strings(flags)
|
||||||
|
i := sort.SearchStrings(flags, flag.GetName())
|
||||||
|
return i != len(flags) && flags[i] == flag.GetName()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNodeArg handles the common case of a single node descriptor argument.
|
||||||
|
func getNodeArg(ctx *cli.Context) *enode.Node {
|
||||||
|
if ctx.NArg() != 1 {
|
||||||
|
exit("missing node as command-line argument")
|
||||||
|
}
|
||||||
|
n, err := parseNode(ctx.Args()[0])
|
||||||
|
if err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func exit(err interface{}) {
|
||||||
|
if err == nil {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
87
cmd/devp2p/nodeset.go
Normal file
87
cmd/devp2p/nodeset.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
)
|
||||||
|
|
||||||
|
const jsonIndent = " "
|
||||||
|
|
||||||
|
// nodeSet is the nodes.json file format. It holds a set of node records
|
||||||
|
// as a JSON object.
|
||||||
|
type nodeSet map[enode.ID]nodeJSON
|
||||||
|
|
||||||
|
type nodeJSON struct {
|
||||||
|
Seq uint64 `json:"seq"`
|
||||||
|
N *enode.Node `json:"record"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadNodesJSON(file string) nodeSet {
|
||||||
|
var nodes nodeSet
|
||||||
|
if err := common.LoadJSON(file, &nodes); err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeNodesJSON(file string, nodes nodeSet) {
|
||||||
|
nodesJSON, err := json.MarshalIndent(nodes, "", jsonIndent)
|
||||||
|
if err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(file, nodesJSON, 0644); err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns nodeSet) nodes() []*enode.Node {
|
||||||
|
result := make([]*enode.Node, 0, len(ns))
|
||||||
|
for _, n := range ns {
|
||||||
|
result = append(result, n.N)
|
||||||
|
}
|
||||||
|
// Sort by ID.
|
||||||
|
sort.Slice(result, func(i, j int) bool {
|
||||||
|
return bytes.Compare(result[i].ID().Bytes(), result[j].ID().Bytes()) < 0
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns nodeSet) add(nodes ...*enode.Node) {
|
||||||
|
for _, n := range nodes {
|
||||||
|
ns[n.ID()] = nodeJSON{Seq: n.Seq(), N: n}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns nodeSet) verify() error {
|
||||||
|
for id, n := range ns {
|
||||||
|
if n.N.ID() != id {
|
||||||
|
return fmt.Errorf("invalid node %v: ID does not match ID %v in record", id, n.N.ID())
|
||||||
|
}
|
||||||
|
if n.N.Seq() != n.Seq {
|
||||||
|
return fmt.Errorf("invalid node %v: 'seq' does not match seq %d from record", id, n.N.Seq())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
260
p2p/dnsdisc/client.go
Normal file
260
p2p/dnsdisc/client.go
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package dnsdisc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client discovers nodes by querying DNS servers.
|
||||||
|
type Client struct {
|
||||||
|
cfg Config
|
||||||
|
clock mclock.Clock
|
||||||
|
linkCache linkCache
|
||||||
|
trees map[string]*clientTree
|
||||||
|
|
||||||
|
entries *lru.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config holds configuration options for the client.
|
||||||
|
type Config struct {
|
||||||
|
Timeout time.Duration // timeout used for DNS lookups (default 5s)
|
||||||
|
RecheckInterval time.Duration // time between tree root update checks (default 30min)
|
||||||
|
CacheLimit int // maximum number of cached records (default 1000)
|
||||||
|
ValidSchemes enr.IdentityScheme // acceptable ENR identity schemes (default enode.ValidSchemes)
|
||||||
|
Resolver Resolver // the DNS resolver to use (defaults to system DNS)
|
||||||
|
Logger log.Logger // destination of client log messages (defaults to root logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolver is a DNS resolver that can query TXT records.
|
||||||
|
type Resolver interface {
|
||||||
|
LookupTXT(ctx context.Context, domain string) ([]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg Config) withDefaults() Config {
|
||||||
|
const (
|
||||||
|
defaultTimeout = 5 * time.Second
|
||||||
|
defaultRecheck = 30 * time.Minute
|
||||||
|
defaultCache = 1000
|
||||||
|
)
|
||||||
|
if cfg.Timeout == 0 {
|
||||||
|
cfg.Timeout = defaultTimeout
|
||||||
|
}
|
||||||
|
if cfg.RecheckInterval == 0 {
|
||||||
|
cfg.RecheckInterval = defaultRecheck
|
||||||
|
}
|
||||||
|
if cfg.CacheLimit == 0 {
|
||||||
|
cfg.CacheLimit = defaultCache
|
||||||
|
}
|
||||||
|
if cfg.ValidSchemes == nil {
|
||||||
|
cfg.ValidSchemes = enode.ValidSchemes
|
||||||
|
}
|
||||||
|
if cfg.Resolver == nil {
|
||||||
|
cfg.Resolver = new(net.Resolver)
|
||||||
|
}
|
||||||
|
if cfg.Logger == nil {
|
||||||
|
cfg.Logger = log.Root()
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a client.
|
||||||
|
func NewClient(cfg Config, urls ...string) (*Client, error) {
|
||||||
|
c := &Client{
|
||||||
|
cfg: cfg.withDefaults(),
|
||||||
|
clock: mclock.System{},
|
||||||
|
trees: make(map[string]*clientTree),
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if c.entries, err = lru.New(c.cfg.CacheLimit); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, url := range urls {
|
||||||
|
if err := c.AddTree(url); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncTree downloads the entire node tree at the given URL. This doesn't add the tree for
|
||||||
|
// later use, but any previously-synced entries are reused.
|
||||||
|
func (c *Client) SyncTree(url string) (*Tree, error) {
|
||||||
|
le, err := parseURL(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid enrtree URL: %v", err)
|
||||||
|
}
|
||||||
|
ct := newClientTree(c, le)
|
||||||
|
t := &Tree{entries: make(map[string]entry)}
|
||||||
|
if err := ct.syncAll(t.entries); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.root = ct.root
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTree adds a enrtree:// URL to crawl.
|
||||||
|
func (c *Client) AddTree(url string) error {
|
||||||
|
le, err := parseURL(url)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid enrtree URL: %v", err)
|
||||||
|
}
|
||||||
|
ct, err := c.ensureTree(le)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.linkCache.add(ct)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ensureTree(le *linkEntry) (*clientTree, error) {
|
||||||
|
if tree, ok := c.trees[le.domain]; ok {
|
||||||
|
if !tree.matchPubkey(le.pubkey) {
|
||||||
|
return nil, fmt.Errorf("conflicting public keys for domain %q", le.domain)
|
||||||
|
}
|
||||||
|
return tree, nil
|
||||||
|
}
|
||||||
|
ct := newClientTree(c, le)
|
||||||
|
c.trees[le.domain] = ct
|
||||||
|
return ct, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandomNode retrieves the next random node.
|
||||||
|
func (c *Client) RandomNode(ctx context.Context) *enode.Node {
|
||||||
|
for {
|
||||||
|
ct := c.randomTree()
|
||||||
|
if ct == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
n, err := ct.syncRandom(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if err == ctx.Err() {
|
||||||
|
return nil // context canceled.
|
||||||
|
}
|
||||||
|
c.cfg.Logger.Debug("Error in DNS random node sync", "tree", ct.loc.domain, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n != nil {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomTree returns a random tree.
|
||||||
|
func (c *Client) randomTree() *clientTree {
|
||||||
|
if !c.linkCache.valid() {
|
||||||
|
c.gcTrees()
|
||||||
|
}
|
||||||
|
limit := rand.Intn(len(c.trees))
|
||||||
|
for _, ct := range c.trees {
|
||||||
|
if limit == 0 {
|
||||||
|
return ct
|
||||||
|
}
|
||||||
|
limit--
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// gcTrees rebuilds the 'trees' map.
|
||||||
|
func (c *Client) gcTrees() {
|
||||||
|
trees := make(map[string]*clientTree)
|
||||||
|
for t := range c.linkCache.all() {
|
||||||
|
trees[t.loc.domain] = t
|
||||||
|
}
|
||||||
|
c.trees = trees
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveRoot retrieves a root entry via DNS.
|
||||||
|
func (c *Client) resolveRoot(ctx context.Context, loc *linkEntry) (rootEntry, error) {
|
||||||
|
txts, err := c.cfg.Resolver.LookupTXT(ctx, loc.domain)
|
||||||
|
c.cfg.Logger.Trace("Updating DNS discovery root", "tree", loc.domain, "err", err)
|
||||||
|
if err != nil {
|
||||||
|
return rootEntry{}, err
|
||||||
|
}
|
||||||
|
for _, txt := range txts {
|
||||||
|
if strings.HasPrefix(txt, rootPrefix) {
|
||||||
|
return parseAndVerifyRoot(txt, loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rootEntry{}, nameError{loc.domain, errNoRoot}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAndVerifyRoot(txt string, loc *linkEntry) (rootEntry, error) {
|
||||||
|
e, err := parseRoot(txt)
|
||||||
|
if err != nil {
|
||||||
|
return e, err
|
||||||
|
}
|
||||||
|
if !e.verifySignature(loc.pubkey) {
|
||||||
|
return e, entryError{typ: "root", err: errInvalidSig}
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveEntry retrieves an entry from the cache or fetches it from the network
|
||||||
|
// if it isn't cached.
|
||||||
|
func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, error) {
|
||||||
|
cacheKey := truncateHash(hash)
|
||||||
|
if e, ok := c.entries.Get(cacheKey); ok {
|
||||||
|
return e.(entry), nil
|
||||||
|
}
|
||||||
|
e, err := c.doResolveEntry(ctx, domain, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.entries.Add(cacheKey, e)
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// doResolveEntry fetches an entry via DNS.
|
||||||
|
func (c *Client) doResolveEntry(ctx context.Context, domain, hash string) (entry, error) {
|
||||||
|
wantHash, err := b32format.DecodeString(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid base32 hash")
|
||||||
|
}
|
||||||
|
name := hash + "." + domain
|
||||||
|
txts, err := c.cfg.Resolver.LookupTXT(ctx, hash+"."+domain)
|
||||||
|
c.cfg.Logger.Trace("DNS discovery lookup", "name", name, "err", err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, txt := range txts {
|
||||||
|
e, err := parseEntry(txt, c.cfg.ValidSchemes)
|
||||||
|
if err == errUnknownEntry {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(crypto.Keccak256([]byte(txt)), wantHash) {
|
||||||
|
err = nameError{name, errHashMismatch}
|
||||||
|
} else if err != nil {
|
||||||
|
err = nameError{name, err}
|
||||||
|
}
|
||||||
|
return e, err
|
||||||
|
}
|
||||||
|
return nil, nameError{name, errNoEntry}
|
||||||
|
}
|
306
p2p/dnsdisc/client_test.go
Normal file
306
p2p/dnsdisc/client_test.go
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package dnsdisc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/testlog"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
signingKeySeed = 0x111111
|
||||||
|
nodesSeed1 = 0x2945237
|
||||||
|
nodesSeed2 = 0x4567299
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClientSyncTree(t *testing.T) {
|
||||||
|
r := mapResolver{
|
||||||
|
"3CA2MBMUQ55ZCT74YEEQLANJDI.n": "enr=-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI=",
|
||||||
|
"53HBTPGGZ4I76UEPCNQGZWIPTQ.n": "enr=-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA=",
|
||||||
|
"BG7SVUBUAJ3UAWD2ATEBLMRNEE.n": "enrtree=53HBTPGGZ4I76UEPCNQGZWIPTQ,3CA2MBMUQ55ZCT74YEEQLANJDI,HNHR6UTVZF5TJKK3FV27ZI76P4",
|
||||||
|
"HNHR6UTVZF5TJKK3FV27ZI76P4.n": "enr=-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o=",
|
||||||
|
"JGUFMSAGI7KZYB3P7IZW4S5Y3A.n": "enrtree-link=AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
|
||||||
|
"n": "enrtree-root=v1 e=BG7SVUBUAJ3UAWD2ATEBLMRNEE l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=1 sig=gacuU0nTy9duIdu1IFDyF5Lv9CFHqHiNcj91n0frw70tZo3tZZsCVkE3j1ILYyVOHRLWGBmawo_SEkThZ9PgcQE=",
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
wantNodes = testNodes(0x29452, 3)
|
||||||
|
wantLinks = []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"}
|
||||||
|
wantSeq = uint(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
c, _ := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
|
||||||
|
stree, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("sync error:", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(sortByID(stree.Nodes()), sortByID(wantNodes)) {
|
||||||
|
t.Errorf("wrong nodes in synced tree:\nhave %v\nwant %v", spew.Sdump(stree.Nodes()), spew.Sdump(wantNodes))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(stree.Links(), wantLinks) {
|
||||||
|
t.Errorf("wrong links in synced tree: %v", stree.Links())
|
||||||
|
}
|
||||||
|
if stree.Seq() != wantSeq {
|
||||||
|
t.Errorf("synced tree has wrong seq: %d", stree.Seq())
|
||||||
|
}
|
||||||
|
if len(c.trees) > 0 {
|
||||||
|
t.Errorf("tree from SyncTree added to client")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In this test, syncing the tree fails because it contains an invalid ENR entry.
|
||||||
|
func TestClientSyncTreeBadNode(t *testing.T) {
|
||||||
|
r := mapResolver{
|
||||||
|
"n": "enrtree-root=v1 e=ZFJZDQKSOMJRYYQSZKJZC54HCF l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=3 sig=WEy8JTZ2dHmXM2qeBZ7D2ECK7SGbnurl1ge_S_5GQBAqnADk0gLTcg8Lm5QNqLHZjJKGAb443p996idlMcBqEQA=",
|
||||||
|
"JGUFMSAGI7KZYB3P7IZW4S5Y3A.n": "enrtree-link=AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
|
||||||
|
"ZFJZDQKSOMJRYYQSZKJZC54HCF.n": "enr=gggggggggggggg=",
|
||||||
|
}
|
||||||
|
|
||||||
|
c, _ := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
|
||||||
|
_, err := c.SyncTree("enrtree://APFGGTFOBVE2ZNAB3CSMNNX6RRK3ODIRLP2AA5U4YFAA6MSYZUYTQ@n")
|
||||||
|
wantErr := nameError{name: "ZFJZDQKSOMJRYYQSZKJZC54HCF.n", err: entryError{typ: "enr", err: errInvalidENR}}
|
||||||
|
if err != wantErr {
|
||||||
|
t.Fatalf("expected sync error %q, got %q", wantErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test checks that RandomNode hits all entries.
|
||||||
|
func TestClientRandomNode(t *testing.T) {
|
||||||
|
nodes := testNodes(nodesSeed1, 30)
|
||||||
|
tree, url := makeTestTree("n", nodes, nil)
|
||||||
|
r := mapResolver(tree.ToTXT("n"))
|
||||||
|
c, _ := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
|
||||||
|
if err := c.AddTree(url); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRandomNode(t, c, nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test checks that RandomNode traverses linked trees as well as explicitly added trees.
|
||||||
|
func TestClientRandomNodeLinks(t *testing.T) {
|
||||||
|
nodes := testNodes(nodesSeed1, 40)
|
||||||
|
tree1, url1 := makeTestTree("t1", nodes[:10], nil)
|
||||||
|
tree2, url2 := makeTestTree("t2", nodes[10:], []string{url1})
|
||||||
|
cfg := Config{
|
||||||
|
Resolver: newMapResolver(tree1.ToTXT("t1"), tree2.ToTXT("t2")),
|
||||||
|
Logger: testlog.Logger(t, log.LvlTrace),
|
||||||
|
}
|
||||||
|
c, _ := NewClient(cfg)
|
||||||
|
if err := c.AddTree(url2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRandomNode(t, c, nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test verifies that RandomNode re-checks the root of the tree to catch
|
||||||
|
// updates to nodes.
|
||||||
|
func TestClientRandomNodeUpdates(t *testing.T) {
|
||||||
|
var (
|
||||||
|
clock = new(mclock.Simulated)
|
||||||
|
nodes = testNodes(nodesSeed1, 30)
|
||||||
|
resolver = newMapResolver()
|
||||||
|
cfg = Config{
|
||||||
|
Resolver: resolver,
|
||||||
|
Logger: testlog.Logger(t, log.LvlTrace),
|
||||||
|
RecheckInterval: 20 * time.Minute,
|
||||||
|
}
|
||||||
|
c, _ = NewClient(cfg)
|
||||||
|
)
|
||||||
|
c.clock = clock
|
||||||
|
tree1, url := makeTestTree("n", nodes[:25], nil)
|
||||||
|
|
||||||
|
// Sync the original tree.
|
||||||
|
resolver.add(tree1.ToTXT("n"))
|
||||||
|
c.AddTree(url)
|
||||||
|
checkRandomNode(t, c, nodes[:25])
|
||||||
|
|
||||||
|
// Update some nodes and ensure RandomNode returns the new nodes as well.
|
||||||
|
keys := testKeys(nodesSeed1, len(nodes))
|
||||||
|
for i, n := range nodes[:len(nodes)/2] {
|
||||||
|
r := n.Record()
|
||||||
|
r.Set(enr.IP{127, 0, 0, 1})
|
||||||
|
r.SetSeq(55)
|
||||||
|
enode.SignV4(r, keys[i])
|
||||||
|
n2, _ := enode.New(enode.ValidSchemes, r)
|
||||||
|
nodes[i] = n2
|
||||||
|
}
|
||||||
|
tree2, _ := makeTestTree("n", nodes, nil)
|
||||||
|
clock.Run(cfg.RecheckInterval + 1*time.Second)
|
||||||
|
resolver.clear()
|
||||||
|
resolver.add(tree2.ToTXT("n"))
|
||||||
|
checkRandomNode(t, c, nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test verifies that RandomNode re-checks the root of the tree to catch
|
||||||
|
// updates to links.
|
||||||
|
func TestClientRandomNodeLinkUpdates(t *testing.T) {
|
||||||
|
var (
|
||||||
|
clock = new(mclock.Simulated)
|
||||||
|
nodes = testNodes(nodesSeed1, 30)
|
||||||
|
resolver = newMapResolver()
|
||||||
|
cfg = Config{
|
||||||
|
Resolver: resolver,
|
||||||
|
Logger: testlog.Logger(t, log.LvlTrace),
|
||||||
|
RecheckInterval: 20 * time.Minute,
|
||||||
|
}
|
||||||
|
c, _ = NewClient(cfg)
|
||||||
|
)
|
||||||
|
c.clock = clock
|
||||||
|
tree3, url3 := makeTestTree("t3", nodes[20:30], nil)
|
||||||
|
tree2, url2 := makeTestTree("t2", nodes[10:20], nil)
|
||||||
|
tree1, url1 := makeTestTree("t1", nodes[0:10], []string{url2})
|
||||||
|
resolver.add(tree1.ToTXT("t1"))
|
||||||
|
resolver.add(tree2.ToTXT("t2"))
|
||||||
|
resolver.add(tree3.ToTXT("t3"))
|
||||||
|
|
||||||
|
// Sync tree1 using RandomNode.
|
||||||
|
c.AddTree(url1)
|
||||||
|
checkRandomNode(t, c, nodes[:20])
|
||||||
|
|
||||||
|
// Add link to tree3, remove link to tree2.
|
||||||
|
tree1, _ = makeTestTree("t1", nodes[:10], []string{url3})
|
||||||
|
resolver.add(tree1.ToTXT("t1"))
|
||||||
|
clock.Run(cfg.RecheckInterval + 1*time.Second)
|
||||||
|
t.Log("tree1 updated")
|
||||||
|
|
||||||
|
var wantNodes []*enode.Node
|
||||||
|
wantNodes = append(wantNodes, tree1.Nodes()...)
|
||||||
|
wantNodes = append(wantNodes, tree3.Nodes()...)
|
||||||
|
checkRandomNode(t, c, wantNodes)
|
||||||
|
|
||||||
|
// Check that linked trees are GCed when they're no longer referenced.
|
||||||
|
if len(c.trees) != 2 {
|
||||||
|
t.Errorf("client knows %d trees, want 2", len(c.trees))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkRandomNode(t *testing.T, c *Client, wantNodes []*enode.Node) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var (
|
||||||
|
want = make(map[enode.ID]*enode.Node)
|
||||||
|
maxCalls = len(wantNodes) * 2
|
||||||
|
calls = 0
|
||||||
|
ctx = context.Background()
|
||||||
|
)
|
||||||
|
for _, n := range wantNodes {
|
||||||
|
want[n.ID()] = n
|
||||||
|
}
|
||||||
|
for ; len(want) > 0 && calls < maxCalls; calls++ {
|
||||||
|
n := c.RandomNode(ctx)
|
||||||
|
if n == nil {
|
||||||
|
t.Fatalf("RandomNode returned nil (call %d)", calls)
|
||||||
|
}
|
||||||
|
delete(want, n.ID())
|
||||||
|
}
|
||||||
|
t.Logf("checkRandomNode called RandomNode %d times to find %d nodes", calls, len(wantNodes))
|
||||||
|
for _, n := range want {
|
||||||
|
t.Errorf("RandomNode didn't discover node %v", n.ID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, string) {
|
||||||
|
tree, err := MakeTree(1, nodes, links)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
url, err := tree.Sign(testKey(signingKeySeed), domain)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return tree, url
|
||||||
|
}
|
||||||
|
|
||||||
|
// testKeys creates deterministic private keys for testing.
|
||||||
|
func testKeys(seed int64, n int) []*ecdsa.PrivateKey {
|
||||||
|
rand := rand.New(rand.NewSource(seed))
|
||||||
|
keys := make([]*ecdsa.PrivateKey, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
key, err := ecdsa.GenerateKey(crypto.S256(), rand)
|
||||||
|
if err != nil {
|
||||||
|
panic("can't generate key: " + err.Error())
|
||||||
|
}
|
||||||
|
keys[i] = key
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func testKey(seed int64) *ecdsa.PrivateKey {
|
||||||
|
return testKeys(seed, 1)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNodes(seed int64, n int) []*enode.Node {
|
||||||
|
keys := testKeys(seed, n)
|
||||||
|
nodes := make([]*enode.Node, n)
|
||||||
|
for i, key := range keys {
|
||||||
|
record := new(enr.Record)
|
||||||
|
record.SetSeq(uint64(i))
|
||||||
|
enode.SignV4(record, key)
|
||||||
|
n, err := enode.New(enode.ValidSchemes, record)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
nodes[i] = n
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNode(seed int64) *enode.Node {
|
||||||
|
return testNodes(seed, 1)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
type mapResolver map[string]string
|
||||||
|
|
||||||
|
func newMapResolver(maps ...map[string]string) mapResolver {
|
||||||
|
mr := make(mapResolver)
|
||||||
|
for _, m := range maps {
|
||||||
|
mr.add(m)
|
||||||
|
}
|
||||||
|
return mr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mr mapResolver) clear() {
|
||||||
|
for k := range mr {
|
||||||
|
delete(mr, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mr mapResolver) add(m map[string]string) {
|
||||||
|
for k, v := range m {
|
||||||
|
mr[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, error) {
|
||||||
|
if record, ok := mr[name]; ok {
|
||||||
|
return []string{record}, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
18
p2p/dnsdisc/doc.go
Normal file
18
p2p/dnsdisc/doc.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package dnsdisc implements node discovery via DNS (EIP-1459).
|
||||||
|
package dnsdisc
|
63
p2p/dnsdisc/error.go
Normal file
63
p2p/dnsdisc/error.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package dnsdisc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Entry parse errors.
|
||||||
|
var (
|
||||||
|
errUnknownEntry = errors.New("unknown entry type")
|
||||||
|
errNoPubkey = errors.New("missing public key")
|
||||||
|
errBadPubkey = errors.New("invalid public key")
|
||||||
|
errInvalidENR = errors.New("invalid node record")
|
||||||
|
errInvalidChild = errors.New("invalid child hash")
|
||||||
|
errInvalidSig = errors.New("invalid base64 signature")
|
||||||
|
errSyntax = errors.New("invalid syntax")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resolver/sync errors
|
||||||
|
var (
|
||||||
|
errNoRoot = errors.New("no valid root found")
|
||||||
|
errNoEntry = errors.New("no valid tree entry found")
|
||||||
|
errHashMismatch = errors.New("hash mismatch")
|
||||||
|
errENRInLinkTree = errors.New("enr entry in link tree")
|
||||||
|
errLinkInENRTree = errors.New("link entry in ENR tree")
|
||||||
|
)
|
||||||
|
|
||||||
|
type nameError struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err nameError) Error() string {
|
||||||
|
if ee, ok := err.err.(entryError); ok {
|
||||||
|
return fmt.Sprintf("invalid %s entry at %s: %v", ee.typ, err.name, ee.err)
|
||||||
|
}
|
||||||
|
return err.name + ": " + err.err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
type entryError struct {
|
||||||
|
typ string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err entryError) Error() string {
|
||||||
|
return fmt.Sprintf("invalid %s entry: %v", err.typ, err.err)
|
||||||
|
}
|
277
p2p/dnsdisc/sync.go
Normal file
277
p2p/dnsdisc/sync.go
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package dnsdisc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// clientTree is a full tree being synced.
|
||||||
|
type clientTree struct {
|
||||||
|
c *Client
|
||||||
|
loc *linkEntry
|
||||||
|
root *rootEntry
|
||||||
|
lastRootCheck mclock.AbsTime // last revalidation of root
|
||||||
|
enrs *subtreeSync
|
||||||
|
links *subtreeSync
|
||||||
|
linkCache linkCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClientTree(c *Client, loc *linkEntry) *clientTree {
|
||||||
|
ct := &clientTree{c: c, loc: loc}
|
||||||
|
ct.linkCache.self = ct
|
||||||
|
return ct
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTree) matchPubkey(key *ecdsa.PublicKey) bool {
|
||||||
|
return keysEqual(ct.loc.pubkey, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func keysEqual(k1, k2 *ecdsa.PublicKey) bool {
|
||||||
|
return k1.Curve == k2.Curve && k1.X.Cmp(k2.X) == 0 && k1.Y.Cmp(k2.Y) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncAll retrieves all entries of the tree.
|
||||||
|
func (ct *clientTree) syncAll(dest map[string]entry) error {
|
||||||
|
if err := ct.updateRoot(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ct.links.resolveAll(dest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ct.enrs.resolveAll(dest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncRandom retrieves a single entry of the tree. The Node return value
|
||||||
|
// is non-nil if the entry was a node.
|
||||||
|
func (ct *clientTree) syncRandom(ctx context.Context) (*enode.Node, error) {
|
||||||
|
if ct.rootUpdateDue() {
|
||||||
|
if err := ct.updateRoot(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Link tree sync has priority, run it to completion before syncing ENRs.
|
||||||
|
if !ct.links.done() {
|
||||||
|
err := ct.syncNextLink(ctx)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync next random entry in ENR tree. Once every node has been visited, we simply
|
||||||
|
// start over. This is fine because entries are cached.
|
||||||
|
if ct.enrs.done() {
|
||||||
|
ct.enrs = newSubtreeSync(ct.c, ct.loc, ct.root.eroot, false)
|
||||||
|
}
|
||||||
|
return ct.syncNextRandomENR(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTree) syncNextLink(ctx context.Context) error {
|
||||||
|
hash := ct.links.missing[0]
|
||||||
|
e, err := ct.links.resolveNext(ctx, hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ct.links.missing = ct.links.missing[1:]
|
||||||
|
|
||||||
|
if le, ok := e.(*linkEntry); ok {
|
||||||
|
lt, err := ct.c.ensureTree(le)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ct.linkCache.add(lt)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTree) syncNextRandomENR(ctx context.Context) (*enode.Node, error) {
|
||||||
|
index := rand.Intn(len(ct.enrs.missing))
|
||||||
|
hash := ct.enrs.missing[index]
|
||||||
|
e, err := ct.enrs.resolveNext(ctx, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ct.enrs.missing = removeHash(ct.enrs.missing, index)
|
||||||
|
if ee, ok := e.(*enrEntry); ok {
|
||||||
|
return ee.node, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTree) String() string {
|
||||||
|
return ct.loc.url()
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeHash removes the element at index from h.
|
||||||
|
func removeHash(h []string, index int) []string {
|
||||||
|
if len(h) == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
last := len(h) - 1
|
||||||
|
if index < last {
|
||||||
|
h[index] = h[last]
|
||||||
|
h[last] = ""
|
||||||
|
}
|
||||||
|
return h[:last]
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateRoot ensures that the given tree has an up-to-date root.
|
||||||
|
func (ct *clientTree) updateRoot() error {
|
||||||
|
ct.lastRootCheck = ct.c.clock.Now()
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), ct.c.cfg.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
root, err := ct.c.resolveRoot(ctx, ct.loc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ct.root = &root
|
||||||
|
|
||||||
|
// Invalidate subtrees if changed.
|
||||||
|
if ct.links == nil || root.lroot != ct.links.root {
|
||||||
|
ct.links = newSubtreeSync(ct.c, ct.loc, root.lroot, true)
|
||||||
|
ct.linkCache.reset()
|
||||||
|
}
|
||||||
|
if ct.enrs == nil || root.eroot != ct.enrs.root {
|
||||||
|
ct.enrs = newSubtreeSync(ct.c, ct.loc, root.eroot, false)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rootUpdateDue returns true when a root update is needed.
|
||||||
|
func (ct *clientTree) rootUpdateDue() bool {
|
||||||
|
return ct.root == nil || time.Duration(ct.c.clock.Now()-ct.lastRootCheck) > ct.c.cfg.RecheckInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
// subtreeSync is the sync of an ENR or link subtree.
|
||||||
|
type subtreeSync struct {
|
||||||
|
c *Client
|
||||||
|
loc *linkEntry
|
||||||
|
root string
|
||||||
|
missing []string // missing tree node hashes
|
||||||
|
link bool // true if this sync is for the link tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSubtreeSync(c *Client, loc *linkEntry, root string, link bool) *subtreeSync {
|
||||||
|
return &subtreeSync{c, loc, root, []string{root}, link}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *subtreeSync) done() bool {
|
||||||
|
return len(ts.missing) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *subtreeSync) resolveAll(dest map[string]entry) error {
|
||||||
|
for !ts.done() {
|
||||||
|
hash := ts.missing[0]
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), ts.c.cfg.Timeout)
|
||||||
|
e, err := ts.resolveNext(ctx, hash)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dest[hash] = e
|
||||||
|
ts.missing = ts.missing[1:]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *subtreeSync) resolveNext(ctx context.Context, hash string) (entry, error) {
|
||||||
|
e, err := ts.c.resolveEntry(ctx, ts.loc.domain, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch e := e.(type) {
|
||||||
|
case *enrEntry:
|
||||||
|
if ts.link {
|
||||||
|
return nil, errENRInLinkTree
|
||||||
|
}
|
||||||
|
case *linkEntry:
|
||||||
|
if !ts.link {
|
||||||
|
return nil, errLinkInENRTree
|
||||||
|
}
|
||||||
|
case *subtreeEntry:
|
||||||
|
ts.missing = append(ts.missing, e.children...)
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// linkCache tracks the links of a tree.
|
||||||
|
type linkCache struct {
|
||||||
|
self *clientTree
|
||||||
|
directM map[*clientTree]struct{} // direct links
|
||||||
|
allM map[*clientTree]struct{} // direct & transitive links
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset clears the cache.
|
||||||
|
func (lc *linkCache) reset() {
|
||||||
|
lc.directM = nil
|
||||||
|
lc.allM = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// add adds a direct link to the cache.
|
||||||
|
func (lc *linkCache) add(ct *clientTree) {
|
||||||
|
if lc.directM == nil {
|
||||||
|
lc.directM = make(map[*clientTree]struct{})
|
||||||
|
}
|
||||||
|
if _, ok := lc.directM[ct]; !ok {
|
||||||
|
lc.invalidate()
|
||||||
|
}
|
||||||
|
lc.directM[ct] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalidate resets the cache of transitive links.
|
||||||
|
func (lc *linkCache) invalidate() {
|
||||||
|
lc.allM = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid returns true when the cache of transitive links is up-to-date.
|
||||||
|
func (lc *linkCache) valid() bool {
|
||||||
|
// Re-check validity of child caches to catch updates.
|
||||||
|
for ct := range lc.allM {
|
||||||
|
if ct != lc.self && !ct.linkCache.valid() {
|
||||||
|
lc.allM = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lc.allM != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// all returns all trees reachable through the cache.
|
||||||
|
func (lc *linkCache) all() map[*clientTree]struct{} {
|
||||||
|
if lc.valid() {
|
||||||
|
return lc.allM
|
||||||
|
}
|
||||||
|
// Remake lc.allM it by taking the union of all() across children.
|
||||||
|
m := make(map[*clientTree]struct{})
|
||||||
|
if lc.self != nil {
|
||||||
|
m[lc.self] = struct{}{}
|
||||||
|
}
|
||||||
|
for ct := range lc.directM {
|
||||||
|
m[ct] = struct{}{}
|
||||||
|
for lt := range ct.linkCache.all() {
|
||||||
|
m[lt] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lc.allM = m
|
||||||
|
return m
|
||||||
|
}
|
384
p2p/dnsdisc/tree.go
Normal file
384
p2p/dnsdisc/tree.go
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package dnsdisc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"encoding/base32"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tree is a merkle tree of node records.
|
||||||
|
type Tree struct {
|
||||||
|
root *rootEntry
|
||||||
|
entries map[string]entry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the tree with the given private key and sets the sequence number.
|
||||||
|
func (t *Tree) Sign(key *ecdsa.PrivateKey, domain string) (url string, err error) {
|
||||||
|
root := *t.root
|
||||||
|
sig, err := crypto.Sign(root.sigHash(), key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
root.sig = sig
|
||||||
|
t.root = &root
|
||||||
|
link := &linkEntry{domain, &key.PublicKey}
|
||||||
|
return link.url(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSignature verifies the given signature and assigns it as the tree's current
|
||||||
|
// signature if valid.
|
||||||
|
func (t *Tree) SetSignature(pubkey *ecdsa.PublicKey, signature string) error {
|
||||||
|
sig, err := b64format.DecodeString(signature)
|
||||||
|
if err != nil || len(sig) != crypto.SignatureLength {
|
||||||
|
return errInvalidSig
|
||||||
|
}
|
||||||
|
root := *t.root
|
||||||
|
root.sig = sig
|
||||||
|
if !root.verifySignature(pubkey) {
|
||||||
|
return errInvalidSig
|
||||||
|
}
|
||||||
|
t.root = &root
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seq returns the sequence number of the tree.
|
||||||
|
func (t *Tree) Seq() uint {
|
||||||
|
return t.root.seq
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature returns the signature of the tree.
|
||||||
|
func (t *Tree) Signature() string {
|
||||||
|
return b64format.EncodeToString(t.root.sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTXT returns all DNS TXT records required for the tree.
|
||||||
|
func (t *Tree) ToTXT(domain string) map[string]string {
|
||||||
|
records := map[string]string{domain: t.root.String()}
|
||||||
|
for _, e := range t.entries {
|
||||||
|
sd := subdomain(e)
|
||||||
|
if domain != "" {
|
||||||
|
sd = sd + "." + domain
|
||||||
|
}
|
||||||
|
records[sd] = e.String()
|
||||||
|
}
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
// Links returns all links contained in the tree.
|
||||||
|
func (t *Tree) Links() []string {
|
||||||
|
var links []string
|
||||||
|
for _, e := range t.entries {
|
||||||
|
if le, ok := e.(*linkEntry); ok {
|
||||||
|
links = append(links, le.url())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nodes returns all nodes contained in the tree.
|
||||||
|
func (t *Tree) Nodes() []*enode.Node {
|
||||||
|
var nodes []*enode.Node
|
||||||
|
for _, e := range t.entries {
|
||||||
|
if ee, ok := e.(*enrEntry); ok {
|
||||||
|
nodes = append(nodes, ee.node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
hashAbbrev = 16
|
||||||
|
maxChildren = 300 / (hashAbbrev * (13 / 8))
|
||||||
|
minHashLength = 12
|
||||||
|
rootPrefix = "enrtree-root=v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MakeTree creates a tree containing the given nodes and links.
|
||||||
|
func MakeTree(seq uint, nodes []*enode.Node, links []string) (*Tree, error) {
|
||||||
|
// Sort records by ID and ensure all nodes have a valid record.
|
||||||
|
records := make([]*enode.Node, len(nodes))
|
||||||
|
copy(records, nodes)
|
||||||
|
sortByID(records)
|
||||||
|
for _, n := range records {
|
||||||
|
if len(n.Record().Signature()) == 0 {
|
||||||
|
return nil, fmt.Errorf("can't add node %v: unsigned node record", n.ID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the leaf list.
|
||||||
|
enrEntries := make([]entry, len(records))
|
||||||
|
for i, r := range records {
|
||||||
|
enrEntries[i] = &enrEntry{r}
|
||||||
|
}
|
||||||
|
linkEntries := make([]entry, len(links))
|
||||||
|
for i, l := range links {
|
||||||
|
le, err := parseURL(l)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
linkEntries[i] = le
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create intermediate nodes.
|
||||||
|
t := &Tree{entries: make(map[string]entry)}
|
||||||
|
eroot := t.build(enrEntries)
|
||||||
|
t.entries[subdomain(eroot)] = eroot
|
||||||
|
lroot := t.build(linkEntries)
|
||||||
|
t.entries[subdomain(lroot)] = lroot
|
||||||
|
t.root = &rootEntry{seq: seq, eroot: subdomain(eroot), lroot: subdomain(lroot)}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) build(entries []entry) entry {
|
||||||
|
if len(entries) == 1 {
|
||||||
|
return entries[0]
|
||||||
|
}
|
||||||
|
if len(entries) <= maxChildren {
|
||||||
|
hashes := make([]string, len(entries))
|
||||||
|
for i, e := range entries {
|
||||||
|
hashes[i] = subdomain(e)
|
||||||
|
t.entries[hashes[i]] = e
|
||||||
|
}
|
||||||
|
return &subtreeEntry{hashes}
|
||||||
|
}
|
||||||
|
var subtrees []entry
|
||||||
|
for len(entries) > 0 {
|
||||||
|
n := maxChildren
|
||||||
|
if len(entries) < n {
|
||||||
|
n = len(entries)
|
||||||
|
}
|
||||||
|
sub := t.build(entries[:n])
|
||||||
|
entries = entries[n:]
|
||||||
|
subtrees = append(subtrees, sub)
|
||||||
|
t.entries[subdomain(sub)] = sub
|
||||||
|
}
|
||||||
|
return t.build(subtrees)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortByID(nodes []*enode.Node) []*enode.Node {
|
||||||
|
sort.Slice(nodes, func(i, j int) bool {
|
||||||
|
return bytes.Compare(nodes[i].ID().Bytes(), nodes[j].ID().Bytes()) < 0
|
||||||
|
})
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Types
|
||||||
|
|
||||||
|
type entry interface {
|
||||||
|
fmt.Stringer
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
rootEntry struct {
|
||||||
|
eroot string
|
||||||
|
lroot string
|
||||||
|
seq uint
|
||||||
|
sig []byte
|
||||||
|
}
|
||||||
|
subtreeEntry struct {
|
||||||
|
children []string
|
||||||
|
}
|
||||||
|
enrEntry struct {
|
||||||
|
node *enode.Node
|
||||||
|
}
|
||||||
|
linkEntry struct {
|
||||||
|
domain string
|
||||||
|
pubkey *ecdsa.PublicKey
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Entry Encoding
|
||||||
|
|
||||||
|
var (
|
||||||
|
b32format = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||||
|
b64format = base64.URLEncoding
|
||||||
|
)
|
||||||
|
|
||||||
|
func subdomain(e entry) string {
|
||||||
|
h := sha3.NewLegacyKeccak256()
|
||||||
|
io.WriteString(h, e.String())
|
||||||
|
return b32format.EncodeToString(h.Sum(nil)[:16])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *rootEntry) String() string {
|
||||||
|
return fmt.Sprintf(rootPrefix+" e=%s l=%s seq=%d sig=%s", e.eroot, e.lroot, e.seq, b64format.EncodeToString(e.sig))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *rootEntry) sigHash() []byte {
|
||||||
|
h := sha3.NewLegacyKeccak256()
|
||||||
|
fmt.Fprintf(h, rootPrefix+" e=%s l=%s seq=%d", e.eroot, e.lroot, e.seq)
|
||||||
|
return h.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *rootEntry) verifySignature(pubkey *ecdsa.PublicKey) bool {
|
||||||
|
sig := e.sig[:crypto.RecoveryIDOffset] // remove recovery id
|
||||||
|
return crypto.VerifySignature(crypto.FromECDSAPub(pubkey), e.sigHash(), sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *subtreeEntry) String() string {
|
||||||
|
return "enrtree=" + strings.Join(e.children, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *enrEntry) String() string {
|
||||||
|
enc, _ := rlp.EncodeToBytes(e.node.Record())
|
||||||
|
return "enr=" + b64format.EncodeToString(enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *linkEntry) String() string {
|
||||||
|
return "enrtree-link=" + e.link()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *linkEntry) url() string {
|
||||||
|
return "enrtree://" + e.link()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *linkEntry) link() string {
|
||||||
|
return fmt.Sprintf("%s@%s", b32format.EncodeToString(crypto.CompressPubkey(e.pubkey)), e.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Parsing
|
||||||
|
|
||||||
|
func parseEntry(e string, validSchemes enr.IdentityScheme) (entry, error) {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(e, "enrtree-link="):
|
||||||
|
return parseLink(e[13:])
|
||||||
|
case strings.HasPrefix(e, "enrtree="):
|
||||||
|
return parseSubtree(e[8:])
|
||||||
|
case strings.HasPrefix(e, "enr="):
|
||||||
|
return parseENR(e[4:], validSchemes)
|
||||||
|
default:
|
||||||
|
return nil, errUnknownEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRoot(e string) (rootEntry, error) {
|
||||||
|
var eroot, lroot, sig string
|
||||||
|
var seq uint
|
||||||
|
if _, err := fmt.Sscanf(e, rootPrefix+" e=%s l=%s seq=%d sig=%s", &eroot, &lroot, &seq, &sig); err != nil {
|
||||||
|
return rootEntry{}, entryError{"root", errSyntax}
|
||||||
|
}
|
||||||
|
if !isValidHash(eroot) || !isValidHash(lroot) {
|
||||||
|
return rootEntry{}, entryError{"root", errInvalidChild}
|
||||||
|
}
|
||||||
|
sigb, err := b64format.DecodeString(sig)
|
||||||
|
if err != nil || len(sigb) != crypto.SignatureLength {
|
||||||
|
return rootEntry{}, entryError{"root", errInvalidSig}
|
||||||
|
}
|
||||||
|
return rootEntry{eroot, lroot, seq, sigb}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLink(e string) (entry, error) {
|
||||||
|
pos := strings.IndexByte(e, '@')
|
||||||
|
if pos == -1 {
|
||||||
|
return nil, entryError{"link", errNoPubkey}
|
||||||
|
}
|
||||||
|
keystring, domain := e[:pos], e[pos+1:]
|
||||||
|
keybytes, err := b32format.DecodeString(keystring)
|
||||||
|
if err != nil {
|
||||||
|
return nil, entryError{"link", errBadPubkey}
|
||||||
|
}
|
||||||
|
key, err := crypto.DecompressPubkey(keybytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, entryError{"link", errBadPubkey}
|
||||||
|
}
|
||||||
|
return &linkEntry{domain, key}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSubtree(e string) (entry, error) {
|
||||||
|
if e == "" {
|
||||||
|
return &subtreeEntry{}, nil // empty entry is OK
|
||||||
|
}
|
||||||
|
hashes := make([]string, 0, strings.Count(e, ","))
|
||||||
|
for _, c := range strings.Split(e, ",") {
|
||||||
|
if !isValidHash(c) {
|
||||||
|
return nil, entryError{"subtree", errInvalidChild}
|
||||||
|
}
|
||||||
|
hashes = append(hashes, c)
|
||||||
|
}
|
||||||
|
return &subtreeEntry{hashes}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseENR(e string, validSchemes enr.IdentityScheme) (entry, error) {
|
||||||
|
enc, err := b64format.DecodeString(e)
|
||||||
|
if err != nil {
|
||||||
|
return nil, entryError{"enr", errInvalidENR}
|
||||||
|
}
|
||||||
|
var rec enr.Record
|
||||||
|
if err := rlp.DecodeBytes(enc, &rec); err != nil {
|
||||||
|
return nil, entryError{"enr", err}
|
||||||
|
}
|
||||||
|
n, err := enode.New(validSchemes, &rec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, entryError{"enr", err}
|
||||||
|
}
|
||||||
|
return &enrEntry{n}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidHash(s string) bool {
|
||||||
|
dlen := b32format.DecodedLen(len(s))
|
||||||
|
if dlen < minHashLength || dlen > 32 || strings.ContainsAny(s, "\n\r") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
buf := make([]byte, 32)
|
||||||
|
_, err := b32format.Decode(buf, []byte(s))
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncateHash truncates the given base32 hash string to the minimum acceptable length.
|
||||||
|
func truncateHash(hash string) string {
|
||||||
|
maxLen := b32format.EncodedLen(minHashLength)
|
||||||
|
if len(hash) < maxLen {
|
||||||
|
panic(fmt.Errorf("dnsdisc: hash %q is too short", hash))
|
||||||
|
}
|
||||||
|
return hash[:maxLen]
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL encoding
|
||||||
|
|
||||||
|
// ParseURL parses an enrtree:// URL and returns its components.
|
||||||
|
func ParseURL(url string) (domain string, pubkey *ecdsa.PublicKey, err error) {
|
||||||
|
le, err := parseURL(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return le.domain, le.pubkey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseURL(url string) (*linkEntry, error) {
|
||||||
|
const scheme = "enrtree://"
|
||||||
|
if !strings.HasPrefix(url, scheme) {
|
||||||
|
return nil, fmt.Errorf("wrong/missing scheme 'enrtree' in URL")
|
||||||
|
}
|
||||||
|
le, err := parseLink(url[len(scheme):])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err.(entryError).err
|
||||||
|
}
|
||||||
|
return le.(*linkEntry), nil
|
||||||
|
}
|
144
p2p/dnsdisc/tree_test.go
Normal file
144
p2p/dnsdisc/tree_test.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package dnsdisc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseRoot(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
e rootEntry
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "enrtree-root=v1 e=TO4Q75OQ2N7DX4EOOR7X66A6OM seq=3 sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtw=",
|
||||||
|
err: entryError{"root", errSyntax},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "enrtree-root=v1 e=TO4Q75OQ2N7DX4EOOR7X66A6OM l=TO4Q75OQ2N7DX4EOOR7X66A6OM seq=3 sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtw=",
|
||||||
|
err: entryError{"root", errInvalidSig},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "enrtree-root=v1 e=QFT4PBCRX4XQCV3VUYJ6BTCEPU l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=3 sig=3FmXuVwpa8Y7OstZTx9PIb1mt8FrW7VpDOFv4AaGCsZ2EIHmhraWhe4NxYhQDlw5MjeFXYMbJjsPeKlHzmJREQE=",
|
||||||
|
e: rootEntry{
|
||||||
|
eroot: "QFT4PBCRX4XQCV3VUYJ6BTCEPU",
|
||||||
|
lroot: "JGUFMSAGI7KZYB3P7IZW4S5Y3A",
|
||||||
|
seq: 3,
|
||||||
|
sig: hexutil.MustDecode("0xdc5997b95c296bc63b3acb594f1f4f21bd66b7c16b5bb5690ce16fe006860ac6761081e686b69685ee0dc588500e5c393237855d831b263b0f78a947ce62511101"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
e, err := parseRoot(test.input)
|
||||||
|
if !reflect.DeepEqual(e, test.e) {
|
||||||
|
t.Errorf("test %d: wrong entry %s, want %s", i, spew.Sdump(e), spew.Sdump(test.e))
|
||||||
|
}
|
||||||
|
if err != test.err {
|
||||||
|
t.Errorf("test %d: wrong error %q, want %q", i, err, test.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseEntry(t *testing.T) {
|
||||||
|
testkey := testKey(signingKeySeed)
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
e entry
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
// Subtrees:
|
||||||
|
{
|
||||||
|
input: "enrtree=1,2",
|
||||||
|
err: entryError{"subtree", errInvalidChild},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "enrtree=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||||
|
err: entryError{"subtree", errInvalidChild},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "enrtree=",
|
||||||
|
e: &subtreeEntry{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "enrtree=AAAAAAAAAAAAAAAAAAAA",
|
||||||
|
e: &subtreeEntry{[]string{"AAAAAAAAAAAAAAAAAAAA"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "enrtree=AAAAAAAAAAAAAAAAAAAA,BBBBBBBBBBBBBBBBBBBB",
|
||||||
|
e: &subtreeEntry{[]string{"AAAAAAAAAAAAAAAAAAAA", "BBBBBBBBBBBBBBBBBBBB"}},
|
||||||
|
},
|
||||||
|
// Links
|
||||||
|
{
|
||||||
|
input: "enrtree-link=AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@nodes.example.org",
|
||||||
|
e: &linkEntry{"nodes.example.org", &testkey.PublicKey},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "enrtree-link=nodes.example.org",
|
||||||
|
err: entryError{"link", errNoPubkey},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "enrtree-link=AP62DT7WOTEQZGQZOU474PP3KMEGVTTE7A7NPRXKX3DUD57@nodes.example.org",
|
||||||
|
err: entryError{"link", errBadPubkey},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "enrtree-link=AP62DT7WONEQZGQZOU474PP3KMEGVTTE7A7NPRXKX3DUD57TQHGIA@nodes.example.org",
|
||||||
|
err: entryError{"link", errBadPubkey},
|
||||||
|
},
|
||||||
|
// ENRs
|
||||||
|
{
|
||||||
|
input: "enr=-HW4QES8QIeXTYlDzbfr1WEzE-XKY4f8gJFJzjJL-9D7TC9lJb4Z3JPRRz1lP4pL_N_QpT6rGQjAU9Apnc-C1iMP36OAgmlkgnY0iXNlY3AyNTZrMaED5IdwfMxdmR8W37HqSFdQLjDkIwBd4Q_MjxgZifgKSdM=",
|
||||||
|
e: &enrEntry{node: testNode(nodesSeed1)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "enr=-HW4QLZHjM4vZXkbp-5xJoHsKSbE7W39FPC8283X-y8oHcHPTnDDlIlzL5ArvDUlHZVDPgmFASrh7cWgLOLxj4wprRkHgmlkgnY0iXNlY3AyNTZrMaEC3t2jLMhDpCDX5mbSEwDn4L3iUfyXzoO8G28XvjGRkrAg=",
|
||||||
|
err: entryError{"enr", errInvalidENR},
|
||||||
|
},
|
||||||
|
// Invalid:
|
||||||
|
{input: "", err: errUnknownEntry},
|
||||||
|
{input: "foo", err: errUnknownEntry},
|
||||||
|
{input: "enrtree", err: errUnknownEntry},
|
||||||
|
{input: "enrtree-x=", err: errUnknownEntry},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
e, err := parseEntry(test.input, enode.ValidSchemes)
|
||||||
|
if !reflect.DeepEqual(e, test.e) {
|
||||||
|
t.Errorf("test %d: wrong entry %s, want %s", i, spew.Sdump(e), spew.Sdump(test.e))
|
||||||
|
}
|
||||||
|
if err != test.err {
|
||||||
|
t.Errorf("test %d: wrong error %q, want %q", i, err, test.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeTree(t *testing.T) {
|
||||||
|
nodes := testNodes(nodesSeed2, 50)
|
||||||
|
tree, err := MakeTree(2, nodes, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
txt := tree.ToTXT("")
|
||||||
|
if len(txt) < len(nodes)+1 {
|
||||||
|
t.Fatal("too few TXT records in output")
|
||||||
|
}
|
||||||
|
}
|
77
vendor/github.com/cloudflare/cloudflare-go/CODE_OF_CONDUCT.md
generated
vendored
Normal file
77
vendor/github.com/cloudflare/cloudflare-go/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||||
|
level of experience, education, socio-economic status, nationality, personal
|
||||||
|
appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at ggalow@cloudflare.com. All
|
||||||
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see
|
||||||
|
https://www.contributor-covenant.org/faq
|
||||||
|
|
26
vendor/github.com/cloudflare/cloudflare-go/LICENSE
generated
vendored
Normal file
26
vendor/github.com/cloudflare/cloudflare-go/LICENSE
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
Copyright (c) 2015-2019, 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.
|
107
vendor/github.com/cloudflare/cloudflare-go/README.md
generated
vendored
Normal file
107
vendor/github.com/cloudflare/cloudflare-go/README.md
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# 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] Cache purging
|
||||||
|
* [x] Cloudflare IPs
|
||||||
|
* [x] Custom hostnames
|
||||||
|
* [x] DNS Records
|
||||||
|
* [x] Firewall (partial)
|
||||||
|
* [ ] [Keyless SSL](https://blog.cloudflare.com/keyless-ssl-the-nitty-gritty-technical-details/)
|
||||||
|
* [x] [Load Balancing](https://blog.cloudflare.com/introducing-load-balancing-intelligent-failover-with-cloudflare/)
|
||||||
|
* [x] [Logpush Jobs](https://developers.cloudflare.com/logs/logpush/)
|
||||||
|
* [ ] Organization Administration
|
||||||
|
* [x] [Origin CA](https://blog.cloudflare.com/universal-ssl-encryption-all-the-way-to-the-origin-for-free/)
|
||||||
|
* [x] [Railgun](https://www.cloudflare.com/railgun/) administration
|
||||||
|
* [x] Rate Limiting
|
||||||
|
* [x] User Administration (partial)
|
||||||
|
* [x] Virtual DNS Management
|
||||||
|
* [x] Web Application Firewall (WAF)
|
||||||
|
* [x] Zone Lockdown and User-Agent Block rules
|
||||||
|
* [x] Zones
|
||||||
|
|
||||||
|
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.
|
180
vendor/github.com/cloudflare/cloudflare-go/access_application.go
generated
vendored
Normal file
180
vendor/github.com/cloudflare/cloudflare-go/access_application.go
generated
vendored
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessApplication represents an Access application.
|
||||||
|
type AccessApplication struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||||
|
AUD string `json:"aud,omitempty"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
SessionDuration string `json:"session_duration,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessApplicationListResponse represents the response from the list
|
||||||
|
// access applications endpoint.
|
||||||
|
type AccessApplicationListResponse struct {
|
||||||
|
Result []AccessApplication `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessApplicationDetailResponse is the API response, containing a single
|
||||||
|
// access application.
|
||||||
|
type AccessApplicationDetailResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
Messages []string `json:"messages"`
|
||||||
|
Result AccessApplication `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessApplications returns all applications within a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-applications-list-access-applications
|
||||||
|
func (api *API) AccessApplications(zoneID string, pageOpts PaginationOptions) ([]AccessApplication, ResultInfo, error) {
|
||||||
|
v := url.Values{}
|
||||||
|
if pageOpts.PerPage > 0 {
|
||||||
|
v.Set("per_page", strconv.Itoa(pageOpts.PerPage))
|
||||||
|
}
|
||||||
|
if pageOpts.Page > 0 {
|
||||||
|
v.Set("page", strconv.Itoa(pageOpts.Page))
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := "/zones/" + zoneID + "/access/apps"
|
||||||
|
if len(v) > 0 {
|
||||||
|
uri = uri + "?" + v.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []AccessApplication{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessApplicationListResponse AccessApplicationListResponse
|
||||||
|
err = json.Unmarshal(res, &accessApplicationListResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []AccessApplication{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessApplicationListResponse.Result, accessApplicationListResponse.ResultInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessApplication returns a single application based on the
|
||||||
|
// application ID.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-applications-access-applications-details
|
||||||
|
func (api *API) AccessApplication(zoneID, applicationID string) (AccessApplication, error) {
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/zones/%s/access/apps/%s",
|
||||||
|
zoneID,
|
||||||
|
applicationID,
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return AccessApplication{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessApplicationDetailResponse AccessApplicationDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accessApplicationDetailResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccessApplication{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessApplicationDetailResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccessApplication creates a new access application.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-applications-create-access-application
|
||||||
|
func (api *API) CreateAccessApplication(zoneID string, accessApplication AccessApplication) (AccessApplication, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/access/apps"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("POST", uri, accessApplication)
|
||||||
|
if err != nil {
|
||||||
|
return AccessApplication{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessApplicationDetailResponse AccessApplicationDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accessApplicationDetailResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccessApplication{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessApplicationDetailResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccessApplication updates an existing access application.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-applications-update-access-application
|
||||||
|
func (api *API) UpdateAccessApplication(zoneID string, accessApplication AccessApplication) (AccessApplication, error) {
|
||||||
|
if accessApplication.ID == "" {
|
||||||
|
return AccessApplication{}, errors.Errorf("access application ID cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/zones/%s/access/apps/%s",
|
||||||
|
zoneID,
|
||||||
|
accessApplication.ID,
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, accessApplication)
|
||||||
|
if err != nil {
|
||||||
|
return AccessApplication{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessApplicationDetailResponse AccessApplicationDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accessApplicationDetailResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccessApplication{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessApplicationDetailResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAccessApplication deletes an access application.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-applications-delete-access-application
|
||||||
|
func (api *API) DeleteAccessApplication(zoneID, applicationID string) error {
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/zones/%s/access/apps/%s",
|
||||||
|
zoneID,
|
||||||
|
applicationID,
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeAccessApplicationTokens revokes tokens associated with an
|
||||||
|
// access application.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-applications-revoke-access-tokens
|
||||||
|
func (api *API) RevokeAccessApplicationTokens(zoneID, applicationID string) error {
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/zones/%s/access/apps/%s/revoke-tokens",
|
||||||
|
zoneID,
|
||||||
|
applicationID,
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err := api.makeRequest("POST", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
331
vendor/github.com/cloudflare/cloudflare-go/access_identity_provider.go
generated
vendored
Normal file
331
vendor/github.com/cloudflare/cloudflare-go/access_identity_provider.go
generated
vendored
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessIdentityProvider is the structure of the provider object.
|
||||||
|
type AccessIdentityProvider struct {
|
||||||
|
ID string `json:"id,omitemtpy"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Config interface{} `json:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessAzureADConfiguration is the representation of the Azure AD identity
|
||||||
|
// provider.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/azuread/
|
||||||
|
type AccessAzureADConfiguration struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
DirectoryID string `json:"directory_id"`
|
||||||
|
SupportGroups bool `json:"support_groups"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessCentrifyConfiguration is the representation of the Centrify identity
|
||||||
|
// provider.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/centrify/
|
||||||
|
type AccessCentrifyConfiguration struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
CentrifyAccount string `json:"centrify_account"`
|
||||||
|
CentrifyAppID string `json:"centrify_app_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessCentrifySAMLConfiguration is the representation of the Centrify
|
||||||
|
// identity provider using SAML.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/saml-centrify/
|
||||||
|
type AccessCentrifySAMLConfiguration struct {
|
||||||
|
IssuerURL string `json:"issuer_url"`
|
||||||
|
SsoTargetURL string `json:"sso_target_url"`
|
||||||
|
Attributes []string `json:"attributes"`
|
||||||
|
EmailAttributeName string `json:"email_attribute_name"`
|
||||||
|
SignRequest bool `json:"sign_request"`
|
||||||
|
IdpPublicCert string `json:"idp_public_cert"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessFacebookConfiguration is the representation of the Facebook identity
|
||||||
|
// provider.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/facebook-login/
|
||||||
|
type AccessFacebookConfiguration struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessGSuiteConfiguration is the representation of the GSuite identity
|
||||||
|
// provider.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/gsuite/
|
||||||
|
type AccessGSuiteConfiguration struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
AppsDomain string `json:"apps_domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessGenericOIDCConfiguration is the representation of the generic OpenID
|
||||||
|
// Connect (OIDC) connector.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/generic-oidc/
|
||||||
|
type AccessGenericOIDCConfiguration struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
AuthURL string `json:"auth_url"`
|
||||||
|
TokenURL string `json:"token_url"`
|
||||||
|
CertsURL string `json:"certs_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessGitHubConfiguration is the representation of the GitHub identity
|
||||||
|
// provider.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/github/
|
||||||
|
type AccessGitHubConfiguration struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessGoogleConfiguration is the representation of the Google identity
|
||||||
|
// provider.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/google/
|
||||||
|
type AccessGoogleConfiguration struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessJumpCloudSAMLConfiguration is the representation of the Jump Cloud
|
||||||
|
// identity provider using SAML.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/jumpcloud-saml/
|
||||||
|
type AccessJumpCloudSAMLConfiguration struct {
|
||||||
|
IssuerURL string `json:"issuer_url"`
|
||||||
|
SsoTargetURL string `json:"sso_target_url"`
|
||||||
|
Attributes []string `json:"attributes"`
|
||||||
|
EmailAttributeName string `json:"email_attribute_name"`
|
||||||
|
SignRequest bool `json:"sign_request"`
|
||||||
|
IdpPublicCert string `json:"idp_public_cert"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessOktaConfiguration is the representation of the Okta identity provider.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/okta/
|
||||||
|
type AccessOktaConfiguration struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
OktaAccount string `json:"okta_account"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessOktaSAMLConfiguration is the representation of the Okta identity
|
||||||
|
// provider using SAML.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/saml-okta/
|
||||||
|
type AccessOktaSAMLConfiguration struct {
|
||||||
|
IssuerURL string `json:"issuer_url"`
|
||||||
|
SsoTargetURL string `json:"sso_target_url"`
|
||||||
|
Attributes []string `json:"attributes"`
|
||||||
|
EmailAttributeName string `json:"email_attribute_name"`
|
||||||
|
SignRequest bool `json:"sign_request"`
|
||||||
|
IdpPublicCert string `json:"idp_public_cert"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessOneTimePinConfiguration is the representation of the default One Time
|
||||||
|
// Pin identity provider.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/one-time-pin/
|
||||||
|
type AccessOneTimePinConfiguration struct{}
|
||||||
|
|
||||||
|
// AccessOneLoginOIDCConfiguration is the representation of the OneLogin
|
||||||
|
// OpenID connector as an identity provider.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/onelogin-oidc/
|
||||||
|
type AccessOneLoginOIDCConfiguration struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
OneloginAccount string `json:"onelogin_account"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessOneLoginSAMLConfiguration is the representation of the OneLogin
|
||||||
|
// identity provider using SAML.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/onelogin-saml/
|
||||||
|
type AccessOneLoginSAMLConfiguration struct {
|
||||||
|
IssuerURL string `json:"issuer_url"`
|
||||||
|
SsoTargetURL string `json:"sso_target_url"`
|
||||||
|
Attributes []string `json:"attributes"`
|
||||||
|
EmailAttributeName string `json:"email_attribute_name"`
|
||||||
|
SignRequest bool `json:"sign_request"`
|
||||||
|
IdpPublicCert string `json:"idp_public_cert"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessPingSAMLConfiguration is the representation of the Ping identity
|
||||||
|
// provider using SAML.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/ping-saml/
|
||||||
|
type AccessPingSAMLConfiguration struct {
|
||||||
|
IssuerURL string `json:"issuer_url"`
|
||||||
|
SsoTargetURL string `json:"sso_target_url"`
|
||||||
|
Attributes []string `json:"attributes"`
|
||||||
|
EmailAttributeName string `json:"email_attribute_name"`
|
||||||
|
SignRequest bool `json:"sign_request"`
|
||||||
|
IdpPublicCert string `json:"idp_public_cert"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessYandexConfiguration is the representation of the Yandex identity provider.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/yandex/
|
||||||
|
type AccessYandexConfiguration struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessADSAMLConfiguration is the representation of the Active Directory
|
||||||
|
// identity provider using SAML.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/adfs/
|
||||||
|
type AccessADSAMLConfiguration struct {
|
||||||
|
IssuerURL string `json:"issuer_url"`
|
||||||
|
SsoTargetURL string `json:"sso_target_url"`
|
||||||
|
Attributes []string `json:"attributes"`
|
||||||
|
EmailAttributeName string `json:"email_attribute_name"`
|
||||||
|
SignRequest bool `json:"sign_request"`
|
||||||
|
IdpPublicCert string `json:"idp_public_cert"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessIdentityProvidersListResponse is the API response for multiple
|
||||||
|
// Access Identity Providers.
|
||||||
|
type AccessIdentityProvidersListResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
Messages []string `json:"messages"`
|
||||||
|
Result []AccessIdentityProvider `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessIdentityProviderListResponse is the API response for a single
|
||||||
|
// Access Identity Provider.
|
||||||
|
type AccessIdentityProviderListResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
Messages []string `json:"messages"`
|
||||||
|
Result AccessIdentityProvider `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessIdentityProviders returns all Access Identity Providers for an
|
||||||
|
// account.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-identity-providers-list-access-identity-providers
|
||||||
|
func (api *API) AccessIdentityProviders(accountID string) ([]AccessIdentityProvider, error) {
|
||||||
|
uri := "/accounts/" + accountID + "/access/identity_providers"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []AccessIdentityProvider{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessIdentityProviderResponse AccessIdentityProvidersListResponse
|
||||||
|
err = json.Unmarshal(res, &accessIdentityProviderResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessIdentityProviderResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessIdentityProviderDetails returns a single Access Identity
|
||||||
|
// Provider for an account.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-identity-providers-access-identity-providers-details
|
||||||
|
func (api *API) AccessIdentityProviderDetails(accountID, identityProviderID string) (AccessIdentityProvider, error) {
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/accounts/%s/access/identity_providers/%s",
|
||||||
|
accountID,
|
||||||
|
identityProviderID,
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return AccessIdentityProvider{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessIdentityProviderResponse AccessIdentityProviderListResponse
|
||||||
|
err = json.Unmarshal(res, &accessIdentityProviderResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessIdentityProviderResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccessIdentityProvider creates a new Access Identity Provider.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-identity-providers-create-access-identity-provider
|
||||||
|
func (api *API) CreateAccessIdentityProvider(accountID string, identityProviderConfiguration AccessIdentityProvider) (AccessIdentityProvider, error) {
|
||||||
|
uri := "/accounts/" + accountID + "/access/identity_providers"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("POST", uri, identityProviderConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
return AccessIdentityProvider{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessIdentityProviderResponse AccessIdentityProviderListResponse
|
||||||
|
err = json.Unmarshal(res, &accessIdentityProviderResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessIdentityProviderResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccessIdentityProvider updates an existing Access Identity
|
||||||
|
// Provider.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-identity-providers-create-access-identity-provider
|
||||||
|
func (api *API) UpdateAccessIdentityProvider(accountID, identityProviderUUID string, identityProviderConfiguration AccessIdentityProvider) (AccessIdentityProvider, error) {
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/accounts/%s/access/identity_providers/%s",
|
||||||
|
accountID,
|
||||||
|
identityProviderUUID,
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, identityProviderConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
return AccessIdentityProvider{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessIdentityProviderResponse AccessIdentityProviderListResponse
|
||||||
|
err = json.Unmarshal(res, &accessIdentityProviderResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessIdentityProviderResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAccessIdentityProvider deletes an Access Identity Provider.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-identity-providers-create-access-identity-provider
|
||||||
|
func (api *API) DeleteAccessIdentityProvider(accountID, identityProviderUUID string) (AccessIdentityProvider, error) {
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/accounts/%s/access/identity_providers/%s",
|
||||||
|
accountID,
|
||||||
|
identityProviderUUID,
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return AccessIdentityProvider{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessIdentityProviderResponse AccessIdentityProviderListResponse
|
||||||
|
err = json.Unmarshal(res, &accessIdentityProviderResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessIdentityProviderResponse.Result, nil
|
||||||
|
}
|
101
vendor/github.com/cloudflare/cloudflare-go/access_organization.go
generated
vendored
Normal file
101
vendor/github.com/cloudflare/cloudflare-go/access_organization.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessOrganization represents an Access organization.
|
||||||
|
type AccessOrganization struct {
|
||||||
|
CreatedAt *time.Time `json:"created_at"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
AuthDomain string `json:"auth_domain"`
|
||||||
|
LoginDesign AccessOrganizationLoginDesign `json:"login_design"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessOrganizationLoginDesign represents the login design options.
|
||||||
|
type AccessOrganizationLoginDesign struct {
|
||||||
|
BackgroundColor string `json:"background_color"`
|
||||||
|
TextColor string `json:"text_color"`
|
||||||
|
LogoPath string `json:"logo_path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessOrganizationListResponse represents the response from the list
|
||||||
|
// access organization endpoint.
|
||||||
|
type AccessOrganizationListResponse struct {
|
||||||
|
Result AccessOrganization `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessOrganizationDetailResponse is the API response, containing a
|
||||||
|
// single access organization.
|
||||||
|
type AccessOrganizationDetailResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
Messages []string `json:"messages"`
|
||||||
|
Result AccessOrganization `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessOrganization returns the Access organisation details.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-organizations-access-organization-details
|
||||||
|
func (api *API) AccessOrganization(accountID string) (AccessOrganization, ResultInfo, error) {
|
||||||
|
uri := "/accounts/" + accountID + "/access/organizations"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return AccessOrganization{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessOrganizationListResponse AccessOrganizationListResponse
|
||||||
|
err = json.Unmarshal(res, &accessOrganizationListResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccessOrganization{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessOrganizationListResponse.Result, accessOrganizationListResponse.ResultInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccessOrganization creates the Access organisation details.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-organizations-create-access-organization
|
||||||
|
func (api *API) CreateAccessOrganization(accountID string, accessOrganization AccessOrganization) (AccessOrganization, error) {
|
||||||
|
uri := "/accounts/" + accountID + "/access/organizations"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("POST", uri, accessOrganization)
|
||||||
|
if err != nil {
|
||||||
|
return AccessOrganization{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessOrganizationDetailResponse AccessOrganizationDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accessOrganizationDetailResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccessOrganization{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessOrganizationDetailResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccessOrganization creates the Access organisation details.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-organizations-update-access-organization
|
||||||
|
func (api *API) UpdateAccessOrganization(accountID string, accessOrganization AccessOrganization) (AccessOrganization, error) {
|
||||||
|
uri := "/accounts/" + accountID + "/access/organizations"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, accessOrganization)
|
||||||
|
if err != nil {
|
||||||
|
return AccessOrganization{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessOrganizationDetailResponse AccessOrganizationDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accessOrganizationDetailResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccessOrganization{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessOrganizationDetailResponse.Result, nil
|
||||||
|
}
|
221
vendor/github.com/cloudflare/cloudflare-go/access_policy.go
generated
vendored
Normal file
221
vendor/github.com/cloudflare/cloudflare-go/access_policy.go
generated
vendored
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessPolicy defines a policy for allowing or disallowing access to
|
||||||
|
// one or more Access applications.
|
||||||
|
type AccessPolicy struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Precedence int `json:"precedence"`
|
||||||
|
Decision string `json:"decision"`
|
||||||
|
CreatedAt *time.Time `json:"created_at"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// The include policy works like an OR logical operator. The user must
|
||||||
|
// satisfy one of the rules.
|
||||||
|
Include []interface{} `json:"include"`
|
||||||
|
|
||||||
|
// The exclude policy works like a NOT logical operator. The user must
|
||||||
|
// not satisfy all of the rules in exclude.
|
||||||
|
Exclude []interface{} `json:"exclude"`
|
||||||
|
|
||||||
|
// The require policy works like a AND logical operator. The user must
|
||||||
|
// satisfy all of the rules in require.
|
||||||
|
Require []interface{} `json:"require"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessPolicyEmail is used for managing access based on the email.
|
||||||
|
// For example, restrict access to users with the email addresses
|
||||||
|
// `test@example.com` or `someone@example.com`.
|
||||||
|
type AccessPolicyEmail struct {
|
||||||
|
Email struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
} `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessPolicyEmailDomain is used for managing access based on an email
|
||||||
|
// domain domain such as `example.com` instead of individual addresses.
|
||||||
|
type AccessPolicyEmailDomain struct {
|
||||||
|
EmailDomain struct {
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
} `json:"email_domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessPolicyIP is used for managing access based in the IP. It
|
||||||
|
// accepts individual IPs or CIDRs.
|
||||||
|
type AccessPolicyIP struct {
|
||||||
|
IP struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
} `json:"ip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessPolicyEveryone is used for managing access to everyone.
|
||||||
|
type AccessPolicyEveryone struct {
|
||||||
|
Everyone struct{} `json:"everyone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessPolicyAccessGroup is used for managing access based on an
|
||||||
|
// access group.
|
||||||
|
type AccessPolicyAccessGroup struct {
|
||||||
|
Group struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
} `json:"group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessPolicyListResponse represents the response from the list
|
||||||
|
// access polciies endpoint.
|
||||||
|
type AccessPolicyListResponse struct {
|
||||||
|
Result []AccessPolicy `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessPolicyDetailResponse is the API response, containing a single
|
||||||
|
// access policy.
|
||||||
|
type AccessPolicyDetailResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
Messages []string `json:"messages"`
|
||||||
|
Result AccessPolicy `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessPolicies returns all access policies for an access application.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-policy-list-access-policies
|
||||||
|
func (api *API) AccessPolicies(zoneID, applicationID string, pageOpts PaginationOptions) ([]AccessPolicy, ResultInfo, error) {
|
||||||
|
v := url.Values{}
|
||||||
|
if pageOpts.PerPage > 0 {
|
||||||
|
v.Set("per_page", strconv.Itoa(pageOpts.PerPage))
|
||||||
|
}
|
||||||
|
if pageOpts.Page > 0 {
|
||||||
|
v.Set("page", strconv.Itoa(pageOpts.Page))
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/zones/%s/access/apps/%s/policies",
|
||||||
|
zoneID,
|
||||||
|
applicationID,
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(v) > 0 {
|
||||||
|
uri = uri + "?" + v.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []AccessPolicy{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessPolicyListResponse AccessPolicyListResponse
|
||||||
|
err = json.Unmarshal(res, &accessPolicyListResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []AccessPolicy{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessPolicyListResponse.Result, accessPolicyListResponse.ResultInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessPolicy returns a single policy based on the policy ID.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-policy-access-policy-details
|
||||||
|
func (api *API) AccessPolicy(zoneID, applicationID, policyID string) (AccessPolicy, error) {
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/zones/%s/access/apps/%s/policies/%s",
|
||||||
|
zoneID,
|
||||||
|
applicationID,
|
||||||
|
policyID,
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return AccessPolicy{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessPolicyDetailResponse AccessPolicyDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accessPolicyDetailResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccessPolicy{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessPolicyDetailResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccessPolicy creates a new access policy.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-policy-create-access-policy
|
||||||
|
func (api *API) CreateAccessPolicy(zoneID, applicationID string, accessPolicy AccessPolicy) (AccessPolicy, error) {
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/zones/%s/access/apps/%s/policies",
|
||||||
|
zoneID,
|
||||||
|
applicationID,
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("POST", uri, accessPolicy)
|
||||||
|
if err != nil {
|
||||||
|
return AccessPolicy{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessPolicyDetailResponse AccessPolicyDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accessPolicyDetailResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccessPolicy{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessPolicyDetailResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccessPolicy updates an existing access policy.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-policy-update-access-policy
|
||||||
|
func (api *API) UpdateAccessPolicy(zoneID, applicationID string, accessPolicy AccessPolicy) (AccessPolicy, error) {
|
||||||
|
if accessPolicy.ID == "" {
|
||||||
|
return AccessPolicy{}, errors.Errorf("access policy ID cannot be empty")
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/zones/%s/access/apps/%s/policies/%s",
|
||||||
|
zoneID,
|
||||||
|
applicationID,
|
||||||
|
accessPolicy.ID,
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, accessPolicy)
|
||||||
|
if err != nil {
|
||||||
|
return AccessPolicy{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessPolicyDetailResponse AccessPolicyDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accessPolicyDetailResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccessPolicy{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessPolicyDetailResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAccessPolicy deletes an access policy.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-policy-update-access-policy
|
||||||
|
func (api *API) DeleteAccessPolicy(zoneID, applicationID, accessPolicyID string) error {
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/zones/%s/access/apps/%s/policies/%s",
|
||||||
|
zoneID,
|
||||||
|
applicationID,
|
||||||
|
accessPolicyID,
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
167
vendor/github.com/cloudflare/cloudflare-go/access_service_tokens.go
generated
vendored
Normal file
167
vendor/github.com/cloudflare/cloudflare-go/access_service_tokens.go
generated
vendored
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessServiceToken represents an Access Service Token.
|
||||||
|
type AccessServiceToken struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
CreatedAt *time.Time `json:"created_at"`
|
||||||
|
ExpiresAt *time.Time `json:"expires_at"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessServiceTokenUpdateResponse represents the response from the API
|
||||||
|
// when a new Service Token is updated. This base struct is also used in the
|
||||||
|
// Create as they are very similar responses.
|
||||||
|
type AccessServiceTokenUpdateResponse struct {
|
||||||
|
CreatedAt *time.Time `json:"created_at"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessServiceTokenCreateResponse is the same API response as the Update
|
||||||
|
// operation with the exception that the `ClientSecret` is present in a
|
||||||
|
// Create operation.
|
||||||
|
type AccessServiceTokenCreateResponse struct {
|
||||||
|
CreatedAt *time.Time `json:"created_at"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessServiceTokensListResponse represents the response from the list
|
||||||
|
// Access Service Tokens endpoint.
|
||||||
|
type AccessServiceTokensListResponse struct {
|
||||||
|
Result []AccessServiceToken `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessServiceTokensDetailResponse is the API response, containing a single
|
||||||
|
// Access Service Token.
|
||||||
|
type AccessServiceTokensDetailResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
Messages []string `json:"messages"`
|
||||||
|
Result AccessServiceToken `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessServiceTokensCreationDetailResponse is the API response, containing a
|
||||||
|
// single Access Service Token.
|
||||||
|
type AccessServiceTokensCreationDetailResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
Messages []string `json:"messages"`
|
||||||
|
Result AccessServiceTokenCreateResponse `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessServiceTokensUpdateDetailResponse is the API response, containing a
|
||||||
|
// single Access Service Token.
|
||||||
|
type AccessServiceTokensUpdateDetailResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
Messages []string `json:"messages"`
|
||||||
|
Result AccessServiceTokenUpdateResponse `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessServiceTokens returns all Access Service Tokens for an account.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-service-tokens-list-access-service-tokens
|
||||||
|
func (api *API) AccessServiceTokens(accountID string) ([]AccessServiceToken, ResultInfo, error) {
|
||||||
|
uri := "/accounts/" + accountID + "/access/service_tokens"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []AccessServiceToken{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessServiceTokensListResponse AccessServiceTokensListResponse
|
||||||
|
err = json.Unmarshal(res, &accessServiceTokensListResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []AccessServiceToken{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessServiceTokensListResponse.Result, accessServiceTokensListResponse.ResultInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccessServiceToken creates a new Access Service Token for an account.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-service-tokens-create-access-service-token
|
||||||
|
func (api *API) CreateAccessServiceToken(accountID, name string) (AccessServiceTokenCreateResponse, error) {
|
||||||
|
uri := "/accounts/" + accountID + "/access/service_tokens"
|
||||||
|
marshalledName, _ := json.Marshal(struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}{name})
|
||||||
|
|
||||||
|
res, err := api.makeRequest("POST", uri, marshalledName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return AccessServiceTokenCreateResponse{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessServiceTokenCreation AccessServiceTokensCreationDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accessServiceTokenCreation)
|
||||||
|
if err != nil {
|
||||||
|
return AccessServiceTokenCreateResponse{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessServiceTokenCreation.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccessServiceToken updates an existing Access Service Token for an
|
||||||
|
// account.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-service-tokens-update-access-service-token
|
||||||
|
func (api *API) UpdateAccessServiceToken(accountID, uuid, name string) (AccessServiceTokenUpdateResponse, error) {
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/access/service_tokens/%s", accountID, uuid)
|
||||||
|
|
||||||
|
marshalledName, _ := json.Marshal(struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}{name})
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, marshalledName)
|
||||||
|
if err != nil {
|
||||||
|
return AccessServiceTokenUpdateResponse{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessServiceTokenUpdate AccessServiceTokensUpdateDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accessServiceTokenUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return AccessServiceTokenUpdateResponse{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessServiceTokenUpdate.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAccessServiceToken removes an existing Access Service Token for an
|
||||||
|
// account.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#access-service-tokens-delete-access-service-token
|
||||||
|
func (api *API) DeleteAccessServiceToken(accountID, uuid string) (AccessServiceTokenUpdateResponse, error) {
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/access/service_tokens/%s", accountID, uuid)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return AccessServiceTokenUpdateResponse{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessServiceTokenUpdate AccessServiceTokensUpdateDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accessServiceTokenUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return AccessServiceTokenUpdateResponse{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessServiceTokenUpdate.Result, nil
|
||||||
|
}
|
186
vendor/github.com/cloudflare/cloudflare-go/account_members.go
generated
vendored
Normal file
186
vendor/github.com/cloudflare/cloudflare-go/account_members.go
generated
vendored
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccountMember is the definition of a member of an account.
|
||||||
|
type AccountMember struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
User AccountMemberUserDetails `json:"user"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Roles []AccountRole `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountMemberUserDetails outlines all the personal information about
|
||||||
|
// a member.
|
||||||
|
type AccountMemberUserDetails struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
TwoFactorAuthenticationEnabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountMembersListResponse represents the response from the list
|
||||||
|
// account members endpoint.
|
||||||
|
type AccountMembersListResponse struct {
|
||||||
|
Result []AccountMember `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountMemberDetailResponse is the API response, containing a single
|
||||||
|
// account member.
|
||||||
|
type AccountMemberDetailResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
Messages []string `json:"messages"`
|
||||||
|
Result AccountMember `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountMemberInvitation represents the invitation for a new member to
|
||||||
|
// the account.
|
||||||
|
type AccountMemberInvitation struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountMembers returns all members of an account.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#accounts-list-accounts
|
||||||
|
func (api *API) AccountMembers(accountID string, pageOpts PaginationOptions) ([]AccountMember, ResultInfo, error) {
|
||||||
|
if accountID == "" {
|
||||||
|
return []AccountMember{}, ResultInfo{}, errors.New(errMissingAccountID)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
if pageOpts.PerPage > 0 {
|
||||||
|
v.Set("per_page", strconv.Itoa(pageOpts.PerPage))
|
||||||
|
}
|
||||||
|
if pageOpts.Page > 0 {
|
||||||
|
v.Set("page", strconv.Itoa(pageOpts.Page))
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := "/accounts/" + accountID + "/members"
|
||||||
|
if len(v) > 0 {
|
||||||
|
uri = uri + "?" + v.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []AccountMember{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountMemberListresponse AccountMembersListResponse
|
||||||
|
err = json.Unmarshal(res, &accountMemberListresponse)
|
||||||
|
if err != nil {
|
||||||
|
return []AccountMember{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountMemberListresponse.Result, accountMemberListresponse.ResultInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccountMember invites a new member to join an account.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#account-members-add-member
|
||||||
|
func (api *API) CreateAccountMember(accountID string, emailAddress string, roles []string) (AccountMember, error) {
|
||||||
|
if accountID == "" {
|
||||||
|
return AccountMember{}, errors.New(errMissingAccountID)
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := "/accounts/" + accountID + "/members"
|
||||||
|
|
||||||
|
var newMember = AccountMemberInvitation{
|
||||||
|
Email: emailAddress,
|
||||||
|
Roles: roles,
|
||||||
|
}
|
||||||
|
res, err := api.makeRequest("POST", uri, newMember)
|
||||||
|
if err != nil {
|
||||||
|
return AccountMember{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountMemberListResponse AccountMemberDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accountMemberListResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccountMember{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountMemberListResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAccountMember removes a member from an account.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#account-members-remove-member
|
||||||
|
func (api *API) DeleteAccountMember(accountID string, userID string) error {
|
||||||
|
if accountID == "" {
|
||||||
|
return errors.New(errMissingAccountID)
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/members/%s", accountID, userID)
|
||||||
|
|
||||||
|
_, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccountMember modifies an existing account member.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#account-members-update-member
|
||||||
|
func (api *API) UpdateAccountMember(accountID string, userID string, member AccountMember) (AccountMember, error) {
|
||||||
|
if accountID == "" {
|
||||||
|
return AccountMember{}, errors.New(errMissingAccountID)
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/members/%s", accountID, userID)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, member)
|
||||||
|
if err != nil {
|
||||||
|
return AccountMember{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountMemberListResponse AccountMemberDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accountMemberListResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccountMember{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountMemberListResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountMember returns details of a single account member.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#account-members-member-details
|
||||||
|
func (api *API) AccountMember(accountID string, memberID string) (AccountMember, error) {
|
||||||
|
if accountID == "" {
|
||||||
|
return AccountMember{}, errors.New(errMissingAccountID)
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/accounts/%s/members/%s",
|
||||||
|
accountID,
|
||||||
|
memberID,
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return AccountMember{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountMemberResponse AccountMemberDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accountMemberResponse)
|
||||||
|
if err != nil {
|
||||||
|
return AccountMember{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountMemberResponse.Result, nil
|
||||||
|
}
|
80
vendor/github.com/cloudflare/cloudflare-go/account_roles.go
generated
vendored
Normal file
80
vendor/github.com/cloudflare/cloudflare-go/account_roles.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccountRole defines the roles that a member can have attached.
|
||||||
|
type AccountRole struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Permissions map[string]AccountRolePermission `json:"permissions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRolePermission is the shared structure for all permissions
|
||||||
|
// that can be assigned to a member.
|
||||||
|
type AccountRolePermission struct {
|
||||||
|
Read bool `json:"read"`
|
||||||
|
Edit bool `json:"edit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRolesListResponse represents the list response from the
|
||||||
|
// account roles.
|
||||||
|
type AccountRolesListResponse struct {
|
||||||
|
Result []AccountRole `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRoleDetailResponse is the API response, containing a single
|
||||||
|
// account role.
|
||||||
|
type AccountRoleDetailResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
Messages []string `json:"messages"`
|
||||||
|
Result AccountRole `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRoles returns all roles of an account.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#account-roles-list-roles
|
||||||
|
func (api *API) AccountRoles(accountID string) ([]AccountRole, error) {
|
||||||
|
uri := "/accounts/" + accountID + "/roles"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []AccountRole{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountRolesListResponse AccountRolesListResponse
|
||||||
|
err = json.Unmarshal(res, &accountRolesListResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []AccountRole{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountRolesListResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRole returns the details of a single account role.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#account-roles-role-details
|
||||||
|
func (api *API) AccountRole(accountID string, roleID string) (AccountRole, error) {
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/roles/%s", accountID, roleID)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return AccountRole{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountRole AccountRoleDetailResponse
|
||||||
|
err = json.Unmarshal(res, &accountRole)
|
||||||
|
if err != nil {
|
||||||
|
return AccountRole{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountRole.Result, nil
|
||||||
|
}
|
114
vendor/github.com/cloudflare/cloudflare-go/accounts.go
generated
vendored
Normal file
114
vendor/github.com/cloudflare/cloudflare-go/accounts.go
generated
vendored
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccountSettings outlines the available options for an account.
|
||||||
|
type AccountSettings struct {
|
||||||
|
EnforceTwoFactor bool `json:"enforce_twofactor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account represents the root object that owns resources.
|
||||||
|
type Account struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Settings *AccountSettings `json:"settings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountResponse represents the response from the accounts endpoint for a
|
||||||
|
// single account ID.
|
||||||
|
type AccountResponse struct {
|
||||||
|
Result Account `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountListResponse represents the response from the list accounts endpoint.
|
||||||
|
type AccountListResponse struct {
|
||||||
|
Result []Account `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountDetailResponse is the API response, containing a single Account.
|
||||||
|
type AccountDetailResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
Messages []string `json:"messages"`
|
||||||
|
Result Account `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accounts returns all accounts the logged in user has access to.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#accounts-list-accounts
|
||||||
|
func (api *API) Accounts(pageOpts PaginationOptions) ([]Account, ResultInfo, error) {
|
||||||
|
v := url.Values{}
|
||||||
|
if pageOpts.PerPage > 0 {
|
||||||
|
v.Set("per_page", strconv.Itoa(pageOpts.PerPage))
|
||||||
|
}
|
||||||
|
if pageOpts.Page > 0 {
|
||||||
|
v.Set("page", strconv.Itoa(pageOpts.Page))
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := "/accounts"
|
||||||
|
if len(v) > 0 {
|
||||||
|
uri = uri + "?" + v.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []Account{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accListResponse AccountListResponse
|
||||||
|
err = json.Unmarshal(res, &accListResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []Account{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return accListResponse.Result, accListResponse.ResultInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account returns a single account based on the ID.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#accounts-account-details
|
||||||
|
func (api *API) Account(accountID string) (Account, ResultInfo, error) {
|
||||||
|
uri := "/accounts/" + accountID
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return Account{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accResponse AccountResponse
|
||||||
|
err = json.Unmarshal(res, &accResponse)
|
||||||
|
if err != nil {
|
||||||
|
return Account{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accResponse.Result, accResponse.ResultInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccount allows management of an account using the account ID.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#accounts-update-account
|
||||||
|
func (api *API) UpdateAccount(accountID string, account Account) (Account, error) {
|
||||||
|
uri := "/accounts/" + accountID
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, account)
|
||||||
|
if err != nil {
|
||||||
|
return Account{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var a AccountDetailResponse
|
||||||
|
err = json.Unmarshal(res, &a)
|
||||||
|
if err != nil {
|
||||||
|
return Account{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.Result, nil
|
||||||
|
}
|
120
vendor/github.com/cloudflare/cloudflare-go/argo.go
generated
vendored
Normal file
120
vendor/github.com/cloudflare/cloudflare-go/argo.go
generated
vendored
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var validSettingValues = []string{"on", "off"}
|
||||||
|
|
||||||
|
// ArgoFeatureSetting is the structure of the API object for the
|
||||||
|
// argo smart routing and tiered caching settings.
|
||||||
|
type ArgoFeatureSetting struct {
|
||||||
|
Editable bool `json:"editable,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
ModifiedOn time.Time `json:"modified_on,omitempty"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgoDetailsResponse is the API response for the argo smart routing
|
||||||
|
// and tiered caching response.
|
||||||
|
type ArgoDetailsResponse struct {
|
||||||
|
Result ArgoFeatureSetting `json:"result"`
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgoSmartRouting returns the current settings for smart routing.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#argo-smart-routing-get-argo-smart-routing-setting
|
||||||
|
func (api *API) ArgoSmartRouting(zoneID string) (ArgoFeatureSetting, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/argo/smart_routing"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return ArgoFeatureSetting{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var argoDetailsResponse ArgoDetailsResponse
|
||||||
|
err = json.Unmarshal(res, &argoDetailsResponse)
|
||||||
|
if err != nil {
|
||||||
|
return ArgoFeatureSetting{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return argoDetailsResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateArgoSmartRouting updates the setting for smart routing.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#argo-smart-routing-patch-argo-smart-routing-setting
|
||||||
|
func (api *API) UpdateArgoSmartRouting(zoneID, settingValue string) (ArgoFeatureSetting, error) {
|
||||||
|
if !contains(validSettingValues, settingValue) {
|
||||||
|
return ArgoFeatureSetting{}, errors.New(fmt.Sprintf("invalid setting value '%s'. must be 'on' or 'off'", settingValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := "/zones/" + zoneID + "/argo/smart_routing"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PATCH", uri, ArgoFeatureSetting{Value: settingValue})
|
||||||
|
if err != nil {
|
||||||
|
return ArgoFeatureSetting{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var argoDetailsResponse ArgoDetailsResponse
|
||||||
|
err = json.Unmarshal(res, &argoDetailsResponse)
|
||||||
|
if err != nil {
|
||||||
|
return ArgoFeatureSetting{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return argoDetailsResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgoTieredCaching returns the current settings for tiered caching.
|
||||||
|
//
|
||||||
|
// API reference: TBA
|
||||||
|
func (api *API) ArgoTieredCaching(zoneID string) (ArgoFeatureSetting, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/argo/tiered_caching"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return ArgoFeatureSetting{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var argoDetailsResponse ArgoDetailsResponse
|
||||||
|
err = json.Unmarshal(res, &argoDetailsResponse)
|
||||||
|
if err != nil {
|
||||||
|
return ArgoFeatureSetting{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return argoDetailsResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateArgoTieredCaching updates the setting for tiered caching.
|
||||||
|
//
|
||||||
|
// API reference: TBA
|
||||||
|
func (api *API) UpdateArgoTieredCaching(zoneID, settingValue string) (ArgoFeatureSetting, error) {
|
||||||
|
if !contains(validSettingValues, settingValue) {
|
||||||
|
return ArgoFeatureSetting{}, errors.New(fmt.Sprintf("invalid setting value '%s'. must be 'on' or 'off'", settingValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := "/zones/" + zoneID + "/argo/tiered_caching"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PATCH", uri, ArgoFeatureSetting{Value: settingValue})
|
||||||
|
if err != nil {
|
||||||
|
return ArgoFeatureSetting{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var argoDetailsResponse ArgoDetailsResponse
|
||||||
|
err = json.Unmarshal(res, &argoDetailsResponse)
|
||||||
|
if err != nil {
|
||||||
|
return ArgoFeatureSetting{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return argoDetailsResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(s []string, e string) bool {
|
||||||
|
for _, a := range s {
|
||||||
|
if a == e {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
143
vendor/github.com/cloudflare/cloudflare-go/auditlogs.go
generated
vendored
Normal file
143
vendor/github.com/cloudflare/cloudflare-go/auditlogs.go
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditLogAction is a member of AuditLog, the action that was taken.
|
||||||
|
type AuditLogAction struct {
|
||||||
|
Result bool `json:"result"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditLogActor is a member of AuditLog, who performed the action.
|
||||||
|
type AuditLogActor struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditLogOwner is a member of AuditLog, who owns this audit log.
|
||||||
|
type AuditLogOwner struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditLogResource is a member of AuditLog, what was the action performed on.
|
||||||
|
type AuditLogResource struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditLog is an resource that represents an update in the cloudflare dash
|
||||||
|
type AuditLog struct {
|
||||||
|
Action AuditLogAction `json:"action"`
|
||||||
|
Actor AuditLogActor `json:"actor"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Metadata map[string]interface{} `json:"metadata"`
|
||||||
|
NewValue string `json:"newValue"`
|
||||||
|
OldValue string `json:"oldValue"`
|
||||||
|
Owner AuditLogOwner `json:"owner"`
|
||||||
|
Resource AuditLogResource `json:"resource"`
|
||||||
|
When time.Time `json:"when"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditLogResponse is the response returned from the cloudflare v4 api
|
||||||
|
type AuditLogResponse struct {
|
||||||
|
Response Response
|
||||||
|
Result []AuditLog `json:"result"`
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditLogFilter is an object for filtering the audit log response from the api.
|
||||||
|
type AuditLogFilter struct {
|
||||||
|
ID string
|
||||||
|
ActorIP string
|
||||||
|
ActorEmail string
|
||||||
|
Direction string
|
||||||
|
ZoneName string
|
||||||
|
Since string
|
||||||
|
Before string
|
||||||
|
PerPage int
|
||||||
|
Page int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String turns an audit log filter in to an HTTP Query Param
|
||||||
|
// list. It will not inclue empty members of the struct in the
|
||||||
|
// query parameters.
|
||||||
|
func (a AuditLogFilter) String() string {
|
||||||
|
params := "?"
|
||||||
|
if a.ID != "" {
|
||||||
|
params += "&id=" + a.ID
|
||||||
|
}
|
||||||
|
if a.ActorIP != "" {
|
||||||
|
params += "&actor.ip=" + a.ActorIP
|
||||||
|
}
|
||||||
|
if a.ActorEmail != "" {
|
||||||
|
params += "&actor.email=" + a.ActorEmail
|
||||||
|
}
|
||||||
|
if a.ZoneName != "" {
|
||||||
|
params += "&zone.name=" + a.ZoneName
|
||||||
|
}
|
||||||
|
if a.Direction != "" {
|
||||||
|
params += "&direction=" + a.Direction
|
||||||
|
}
|
||||||
|
if a.Since != "" {
|
||||||
|
params += "&since=" + a.Since
|
||||||
|
}
|
||||||
|
if a.Before != "" {
|
||||||
|
params += "&before=" + a.Before
|
||||||
|
}
|
||||||
|
if a.PerPage > 0 {
|
||||||
|
params += "&per_page=" + fmt.Sprintf("%d", a.PerPage)
|
||||||
|
}
|
||||||
|
if a.Page > 0 {
|
||||||
|
params += "&page=" + fmt.Sprintf("%d", a.Page)
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrganizationAuditLogs will return the audit logs of a specific
|
||||||
|
// organization, based on the ID passed in. The audit logs can be
|
||||||
|
// filtered based on any argument in the AuditLogFilter
|
||||||
|
//
|
||||||
|
// API Reference: https://api.cloudflare.com/#audit-logs-list-organization-audit-logs
|
||||||
|
func (api *API) GetOrganizationAuditLogs(organizationID string, a AuditLogFilter) (AuditLogResponse, error) {
|
||||||
|
uri := "/organizations/" + organizationID + "/audit_logs" + fmt.Sprintf("%s", a)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return AuditLogResponse{}, err
|
||||||
|
}
|
||||||
|
buf, err := base64.RawStdEncoding.DecodeString(string(res))
|
||||||
|
if err != nil {
|
||||||
|
return AuditLogResponse{}, err
|
||||||
|
}
|
||||||
|
return unmarshalReturn(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshalReturn will unmarshal bytes and return an auditlogresponse
|
||||||
|
func unmarshalReturn(res []byte) (AuditLogResponse, error) {
|
||||||
|
var auditResponse AuditLogResponse
|
||||||
|
err := json.Unmarshal(res, &auditResponse)
|
||||||
|
if err != nil {
|
||||||
|
return auditResponse, err
|
||||||
|
}
|
||||||
|
return auditResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserAuditLogs will return your user's audit logs. The audit logs can be
|
||||||
|
// filtered based on any argument in the AuditLogFilter
|
||||||
|
//
|
||||||
|
// API Reference: https://api.cloudflare.com/#audit-logs-list-user-audit-logs
|
||||||
|
func (api *API) GetUserAuditLogs(a AuditLogFilter) (AuditLogResponse, error) {
|
||||||
|
uri := "/user/audit_logs" + fmt.Sprintf("%s", a)
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return AuditLogResponse{}, err
|
||||||
|
}
|
||||||
|
return unmarshalReturn(res)
|
||||||
|
}
|
435
vendor/github.com/cloudflare/cloudflare-go/cloudflare.go
generated
vendored
Normal file
435
vendor/github.com/cloudflare/cloudflare-go/cloudflare.go
generated
vendored
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
// Package cloudflare implements the Cloudflare v4 API.
|
||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
const apiURL = "https://api.cloudflare.com/client/v4"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AuthKeyEmail specifies that we should authenticate with API key and email address
|
||||||
|
AuthKeyEmail = 1 << iota
|
||||||
|
// AuthUserService specifies that we should authenticate with a User-Service key
|
||||||
|
AuthUserService
|
||||||
|
// AuthToken specifies that we should authenticate with an API Token
|
||||||
|
AuthToken
|
||||||
|
)
|
||||||
|
|
||||||
|
// API holds the configuration for the current API client. A client should not
|
||||||
|
// be modified concurrently.
|
||||||
|
type API struct {
|
||||||
|
APIKey string
|
||||||
|
APIEmail string
|
||||||
|
APIUserServiceKey string
|
||||||
|
APIToken string
|
||||||
|
BaseURL string
|
||||||
|
AccountID string
|
||||||
|
UserAgent string
|
||||||
|
headers http.Header
|
||||||
|
httpClient *http.Client
|
||||||
|
authType int
|
||||||
|
rateLimiter *rate.Limiter
|
||||||
|
retryPolicy RetryPolicy
|
||||||
|
logger Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// newClient provides shared logic for New and NewWithUserServiceKey
|
||||||
|
func newClient(opts ...Option) (*API, error) {
|
||||||
|
silentLogger := log.New(ioutil.Discard, "", log.LstdFlags)
|
||||||
|
|
||||||
|
api := &API{
|
||||||
|
BaseURL: apiURL,
|
||||||
|
headers: make(http.Header),
|
||||||
|
rateLimiter: rate.NewLimiter(rate.Limit(4), 1), // 4rps equates to default api limit (1200 req/5 min)
|
||||||
|
retryPolicy: RetryPolicy{
|
||||||
|
MaxRetries: 3,
|
||||||
|
MinRetryDelay: time.Duration(1) * time.Second,
|
||||||
|
MaxRetryDelay: time.Duration(30) * time.Second,
|
||||||
|
},
|
||||||
|
logger: silentLogger,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, err := newClient(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
api.APIKey = key
|
||||||
|
api.APIEmail = email
|
||||||
|
api.authType = AuthKeyEmail
|
||||||
|
|
||||||
|
return api, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithAPIToken creates a new Cloudflare v4 API client using API Tokens
|
||||||
|
func NewWithAPIToken(token string, opts ...Option) (*API, error) {
|
||||||
|
if token == "" {
|
||||||
|
return nil, errors.New(errEmptyAPIToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
api, err := newClient(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
api.APIToken = token
|
||||||
|
api.authType = AuthToken
|
||||||
|
|
||||||
|
return api, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithUserServiceKey creates a new Cloudflare v4 API client using service key authentication.
|
||||||
|
func NewWithUserServiceKey(key string, opts ...Option) (*API, error) {
|
||||||
|
if key == "" {
|
||||||
|
return nil, errors.New(errEmptyCredentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
api, err := newClient(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
api.APIUserServiceKey = key
|
||||||
|
api.authType = AuthUserService
|
||||||
|
|
||||||
|
return api, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAuthType sets the authentication method (AuthKeyEmail, AuthToken, or AuthUserService).
|
||||||
|
func (api *API) SetAuthType(authType int) {
|
||||||
|
api.authType = authType
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneIDByName retrieves a zone's ID from the name.
|
||||||
|
func (api *API) ZoneIDByName(zoneName string) (string, error) {
|
||||||
|
res, err := api.ListZonesContext(context.TODO(), WithZoneFilter(zoneName))
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "ListZonesContext command failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res.Result) > 1 && api.AccountID == "" {
|
||||||
|
return "", errors.New("ambiguous zone name used without an account ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, zone := range res.Result {
|
||||||
|
if api.AccountID != "" {
|
||||||
|
if zone.Name == zoneName && api.AccountID == zone.Account.ID {
|
||||||
|
return zone.ID, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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 returning. params will be serialized to JSON.
|
||||||
|
func (api *API) makeRequest(method, uri string, params interface{}) ([]byte, error) {
|
||||||
|
return api.makeRequestWithAuthType(context.TODO(), method, uri, params, api.authType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) makeRequestContext(ctx context.Context, method, uri string, params interface{}) ([]byte, error) {
|
||||||
|
return api.makeRequestWithAuthType(ctx, method, uri, params, api.authType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) makeRequestWithHeaders(method, uri string, params interface{}, headers http.Header) ([]byte, error) {
|
||||||
|
return api.makeRequestWithAuthTypeAndHeaders(context.TODO(), method, uri, params, api.authType, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) makeRequestWithAuthType(ctx context.Context, method, uri string, params interface{}, authType int) ([]byte, error) {
|
||||||
|
return api.makeRequestWithAuthTypeAndHeaders(ctx, method, uri, params, authType, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) makeRequestWithAuthTypeAndHeaders(ctx context.Context, method, uri string, params interface{}, authType int, headers http.Header) ([]byte, error) {
|
||||||
|
// Replace nil with a JSON object if needed
|
||||||
|
var jsonBody []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if params != nil {
|
||||||
|
if paramBytes, ok := params.([]byte); ok {
|
||||||
|
jsonBody = paramBytes
|
||||||
|
} else {
|
||||||
|
jsonBody, err = json.Marshal(params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error marshalling params to JSON")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jsonBody = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp *http.Response
|
||||||
|
var respErr error
|
||||||
|
var reqBody io.Reader
|
||||||
|
var respBody []byte
|
||||||
|
for i := 0; i <= api.retryPolicy.MaxRetries; i++ {
|
||||||
|
if jsonBody != nil {
|
||||||
|
reqBody = bytes.NewReader(jsonBody)
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
// expect the backoff introduced here on errored requests to dominate the effect of rate limiting
|
||||||
|
// don't need a random component here as the rate limiter should do something similar
|
||||||
|
// nb time duration could truncate an arbitrary float. Since our inputs are all ints, we should be ok
|
||||||
|
sleepDuration := time.Duration(math.Pow(2, float64(i-1)) * float64(api.retryPolicy.MinRetryDelay))
|
||||||
|
|
||||||
|
if sleepDuration > api.retryPolicy.MaxRetryDelay {
|
||||||
|
sleepDuration = api.retryPolicy.MaxRetryDelay
|
||||||
|
}
|
||||||
|
// useful to do some simple logging here, maybe introduce levels later
|
||||||
|
api.logger.Printf("Sleeping %s before retry attempt number %d for request %s %s", sleepDuration.String(), i, method, uri)
|
||||||
|
time.Sleep(sleepDuration)
|
||||||
|
|
||||||
|
}
|
||||||
|
err = api.rateLimiter.Wait(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "Error caused by request rate limiting")
|
||||||
|
}
|
||||||
|
resp, respErr = api.request(ctx, method, uri, reqBody, authType, headers)
|
||||||
|
|
||||||
|
// retry if the server is rate limiting us or if it failed
|
||||||
|
// assumes server operations are rolled back on failure
|
||||||
|
if respErr != nil || resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500 {
|
||||||
|
// if we got a valid http response, try to read body so we can reuse the connection
|
||||||
|
// see https://golang.org/pkg/net/http/#Client.Do
|
||||||
|
if respErr == nil {
|
||||||
|
respBody, err = ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
respErr = errors.Wrap(err, "could not read response body")
|
||||||
|
|
||||||
|
api.logger.Printf("Request: %s %s got an error response %d: %s\n", method, uri, resp.StatusCode,
|
||||||
|
strings.Replace(strings.Replace(string(respBody), "\n", "", -1), "\t", "", -1))
|
||||||
|
} else {
|
||||||
|
api.logger.Printf("Error performing request: %s %s : %s \n", method, uri, respErr.Error())
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
respBody, err = ioutil.ReadAll(resp.Body)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not read response body")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices:
|
||||||
|
case resp.StatusCode == http.StatusUnauthorized:
|
||||||
|
return nil, errors.Errorf("HTTP status %d: invalid credentials", resp.StatusCode)
|
||||||
|
case resp.StatusCode == http.StatusForbidden:
|
||||||
|
return nil, errors.Errorf("HTTP status %d: insufficient permissions", resp.StatusCode)
|
||||||
|
case resp.StatusCode == http.StatusServiceUnavailable,
|
||||||
|
resp.StatusCode == http.StatusBadGateway,
|
||||||
|
resp.StatusCode == http.StatusGatewayTimeout,
|
||||||
|
resp.StatusCode == 522,
|
||||||
|
resp.StatusCode == 523,
|
||||||
|
resp.StatusCode == 524:
|
||||||
|
return nil, errors.Errorf("HTTP status %d: service failure", resp.StatusCode)
|
||||||
|
// This isn't a great solution due to the way the `default` case is
|
||||||
|
// a catch all and that the `filters/validate-expr` returns a HTTP 400
|
||||||
|
// yet the clients need to use the HTTP body as a JSON string.
|
||||||
|
case resp.StatusCode == 400 && strings.HasSuffix(resp.Request.URL.Path, "/filters/validate-expr"):
|
||||||
|
return nil, errors.Errorf("%s", respBody)
|
||||||
|
default:
|
||||||
|
var s string
|
||||||
|
if respBody != nil {
|
||||||
|
s = string(respBody)
|
||||||
|
}
|
||||||
|
return nil, errors.Errorf("HTTP status %d: content %q", resp.StatusCode, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBody, 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(ctx context.Context, method, uri string, reqBody io.Reader, authType int, headers http.Header) (*http.Response, error) {
|
||||||
|
req, err := http.NewRequest(method, api.BaseURL+uri, reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "HTTP request creation failed")
|
||||||
|
}
|
||||||
|
req.WithContext(ctx)
|
||||||
|
|
||||||
|
combinedHeaders := make(http.Header)
|
||||||
|
copyHeader(combinedHeaders, api.headers)
|
||||||
|
copyHeader(combinedHeaders, headers)
|
||||||
|
req.Header = combinedHeaders
|
||||||
|
|
||||||
|
if authType&AuthKeyEmail != 0 {
|
||||||
|
req.Header.Set("X-Auth-Key", api.APIKey)
|
||||||
|
req.Header.Set("X-Auth-Email", api.APIEmail)
|
||||||
|
}
|
||||||
|
if authType&AuthUserService != 0 {
|
||||||
|
req.Header.Set("X-Auth-User-Service-Key", api.APIUserServiceKey)
|
||||||
|
}
|
||||||
|
if authType&AuthToken != 0 {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+api.APIToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
if api.UserAgent != "" {
|
||||||
|
req.Header.Set("User-Agent", api.UserAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Header.Get("Content-Type") == "" {
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := api.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "HTTP request failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the base URL to use for API endpoints that exist for accounts.
|
||||||
|
// If an account option was used when creating the API instance, returns
|
||||||
|
// the account URL.
|
||||||
|
//
|
||||||
|
// accountBase is the base URL for endpoints referring to the current user.
|
||||||
|
// It exists as a parameter because it is not consistent across APIs.
|
||||||
|
func (api *API) userBaseURL(accountBase string) string {
|
||||||
|
if api.AccountID != "" {
|
||||||
|
return "/accounts/" + api.AccountID
|
||||||
|
}
|
||||||
|
return accountBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyHeader copies all headers for `source` and sets them on `target`.
|
||||||
|
// based on https://godoc.org/github.com/golang/gddo/httputil/header#Copy
|
||||||
|
func copyHeader(target, source http.Header) {
|
||||||
|
for k, vs := range source {
|
||||||
|
target[k] = vs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawResponse keeps the result as JSON form
|
||||||
|
type RawResponse struct {
|
||||||
|
Response
|
||||||
|
Result json.RawMessage `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw makes a HTTP request with user provided params and returns the
|
||||||
|
// result as untouched JSON.
|
||||||
|
func (api *API) Raw(method, endpoint string, data interface{}) (json.RawMessage, error) {
|
||||||
|
res, err := api.makeRequest(method, endpoint, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r RawResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaginationOptions can be passed to a list request to configure paging
|
||||||
|
// These values will be defaulted if omitted, and PerPage has min/max limits set by resource
|
||||||
|
type PaginationOptions struct {
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PerPage int `json:"per_page,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryPolicy specifies number of retries and min/max retry delays
|
||||||
|
// This config is used when the client exponentially backs off after errored requests
|
||||||
|
type RetryPolicy struct {
|
||||||
|
MaxRetries int
|
||||||
|
MinRetryDelay time.Duration
|
||||||
|
MaxRetryDelay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger defines the interface this library needs to use logging
|
||||||
|
// This is a subset of the methods implemented in the log package
|
||||||
|
type Logger interface {
|
||||||
|
Printf(format string, v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqOption is a functional option for configuring API requests
|
||||||
|
type ReqOption func(opt *reqOption)
|
||||||
|
type reqOption struct {
|
||||||
|
params url.Values
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithZoneFilter applies a filter based on zone name.
|
||||||
|
func WithZoneFilter(zone string) ReqOption {
|
||||||
|
return func(opt *reqOption) {
|
||||||
|
opt.params.Set("name", zone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPagination configures the pagination for a response.
|
||||||
|
func WithPagination(opts PaginationOptions) ReqOption {
|
||||||
|
return func(opt *reqOption) {
|
||||||
|
opt.params.Set("page", strconv.Itoa(opts.Page))
|
||||||
|
opt.params.Set("per_page", strconv.Itoa(opts.PerPage))
|
||||||
|
}
|
||||||
|
}
|
161
vendor/github.com/cloudflare/cloudflare-go/custom_hostname.go
generated
vendored
Normal file
161
vendor/github.com/cloudflare/cloudflare-go/custom_hostname.go
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CustomHostnameSSLSettings represents the SSL settings for a custom hostname.
|
||||||
|
type CustomHostnameSSLSettings struct {
|
||||||
|
HTTP2 string `json:"http2,omitempty"`
|
||||||
|
TLS13 string `json:"tls_1_3,omitempty"`
|
||||||
|
MinTLSVersion string `json:"min_tls_version,omitempty"`
|
||||||
|
Ciphers []string `json:"ciphers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomHostnameSSL represents the SSL section in a given custom hostname.
|
||||||
|
type CustomHostnameSSL struct {
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
Method string `json:"method,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
CnameTarget string `json:"cname_target,omitempty"`
|
||||||
|
CnameName string `json:"cname,omitempty"`
|
||||||
|
Settings CustomHostnameSSLSettings `json:"settings,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomMetadata defines custom metadata for the hostname. This requires logic to be implemented by Cloudflare to act on the data provided.
|
||||||
|
type CustomMetadata map[string]interface{}
|
||||||
|
|
||||||
|
// CustomHostname represents a custom hostname in a zone.
|
||||||
|
type CustomHostname struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Hostname string `json:"hostname,omitempty"`
|
||||||
|
CustomOriginServer string `json:"custom_origin_server,omitempty"`
|
||||||
|
SSL CustomHostnameSSL `json:"ssl,omitempty"`
|
||||||
|
CustomMetadata CustomMetadata `json:"custom_metadata,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomHostnameResponse represents a response from the Custom Hostnames endpoints.
|
||||||
|
type CustomHostnameResponse struct {
|
||||||
|
Result CustomHostname `json:"result"`
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomHostnameListResponse represents a response from the Custom Hostnames endpoints.
|
||||||
|
type CustomHostnameListResponse struct {
|
||||||
|
Result []CustomHostname `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCustomHostnameSSL modifies SSL configuration for the given custom
|
||||||
|
// hostname in the given zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-update-custom-hostname-configuration
|
||||||
|
func (api *API) UpdateCustomHostnameSSL(zoneID string, customHostnameID string, ssl CustomHostnameSSL) (CustomHostname, error) {
|
||||||
|
return CustomHostname{}, errors.New("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCustomHostname deletes a custom hostname (and any issued SSL
|
||||||
|
// certificates).
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-delete-a-custom-hostname-and-any-issued-ssl-certificates-
|
||||||
|
func (api *API) DeleteCustomHostname(zoneID string, customHostnameID string) error {
|
||||||
|
uri := "/zones/" + zoneID + "/custom_hostnames/" + customHostnameID
|
||||||
|
res, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response *CustomHostnameResponse
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCustomHostname creates a new custom hostname and requests that an SSL certificate be issued for it.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-create-custom-hostname
|
||||||
|
func (api *API) CreateCustomHostname(zoneID string, ch CustomHostname) (*CustomHostnameResponse, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/custom_hostnames"
|
||||||
|
res, err := api.makeRequest("POST", uri, ch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response *CustomHostnameResponse
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomHostnames fetches custom hostnames for the given zone,
|
||||||
|
// by applying filter.Hostname if not empty and scoping the result to page'th 50 items.
|
||||||
|
//
|
||||||
|
// The returned ResultInfo can be used to implement pagination.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-list-custom-hostnames
|
||||||
|
func (api *API) CustomHostnames(zoneID string, page int, filter CustomHostname) ([]CustomHostname, ResultInfo, error) {
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("per_page", "50")
|
||||||
|
v.Set("page", strconv.Itoa(page))
|
||||||
|
if filter.Hostname != "" {
|
||||||
|
v.Set("hostname", filter.Hostname)
|
||||||
|
}
|
||||||
|
query := "?" + v.Encode()
|
||||||
|
|
||||||
|
uri := "/zones/" + zoneID + "/custom_hostnames" + query
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []CustomHostname{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var customHostnameListResponse CustomHostnameListResponse
|
||||||
|
err = json.Unmarshal(res, &customHostnameListResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []CustomHostname{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return customHostnameListResponse.Result, customHostnameListResponse.ResultInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomHostname inspects the given custom hostname in the given zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-custom-hostname-configuration-details
|
||||||
|
func (api *API) CustomHostname(zoneID string, customHostnameID string) (CustomHostname, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/custom_hostnames/" + customHostnameID
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return CustomHostname{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response CustomHostnameResponse
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return CustomHostname{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomHostnameIDByName retrieves the ID for the given hostname in the given zone.
|
||||||
|
func (api *API) CustomHostnameIDByName(zoneID string, hostname string) (string, error) {
|
||||||
|
customHostnames, _, err := api.CustomHostnames(zoneID, 1, CustomHostname{Hostname: hostname})
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "CustomHostnames command failed")
|
||||||
|
}
|
||||||
|
for _, ch := range customHostnames {
|
||||||
|
if ch.Hostname == hostname {
|
||||||
|
return ch.ID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("CustomHostname could not be found")
|
||||||
|
}
|
176
vendor/github.com/cloudflare/cloudflare-go/custom_pages.go
generated
vendored
Normal file
176
vendor/github.com/cloudflare/cloudflare-go/custom_pages.go
generated
vendored
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CustomPage represents a custom page configuration.
|
||||||
|
type CustomPage struct {
|
||||||
|
CreatedOn time.Time `json:"created_on"`
|
||||||
|
ModifiedOn time.Time `json:"modified_on"`
|
||||||
|
URL interface{} `json:"url"`
|
||||||
|
State string `json:"state"`
|
||||||
|
RequiredTokens []string `json:"required_tokens"`
|
||||||
|
PreviewTarget string `json:"preview_target"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomPageResponse represents the response from the custom pages endpoint.
|
||||||
|
type CustomPageResponse struct {
|
||||||
|
Response
|
||||||
|
Result []CustomPage `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomPageDetailResponse represents the response from the custom page endpoint.
|
||||||
|
type CustomPageDetailResponse struct {
|
||||||
|
Response
|
||||||
|
Result CustomPage `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomPageOptions is used to determine whether or not the operation
|
||||||
|
// should take place on an account or zone level based on which is
|
||||||
|
// provided to the function.
|
||||||
|
//
|
||||||
|
// A non-empty value denotes desired use.
|
||||||
|
type CustomPageOptions struct {
|
||||||
|
AccountID string
|
||||||
|
ZoneID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomPageParameters is used to update a particular custom page with
|
||||||
|
// the values provided.
|
||||||
|
type CustomPageParameters struct {
|
||||||
|
URL interface{} `json:"url"`
|
||||||
|
State string `json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomPages lists custom pages for a zone or account.
|
||||||
|
//
|
||||||
|
// Zone API reference: https://api.cloudflare.com/#custom-pages-for-a-zone-list-available-custom-pages
|
||||||
|
// Account API reference: https://api.cloudflare.com/#custom-pages-account--list-custom-pages
|
||||||
|
func (api *API) CustomPages(options *CustomPageOptions) ([]CustomPage, error) {
|
||||||
|
var (
|
||||||
|
pageType, identifier string
|
||||||
|
)
|
||||||
|
|
||||||
|
if options.AccountID == "" && options.ZoneID == "" {
|
||||||
|
return nil, errors.New("either account ID or zone ID must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.AccountID != "" && options.ZoneID != "" {
|
||||||
|
return nil, errors.New("account ID and zone ID are mutually exclusive")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should the account ID be defined, treat this as an account level operation.
|
||||||
|
if options.AccountID != "" {
|
||||||
|
pageType = "accounts"
|
||||||
|
identifier = options.AccountID
|
||||||
|
} else {
|
||||||
|
pageType = "zones"
|
||||||
|
identifier = options.ZoneID
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/%s/%s/custom_pages", pageType, identifier)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var customPageResponse CustomPageResponse
|
||||||
|
err = json.Unmarshal(res, &customPageResponse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return customPageResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomPage lists a single custom page based on the ID.
|
||||||
|
//
|
||||||
|
// Zone API reference: https://api.cloudflare.com/#custom-pages-for-a-zone-custom-page-details
|
||||||
|
// Account API reference: https://api.cloudflare.com/#custom-pages-account--custom-page-details
|
||||||
|
func (api *API) CustomPage(options *CustomPageOptions, customPageID string) (CustomPage, error) {
|
||||||
|
var (
|
||||||
|
pageType, identifier string
|
||||||
|
)
|
||||||
|
|
||||||
|
if options.AccountID == "" && options.ZoneID == "" {
|
||||||
|
return CustomPage{}, errors.New("either account ID or zone ID must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.AccountID != "" && options.ZoneID != "" {
|
||||||
|
return CustomPage{}, errors.New("account ID and zone ID are mutually exclusive")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should the account ID be defined, treat this as an account level operation.
|
||||||
|
if options.AccountID != "" {
|
||||||
|
pageType = "accounts"
|
||||||
|
identifier = options.AccountID
|
||||||
|
} else {
|
||||||
|
pageType = "zones"
|
||||||
|
identifier = options.ZoneID
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/%s/%s/custom_pages/%s", pageType, identifier, customPageID)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return CustomPage{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var customPageResponse CustomPageDetailResponse
|
||||||
|
err = json.Unmarshal(res, &customPageResponse)
|
||||||
|
if err != nil {
|
||||||
|
return CustomPage{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return customPageResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCustomPage updates a single custom page setting.
|
||||||
|
//
|
||||||
|
// Zone API reference: https://api.cloudflare.com/#custom-pages-for-a-zone-update-custom-page-url
|
||||||
|
// Account API reference: https://api.cloudflare.com/#custom-pages-account--update-custom-page
|
||||||
|
func (api *API) UpdateCustomPage(options *CustomPageOptions, customPageID string, pageParameters CustomPageParameters) (CustomPage, error) {
|
||||||
|
var (
|
||||||
|
pageType, identifier string
|
||||||
|
)
|
||||||
|
|
||||||
|
if options.AccountID == "" && options.ZoneID == "" {
|
||||||
|
return CustomPage{}, errors.New("either account ID or zone ID must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.AccountID != "" && options.ZoneID != "" {
|
||||||
|
return CustomPage{}, errors.New("account ID and zone ID are mutually exclusive")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should the account ID be defined, treat this as an account level operation.
|
||||||
|
if options.AccountID != "" {
|
||||||
|
pageType = "accounts"
|
||||||
|
identifier = options.AccountID
|
||||||
|
} else {
|
||||||
|
pageType = "zones"
|
||||||
|
identifier = options.ZoneID
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/%s/%s/custom_pages/%s", pageType, identifier, customPageID)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, pageParameters)
|
||||||
|
if err != nil {
|
||||||
|
return CustomPage{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var customPageResponse CustomPageDetailResponse
|
||||||
|
err = json.Unmarshal(res, &customPageResponse)
|
||||||
|
if err != nil {
|
||||||
|
return CustomPage{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return customPageResponse.Result, nil
|
||||||
|
}
|
174
vendor/github.com/cloudflare/cloudflare-go/dns.go
generated
vendored
Normal file
174
vendor/github.com/cloudflare/cloudflare-go/dns.go
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
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"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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.
|
||||||
|
//
|
||||||
|
// This takes a DNSRecord to allow filtering of the results returned.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#dns-records-for-a-zone-list-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
|
||||||
|
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
|
||||||
|
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("PATCH", 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
|
||||||
|
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
|
||||||
|
}
|
40
vendor/github.com/cloudflare/cloudflare-go/duration.go
generated
vendored
Normal file
40
vendor/github.com/cloudflare/cloudflare-go/duration.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Duration implements json.Marshaler and json.Unmarshaler for time.Duration
|
||||||
|
// using the fmt.Stringer interface of time.Duration and time.ParseDuration.
|
||||||
|
type Duration struct {
|
||||||
|
time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON encodes a Duration as a JSON string formatted using String.
|
||||||
|
func (d Duration) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(d.Duration.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON decodes a Duration from a JSON string parsed using time.ParseDuration.
|
||||||
|
func (d *Duration) UnmarshalJSON(buf []byte) error {
|
||||||
|
var str string
|
||||||
|
|
||||||
|
err := json.Unmarshal(buf, &str)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dur, err := time.ParseDuration(str)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Duration = dur
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ = json.Marshaler((*Duration)(nil))
|
||||||
|
_ = json.Unmarshaler((*Duration)(nil))
|
||||||
|
)
|
50
vendor/github.com/cloudflare/cloudflare-go/errors.go
generated
vendored
Normal file
50
vendor/github.com/cloudflare/cloudflare-go/errors.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
// Error messages
|
||||||
|
const (
|
||||||
|
errEmptyCredentials = "invalid credentials: key & email must not be empty"
|
||||||
|
errEmptyAPIToken = "invalid credentials: API Token must not be empty"
|
||||||
|
errMakeRequestError = "error from makeRequest"
|
||||||
|
errUnmarshalError = "error unmarshalling the JSON response"
|
||||||
|
errRequestNotSuccessful = "error reported by API"
|
||||||
|
errMissingAccountID = "account ID is empty and must be provided"
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
241
vendor/github.com/cloudflare/cloudflare-go/filter.go
generated
vendored
Normal file
241
vendor/github.com/cloudflare/cloudflare-go/filter.go
generated
vendored
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filter holds the structure of the filter type.
|
||||||
|
type Filter struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Expression string `json:"expression"`
|
||||||
|
Paused bool `json:"paused"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Property is mentioned in documentation however isn't populated in
|
||||||
|
// any of the API requests. For now, let's just omit it unless it's
|
||||||
|
// provided.
|
||||||
|
Ref string `json:"ref,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FiltersDetailResponse is the API response that is returned
|
||||||
|
// for requesting all filters on a zone.
|
||||||
|
type FiltersDetailResponse struct {
|
||||||
|
Result []Filter `json:"result"`
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterDetailResponse is the API response that is returned
|
||||||
|
// for requesting a single filter on a zone.
|
||||||
|
type FilterDetailResponse struct {
|
||||||
|
Result Filter `json:"result"`
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterValidateExpression represents the JSON payload for checking
|
||||||
|
// an expression.
|
||||||
|
type FilterValidateExpression struct {
|
||||||
|
Expression string `json:"expression"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterValidateExpressionResponse represents the API response for
|
||||||
|
// checking the expression. It conforms to the JSON API approach however
|
||||||
|
// we don't need all of the fields exposed.
|
||||||
|
type FilterValidateExpressionResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []FilterValidationExpressionMessage `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterValidationExpressionMessage represents the API error message.
|
||||||
|
type FilterValidationExpressionMessage struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter returns a single filter in a zone based on the filter ID.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/get/#get-by-filter-id
|
||||||
|
func (api *API) Filter(zoneID, filterID string) (Filter, error) {
|
||||||
|
uri := fmt.Sprintf("/zones/%s/filters/%s", zoneID, filterID)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return Filter{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var filterResponse FilterDetailResponse
|
||||||
|
err = json.Unmarshal(res, &filterResponse)
|
||||||
|
if err != nil {
|
||||||
|
return Filter{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filters returns all filters for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/get/#get-all-filters
|
||||||
|
func (api *API) Filters(zoneID string, pageOpts PaginationOptions) ([]Filter, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/filters"
|
||||||
|
v := url.Values{}
|
||||||
|
|
||||||
|
if pageOpts.PerPage > 0 {
|
||||||
|
v.Set("per_page", strconv.Itoa(pageOpts.PerPage))
|
||||||
|
}
|
||||||
|
|
||||||
|
if pageOpts.Page > 0 {
|
||||||
|
v.Set("page", strconv.Itoa(pageOpts.Page))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v) > 0 {
|
||||||
|
uri = uri + "?" + v.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []Filter{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var filtersResponse FiltersDetailResponse
|
||||||
|
err = json.Unmarshal(res, &filtersResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []Filter{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtersResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFilters creates new filters.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/post/
|
||||||
|
func (api *API) CreateFilters(zoneID string, filters []Filter) ([]Filter, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/filters"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("POST", uri, filters)
|
||||||
|
if err != nil {
|
||||||
|
return []Filter{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var filtersResponse FiltersDetailResponse
|
||||||
|
err = json.Unmarshal(res, &filtersResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []Filter{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtersResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFilter updates a single filter.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/put/#update-a-single-filter
|
||||||
|
func (api *API) UpdateFilter(zoneID string, filter Filter) (Filter, error) {
|
||||||
|
if filter.ID == "" {
|
||||||
|
return Filter{}, errors.Errorf("filter ID cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/zones/%s/filters/%s", zoneID, filter.ID)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, filter)
|
||||||
|
if err != nil {
|
||||||
|
return Filter{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var filterResponse FilterDetailResponse
|
||||||
|
err = json.Unmarshal(res, &filterResponse)
|
||||||
|
if err != nil {
|
||||||
|
return Filter{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFilters updates many filters at once.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/put/#update-multiple-filters
|
||||||
|
func (api *API) UpdateFilters(zoneID string, filters []Filter) ([]Filter, error) {
|
||||||
|
for _, filter := range filters {
|
||||||
|
if filter.ID == "" {
|
||||||
|
return []Filter{}, errors.Errorf("filter ID cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := "/zones/" + zoneID + "/filters"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, filters)
|
||||||
|
if err != nil {
|
||||||
|
return []Filter{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var filtersResponse FiltersDetailResponse
|
||||||
|
err = json.Unmarshal(res, &filtersResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []Filter{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtersResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFilter deletes a single filter.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/delete/#delete-a-single-filter
|
||||||
|
func (api *API) DeleteFilter(zoneID, filterID string) error {
|
||||||
|
if filterID == "" {
|
||||||
|
return errors.Errorf("filter ID cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/zones/%s/filters/%s", zoneID, filterID)
|
||||||
|
|
||||||
|
_, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFilters deletes multiple filters.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/delete/#delete-multiple-filters
|
||||||
|
func (api *API) DeleteFilters(zoneID string, filterIDs []string) error {
|
||||||
|
ids := strings.Join(filterIDs, ",")
|
||||||
|
uri := fmt.Sprintf("/zones/%s/filters?id=%s", zoneID, ids)
|
||||||
|
|
||||||
|
_, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateFilterExpression checks correctness of a filter expression.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/validation/
|
||||||
|
func (api *API) ValidateFilterExpression(expression string) error {
|
||||||
|
uri := fmt.Sprintf("/filters/validate-expr")
|
||||||
|
expressionPayload := FilterValidateExpression{Expression: expression}
|
||||||
|
|
||||||
|
_, err := api.makeRequest("POST", uri, expressionPayload)
|
||||||
|
if err != nil {
|
||||||
|
var filterValidationResponse FilterValidateExpressionResponse
|
||||||
|
|
||||||
|
jsonErr := json.Unmarshal([]byte(err.Error()), &filterValidationResponse)
|
||||||
|
if jsonErr != nil {
|
||||||
|
return errors.Wrap(jsonErr, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filterValidationResponse.Success != true {
|
||||||
|
// Unsure why but the API returns `errors` as an array but it only
|
||||||
|
// ever shows the issue with one problem at a time ¯\_(ツ)_/¯
|
||||||
|
return errors.Errorf(filterValidationResponse.Errors[0].Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
280
vendor/github.com/cloudflare/cloudflare-go/firewall.go
generated
vendored
Normal file
280
vendor/github.com/cloudflare/cloudflare-go/firewall.go
generated
vendored
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessRule represents a firewall access rule.
|
||||||
|
type AccessRule struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Notes string `json:"notes,omitempty"`
|
||||||
|
AllowedModes []string `json:"allowed_modes,omitempty"`
|
||||||
|
Mode string `json:"mode,omitempty"`
|
||||||
|
Configuration AccessRuleConfiguration `json:"configuration,omitempty"`
|
||||||
|
Scope AccessRuleScope `json:"scope,omitempty"`
|
||||||
|
CreatedOn time.Time `json:"created_on,omitempty"`
|
||||||
|
ModifiedOn time.Time `json:"modified_on,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessRuleConfiguration represents the configuration of a firewall
|
||||||
|
// access rule.
|
||||||
|
type AccessRuleConfiguration struct {
|
||||||
|
Target string `json:"target,omitempty"`
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessRuleScope represents the scope of a firewall access rule.
|
||||||
|
type AccessRuleScope struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessRuleResponse represents the response from the firewall access
|
||||||
|
// rule endpoint.
|
||||||
|
type AccessRuleResponse struct {
|
||||||
|
Result AccessRule `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessRuleListResponse represents the response from the list access rules
|
||||||
|
// endpoint.
|
||||||
|
type AccessRuleListResponse struct {
|
||||||
|
Result []AccessRule `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUserAccessRules returns a slice of access rules for the logged-in user.
|
||||||
|
//
|
||||||
|
// This takes an AccessRule to allow filtering of the results returned.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#user-level-firewall-access-rule-list-access-rules
|
||||||
|
func (api *API) ListUserAccessRules(accessRule AccessRule, page int) (*AccessRuleListResponse, error) {
|
||||||
|
return api.listAccessRules("/user", accessRule, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserAccessRule creates a firewall access rule for the logged-in user.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#user-level-firewall-access-rule-create-access-rule
|
||||||
|
func (api *API) CreateUserAccessRule(accessRule AccessRule) (*AccessRuleResponse, error) {
|
||||||
|
return api.createAccessRule("/user", accessRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAccessRule returns the details of a user's account access rule.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#user-level-firewall-access-rule-list-access-rules
|
||||||
|
func (api *API) UserAccessRule(accessRuleID string) (*AccessRuleResponse, error) {
|
||||||
|
return api.retrieveAccessRule("/user", accessRuleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserAccessRule updates a single access rule for the logged-in user &
|
||||||
|
// given access rule identifier.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#user-level-firewall-access-rule-update-access-rule
|
||||||
|
func (api *API) UpdateUserAccessRule(accessRuleID string, accessRule AccessRule) (*AccessRuleResponse, error) {
|
||||||
|
return api.updateAccessRule("/user", accessRuleID, accessRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUserAccessRule deletes a single access rule for the logged-in user and
|
||||||
|
// access rule identifiers.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#user-level-firewall-access-rule-update-access-rule
|
||||||
|
func (api *API) DeleteUserAccessRule(accessRuleID string) (*AccessRuleResponse, error) {
|
||||||
|
return api.deleteAccessRule("/user", accessRuleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListZoneAccessRules returns a slice of access rules for the given zone
|
||||||
|
// identifier.
|
||||||
|
//
|
||||||
|
// This takes an AccessRule to allow filtering of the results returned.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#firewall-access-rule-for-a-zone-list-access-rules
|
||||||
|
func (api *API) ListZoneAccessRules(zoneID string, accessRule AccessRule, page int) (*AccessRuleListResponse, error) {
|
||||||
|
return api.listAccessRules("/zones/"+zoneID, accessRule, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateZoneAccessRule creates a firewall access rule for the given zone
|
||||||
|
// identifier.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#firewall-access-rule-for-a-zone-create-access-rule
|
||||||
|
func (api *API) CreateZoneAccessRule(zoneID string, accessRule AccessRule) (*AccessRuleResponse, error) {
|
||||||
|
return api.createAccessRule("/zones/"+zoneID, accessRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneAccessRule returns the details of a zone's access rule.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#firewall-access-rule-for-a-zone-list-access-rules
|
||||||
|
func (api *API) ZoneAccessRule(zoneID string, accessRuleID string) (*AccessRuleResponse, error) {
|
||||||
|
return api.retrieveAccessRule("/zones/"+zoneID, accessRuleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateZoneAccessRule updates a single access rule for the given zone &
|
||||||
|
// access rule identifiers.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#firewall-access-rule-for-a-zone-update-access-rule
|
||||||
|
func (api *API) UpdateZoneAccessRule(zoneID, accessRuleID string, accessRule AccessRule) (*AccessRuleResponse, error) {
|
||||||
|
return api.updateAccessRule("/zones/"+zoneID, accessRuleID, accessRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteZoneAccessRule deletes a single access rule for the given zone and
|
||||||
|
// access rule identifiers.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#firewall-access-rule-for-a-zone-delete-access-rule
|
||||||
|
func (api *API) DeleteZoneAccessRule(zoneID, accessRuleID string) (*AccessRuleResponse, error) {
|
||||||
|
return api.deleteAccessRule("/zones/"+zoneID, accessRuleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAccountAccessRules returns a slice of access rules for the given
|
||||||
|
// account identifier.
|
||||||
|
//
|
||||||
|
// This takes an AccessRule to allow filtering of the results returned.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#account-level-firewall-access-rule-list-access-rules
|
||||||
|
func (api *API) ListAccountAccessRules(accountID string, accessRule AccessRule, page int) (*AccessRuleListResponse, error) {
|
||||||
|
return api.listAccessRules("/accounts/"+accountID, accessRule, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccountAccessRule creates a firewall access rule for the given
|
||||||
|
// account identifier.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#account-level-firewall-access-rule-create-access-rule
|
||||||
|
func (api *API) CreateAccountAccessRule(accountID string, accessRule AccessRule) (*AccessRuleResponse, error) {
|
||||||
|
return api.createAccessRule("/accounts/"+accountID, accessRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountAccessRule returns the details of an account's access rule.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#account-level-firewall-access-rule-access-rule-details
|
||||||
|
func (api *API) AccountAccessRule(accountID string, accessRuleID string) (*AccessRuleResponse, error) {
|
||||||
|
return api.retrieveAccessRule("/accounts/"+accountID, accessRuleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccountAccessRule updates a single access rule for the given
|
||||||
|
// account & access rule identifiers.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#account-level-firewall-access-rule-update-access-rule
|
||||||
|
func (api *API) UpdateAccountAccessRule(accountID, accessRuleID string, accessRule AccessRule) (*AccessRuleResponse, error) {
|
||||||
|
return api.updateAccessRule("/accounts/"+accountID, accessRuleID, accessRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAccountAccessRule deletes a single access rule for the given
|
||||||
|
// account and access rule identifiers.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#account-level-firewall-access-rule-delete-access-rule
|
||||||
|
func (api *API) DeleteAccountAccessRule(accountID, accessRuleID string) (*AccessRuleResponse, error) {
|
||||||
|
return api.deleteAccessRule("/accounts/"+accountID, accessRuleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) listAccessRules(prefix string, accessRule AccessRule, page int) (*AccessRuleListResponse, error) {
|
||||||
|
// Construct a query string
|
||||||
|
v := url.Values{}
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
v.Set("page", strconv.Itoa(page))
|
||||||
|
// Request as many rules as possible per page - API max is 100
|
||||||
|
v.Set("per_page", "100")
|
||||||
|
if accessRule.Notes != "" {
|
||||||
|
v.Set("notes", accessRule.Notes)
|
||||||
|
}
|
||||||
|
if accessRule.Mode != "" {
|
||||||
|
v.Set("mode", accessRule.Mode)
|
||||||
|
}
|
||||||
|
if accessRule.Scope.Type != "" {
|
||||||
|
v.Set("scope_type", accessRule.Scope.Type)
|
||||||
|
}
|
||||||
|
if accessRule.Configuration.Value != "" {
|
||||||
|
v.Set("configuration_value", accessRule.Configuration.Value)
|
||||||
|
}
|
||||||
|
if accessRule.Configuration.Target != "" {
|
||||||
|
v.Set("configuration_target", accessRule.Configuration.Target)
|
||||||
|
}
|
||||||
|
v.Set("page", strconv.Itoa(page))
|
||||||
|
query := "?" + v.Encode()
|
||||||
|
|
||||||
|
uri := prefix + "/firewall/access_rules/rules" + query
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &AccessRuleListResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) createAccessRule(prefix string, accessRule AccessRule) (*AccessRuleResponse, error) {
|
||||||
|
uri := prefix + "/firewall/access_rules/rules"
|
||||||
|
res, err := api.makeRequest("POST", uri, accessRule)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &AccessRuleResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) retrieveAccessRule(prefix, accessRuleID string) (*AccessRuleResponse, error) {
|
||||||
|
uri := prefix + "/firewall/access_rules/rules/" + accessRuleID
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &AccessRuleResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) updateAccessRule(prefix, accessRuleID string, accessRule AccessRule) (*AccessRuleResponse, error) {
|
||||||
|
uri := prefix + "/firewall/access_rules/rules/" + accessRuleID
|
||||||
|
res, err := api.makeRequest("PATCH", uri, accessRule)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &AccessRuleResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) deleteAccessRule(prefix, accessRuleID string) (*AccessRuleResponse, error) {
|
||||||
|
uri := prefix + "/firewall/access_rules/rules/" + accessRuleID
|
||||||
|
res, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &AccessRuleResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
196
vendor/github.com/cloudflare/cloudflare-go/firewall_rules.go
generated
vendored
Normal file
196
vendor/github.com/cloudflare/cloudflare-go/firewall_rules.go
generated
vendored
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FirewallRule is the struct of the firewall rule.
|
||||||
|
type FirewallRule struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Paused bool `json:"paused"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
Priority interface{} `json:"priority"`
|
||||||
|
Filter Filter `json:"filter"`
|
||||||
|
CreatedOn time.Time `json:"created_on,omitempty"`
|
||||||
|
ModifiedOn time.Time `json:"modified_on,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirewallRulesDetailResponse is the API response for the firewall
|
||||||
|
// rules.
|
||||||
|
type FirewallRulesDetailResponse struct {
|
||||||
|
Result []FirewallRule `json:"result"`
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirewallRuleResponse is the API response that is returned
|
||||||
|
// for requesting a single firewall rule on a zone.
|
||||||
|
type FirewallRuleResponse struct {
|
||||||
|
Result FirewallRule `json:"result"`
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirewallRules returns all firewall rules.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/get/#get-all-rules
|
||||||
|
func (api *API) FirewallRules(zoneID string, pageOpts PaginationOptions) ([]FirewallRule, error) {
|
||||||
|
uri := fmt.Sprintf("/zones/%s/firewall/rules", zoneID)
|
||||||
|
v := url.Values{}
|
||||||
|
|
||||||
|
if pageOpts.PerPage > 0 {
|
||||||
|
v.Set("per_page", strconv.Itoa(pageOpts.PerPage))
|
||||||
|
}
|
||||||
|
|
||||||
|
if pageOpts.Page > 0 {
|
||||||
|
v.Set("page", strconv.Itoa(pageOpts.Page))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v) > 0 {
|
||||||
|
uri = uri + "?" + v.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []FirewallRule{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var firewallDetailResponse FirewallRulesDetailResponse
|
||||||
|
err = json.Unmarshal(res, &firewallDetailResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []FirewallRule{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return firewallDetailResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirewallRule returns a single firewall rule based on the ID.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/get/#get-by-rule-id
|
||||||
|
func (api *API) FirewallRule(zoneID, firewallRuleID string) (FirewallRule, error) {
|
||||||
|
uri := fmt.Sprintf("/zones/%s/firewall/rules/%s", zoneID, firewallRuleID)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return FirewallRule{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var firewallRuleResponse FirewallRuleResponse
|
||||||
|
err = json.Unmarshal(res, &firewallRuleResponse)
|
||||||
|
if err != nil {
|
||||||
|
return FirewallRule{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return firewallRuleResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFirewallRules creates new firewall rules.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/post/
|
||||||
|
func (api *API) CreateFirewallRules(zoneID string, firewallRules []FirewallRule) ([]FirewallRule, error) {
|
||||||
|
uri := fmt.Sprintf("/zones/%s/firewall/rules", zoneID)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("POST", uri, firewallRules)
|
||||||
|
if err != nil {
|
||||||
|
return []FirewallRule{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var firewallRulesDetailResponse FirewallRulesDetailResponse
|
||||||
|
err = json.Unmarshal(res, &firewallRulesDetailResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []FirewallRule{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return firewallRulesDetailResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFirewallRule updates a single firewall rule.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/put/#update-a-single-rule
|
||||||
|
func (api *API) UpdateFirewallRule(zoneID string, firewallRule FirewallRule) (FirewallRule, error) {
|
||||||
|
if firewallRule.ID == "" {
|
||||||
|
return FirewallRule{}, errors.Errorf("firewall rule ID cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/zones/%s/firewall/rules/%s", zoneID, firewallRule.ID)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, firewallRule)
|
||||||
|
if err != nil {
|
||||||
|
return FirewallRule{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var firewallRuleResponse FirewallRuleResponse
|
||||||
|
err = json.Unmarshal(res, &firewallRuleResponse)
|
||||||
|
if err != nil {
|
||||||
|
return FirewallRule{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return firewallRuleResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFirewallRules updates a single firewall rule.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/put/#update-multiple-rules
|
||||||
|
func (api *API) UpdateFirewallRules(zoneID string, firewallRules []FirewallRule) ([]FirewallRule, error) {
|
||||||
|
for _, firewallRule := range firewallRules {
|
||||||
|
if firewallRule.ID == "" {
|
||||||
|
return []FirewallRule{}, errors.Errorf("firewall ID cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/zones/%s/firewall/rules", zoneID)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, firewallRules)
|
||||||
|
if err != nil {
|
||||||
|
return []FirewallRule{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var firewallRulesDetailResponse FirewallRulesDetailResponse
|
||||||
|
err = json.Unmarshal(res, &firewallRulesDetailResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []FirewallRule{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return firewallRulesDetailResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFirewallRule updates a single firewall rule.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/delete/#delete-a-single-rule
|
||||||
|
func (api *API) DeleteFirewallRule(zoneID, firewallRuleID string) error {
|
||||||
|
if firewallRuleID == "" {
|
||||||
|
return errors.Errorf("firewall rule ID cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/zones/%s/firewall/rules/%s", zoneID, firewallRuleID)
|
||||||
|
|
||||||
|
_, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFirewallRules updates a single firewall rule.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/delete/#delete-multiple-rules
|
||||||
|
func (api *API) DeleteFirewallRules(zoneID string, firewallRuleIDs []string) error {
|
||||||
|
ids := strings.Join(firewallRuleIDs, ",")
|
||||||
|
uri := fmt.Sprintf("/zones/%s/firewall/rules?id=%s", zoneID, ids)
|
||||||
|
|
||||||
|
_, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
13
vendor/github.com/cloudflare/cloudflare-go/go.mod
generated
vendored
Normal file
13
vendor/github.com/cloudflare/cloudflare-go/go.mod
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module github.com/cloudflare/cloudflare-go
|
||||||
|
|
||||||
|
go 1.11
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||||
|
github.com/olekukonko/tablewriter v0.0.1
|
||||||
|
github.com/pkg/errors v0.8.1
|
||||||
|
github.com/stretchr/testify v1.4.0
|
||||||
|
github.com/urfave/cli v1.22.1
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
|
||||||
|
)
|
26
vendor/github.com/cloudflare/cloudflare-go/go.sum
generated
vendored
Normal file
26
vendor/github.com/cloudflare/cloudflare-go/go.sum
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/urfave/cli v1.21.0 h1:wYSSj06510qPIzGSua9ZqsncMmWE3Zr55KBERygyrxE=
|
||||||
|
github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ=
|
||||||
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
44
vendor/github.com/cloudflare/cloudflare-go/ips.go
generated
vendored
Normal file
44
vendor/github.com/cloudflare/cloudflare-go/ips.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
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
|
||||||
|
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
|
||||||
|
}
|
52
vendor/github.com/cloudflare/cloudflare-go/keyless.go
generated
vendored
Normal file
52
vendor/github.com/cloudflare/cloudflare-go/keyless.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
func (api *API) DeleteKeyless() {
|
||||||
|
}
|
387
vendor/github.com/cloudflare/cloudflare-go/load_balancing.go
generated
vendored
Normal file
387
vendor/github.com/cloudflare/cloudflare-go/load_balancing.go
generated
vendored
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadBalancerPool represents a load balancer pool's properties.
|
||||||
|
type LoadBalancerPool struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
CreatedOn *time.Time `json:"created_on,omitempty"`
|
||||||
|
ModifiedOn *time.Time `json:"modified_on,omitempty"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
MinimumOrigins int `json:"minimum_origins,omitempty"`
|
||||||
|
Monitor string `json:"monitor,omitempty"`
|
||||||
|
Origins []LoadBalancerOrigin `json:"origins"`
|
||||||
|
NotificationEmail string `json:"notification_email,omitempty"`
|
||||||
|
|
||||||
|
// CheckRegions defines the geographic region(s) from where to run health-checks from - e.g. "WNAM", "WEU", "SAF", "SAM".
|
||||||
|
// Providing a null/empty value means "all regions", which may not be available to all plan types.
|
||||||
|
CheckRegions []string `json:"check_regions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadBalancerOrigin represents a Load Balancer origin's properties.
|
||||||
|
type LoadBalancerOrigin struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Weight float64 `json:"weight"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadBalancerMonitor represents a load balancer monitor's properties.
|
||||||
|
type LoadBalancerMonitor struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
CreatedOn *time.Time `json:"created_on,omitempty"`
|
||||||
|
ModifiedOn *time.Time `json:"modified_on,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Header map[string][]string `json:"header"`
|
||||||
|
Timeout int `json:"timeout"`
|
||||||
|
Retries int `json:"retries"`
|
||||||
|
Interval int `json:"interval"`
|
||||||
|
Port uint16 `json:"port,omitempty"`
|
||||||
|
ExpectedBody string `json:"expected_body"`
|
||||||
|
ExpectedCodes string `json:"expected_codes"`
|
||||||
|
FollowRedirects bool `json:"follow_redirects"`
|
||||||
|
AllowInsecure bool `json:"allow_insecure"`
|
||||||
|
ProbeZone string `json:"probe_zone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadBalancer represents a load balancer's properties.
|
||||||
|
type LoadBalancer struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
CreatedOn *time.Time `json:"created_on,omitempty"`
|
||||||
|
ModifiedOn *time.Time `json:"modified_on,omitempty"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
TTL int `json:"ttl,omitempty"`
|
||||||
|
FallbackPool string `json:"fallback_pool"`
|
||||||
|
DefaultPools []string `json:"default_pools"`
|
||||||
|
RegionPools map[string][]string `json:"region_pools"`
|
||||||
|
PopPools map[string][]string `json:"pop_pools"`
|
||||||
|
Proxied bool `json:"proxied"`
|
||||||
|
Enabled *bool `json:"enabled,omitempty"`
|
||||||
|
Persistence string `json:"session_affinity,omitempty"`
|
||||||
|
PersistenceTTL int `json:"session_affinity_ttl,omitempty"`
|
||||||
|
|
||||||
|
// SteeringPolicy controls pool selection logic.
|
||||||
|
// "off" select pools in DefaultPools order
|
||||||
|
// "geo" select pools based on RegionPools/PopPools
|
||||||
|
// "dynamic_latency" select pools based on RTT (requires health checks)
|
||||||
|
// "random" selects pools in a random order
|
||||||
|
// "" maps to "geo" if RegionPools or PopPools have entries otherwise "off"
|
||||||
|
SteeringPolicy string `json:"steering_policy,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadBalancerOriginHealth represents the health of the origin.
|
||||||
|
type LoadBalancerOriginHealth struct {
|
||||||
|
Healthy bool `json:"healthy,omitempty"`
|
||||||
|
RTT Duration `json:"rtt,omitempty"`
|
||||||
|
FailureReason string `json:"failure_reason,omitempty"`
|
||||||
|
ResponseCode int `json:"response_code,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadBalancerPoolPopHealth represents the health of the pool for given PoP.
|
||||||
|
type LoadBalancerPoolPopHealth struct {
|
||||||
|
Healthy bool `json:"healthy,omitempty"`
|
||||||
|
Origins []map[string]LoadBalancerOriginHealth `json:"origins,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadBalancerPoolHealth represents the healthchecks from different PoPs for a pool.
|
||||||
|
type LoadBalancerPoolHealth struct {
|
||||||
|
ID string `json:"pool_id,omitempty"`
|
||||||
|
PopHealth map[string]LoadBalancerPoolPopHealth `json:"pop_health,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBalancerPoolResponse represents the response from the load balancer pool endpoints.
|
||||||
|
type loadBalancerPoolResponse struct {
|
||||||
|
Response
|
||||||
|
Result LoadBalancerPool `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBalancerPoolListResponse represents the response from the List Pools endpoint.
|
||||||
|
type loadBalancerPoolListResponse struct {
|
||||||
|
Response
|
||||||
|
Result []LoadBalancerPool `json:"result"`
|
||||||
|
ResultInfo ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBalancerMonitorResponse represents the response from the load balancer monitor endpoints.
|
||||||
|
type loadBalancerMonitorResponse struct {
|
||||||
|
Response
|
||||||
|
Result LoadBalancerMonitor `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBalancerMonitorListResponse represents the response from the List Monitors endpoint.
|
||||||
|
type loadBalancerMonitorListResponse struct {
|
||||||
|
Response
|
||||||
|
Result []LoadBalancerMonitor `json:"result"`
|
||||||
|
ResultInfo ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBalancerResponse represents the response from the load balancer endpoints.
|
||||||
|
type loadBalancerResponse struct {
|
||||||
|
Response
|
||||||
|
Result LoadBalancer `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBalancerListResponse represents the response from the List Load Balancers endpoint.
|
||||||
|
type loadBalancerListResponse struct {
|
||||||
|
Response
|
||||||
|
Result []LoadBalancer `json:"result"`
|
||||||
|
ResultInfo ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBalancerPoolHealthResponse represents the response from the Pool Health Details endpoint.
|
||||||
|
type loadBalancerPoolHealthResponse struct {
|
||||||
|
Response
|
||||||
|
Result LoadBalancerPoolHealth `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLoadBalancerPool creates a new load balancer pool.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancer-pools-create-a-pool
|
||||||
|
func (api *API) CreateLoadBalancerPool(pool LoadBalancerPool) (LoadBalancerPool, error) {
|
||||||
|
uri := api.userBaseURL("/user") + "/load_balancers/pools"
|
||||||
|
res, err := api.makeRequest("POST", uri, pool)
|
||||||
|
if err != nil {
|
||||||
|
return LoadBalancerPool{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r loadBalancerPoolResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return LoadBalancerPool{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLoadBalancerPools lists load balancer pools connected to an account.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancer-pools-list-pools
|
||||||
|
func (api *API) ListLoadBalancerPools() ([]LoadBalancerPool, error) {
|
||||||
|
uri := api.userBaseURL("/user") + "/load_balancers/pools"
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r loadBalancerPoolListResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadBalancerPoolDetails returns the details for a load balancer pool.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancer-pools-pool-details
|
||||||
|
func (api *API) LoadBalancerPoolDetails(poolID string) (LoadBalancerPool, error) {
|
||||||
|
uri := api.userBaseURL("/user") + "/load_balancers/pools/" + poolID
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return LoadBalancerPool{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r loadBalancerPoolResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return LoadBalancerPool{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLoadBalancerPool disables and deletes a load balancer pool.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancer-pools-delete-a-pool
|
||||||
|
func (api *API) DeleteLoadBalancerPool(poolID string) error {
|
||||||
|
uri := api.userBaseURL("/user") + "/load_balancers/pools/" + poolID
|
||||||
|
if _, err := api.makeRequest("DELETE", uri, nil); err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModifyLoadBalancerPool modifies a configured load balancer pool.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancer-pools-modify-a-pool
|
||||||
|
func (api *API) ModifyLoadBalancerPool(pool LoadBalancerPool) (LoadBalancerPool, error) {
|
||||||
|
uri := api.userBaseURL("/user") + "/load_balancers/pools/" + pool.ID
|
||||||
|
res, err := api.makeRequest("PUT", uri, pool)
|
||||||
|
if err != nil {
|
||||||
|
return LoadBalancerPool{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r loadBalancerPoolResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return LoadBalancerPool{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLoadBalancerMonitor creates a new load balancer monitor.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancer-monitors-create-a-monitor
|
||||||
|
func (api *API) CreateLoadBalancerMonitor(monitor LoadBalancerMonitor) (LoadBalancerMonitor, error) {
|
||||||
|
uri := api.userBaseURL("/user") + "/load_balancers/monitors"
|
||||||
|
res, err := api.makeRequest("POST", uri, monitor)
|
||||||
|
if err != nil {
|
||||||
|
return LoadBalancerMonitor{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r loadBalancerMonitorResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return LoadBalancerMonitor{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLoadBalancerMonitors lists load balancer monitors connected to an account.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancer-monitors-list-monitors
|
||||||
|
func (api *API) ListLoadBalancerMonitors() ([]LoadBalancerMonitor, error) {
|
||||||
|
uri := api.userBaseURL("/user") + "/load_balancers/monitors"
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r loadBalancerMonitorListResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadBalancerMonitorDetails returns the details for a load balancer monitor.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancer-monitors-monitor-details
|
||||||
|
func (api *API) LoadBalancerMonitorDetails(monitorID string) (LoadBalancerMonitor, error) {
|
||||||
|
uri := api.userBaseURL("/user") + "/load_balancers/monitors/" + monitorID
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return LoadBalancerMonitor{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r loadBalancerMonitorResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return LoadBalancerMonitor{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLoadBalancerMonitor disables and deletes a load balancer monitor.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancer-monitors-delete-a-monitor
|
||||||
|
func (api *API) DeleteLoadBalancerMonitor(monitorID string) error {
|
||||||
|
uri := api.userBaseURL("/user") + "/load_balancers/monitors/" + monitorID
|
||||||
|
if _, err := api.makeRequest("DELETE", uri, nil); err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModifyLoadBalancerMonitor modifies a configured load balancer monitor.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancer-monitors-modify-a-monitor
|
||||||
|
func (api *API) ModifyLoadBalancerMonitor(monitor LoadBalancerMonitor) (LoadBalancerMonitor, error) {
|
||||||
|
uri := api.userBaseURL("/user") + "/load_balancers/monitors/" + monitor.ID
|
||||||
|
res, err := api.makeRequest("PUT", uri, monitor)
|
||||||
|
if err != nil {
|
||||||
|
return LoadBalancerMonitor{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r loadBalancerMonitorResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return LoadBalancerMonitor{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLoadBalancer creates a new load balancer.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancers-create-a-load-balancer
|
||||||
|
func (api *API) CreateLoadBalancer(zoneID string, lb LoadBalancer) (LoadBalancer, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/load_balancers"
|
||||||
|
res, err := api.makeRequest("POST", uri, lb)
|
||||||
|
if err != nil {
|
||||||
|
return LoadBalancer{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r loadBalancerResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return LoadBalancer{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLoadBalancers lists load balancers configured on a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancers-list-load-balancers
|
||||||
|
func (api *API) ListLoadBalancers(zoneID string) ([]LoadBalancer, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/load_balancers"
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r loadBalancerListResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadBalancerDetails returns the details for a load balancer.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancers-load-balancer-details
|
||||||
|
func (api *API) LoadBalancerDetails(zoneID, lbID string) (LoadBalancer, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/load_balancers/" + lbID
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return LoadBalancer{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r loadBalancerResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return LoadBalancer{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLoadBalancer disables and deletes a load balancer.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancers-delete-a-load-balancer
|
||||||
|
func (api *API) DeleteLoadBalancer(zoneID, lbID string) error {
|
||||||
|
uri := "/zones/" + zoneID + "/load_balancers/" + lbID
|
||||||
|
if _, err := api.makeRequest("DELETE", uri, nil); err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModifyLoadBalancer modifies a configured load balancer.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancers-modify-a-load-balancer
|
||||||
|
func (api *API) ModifyLoadBalancer(zoneID string, lb LoadBalancer) (LoadBalancer, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/load_balancers/" + lb.ID
|
||||||
|
res, err := api.makeRequest("PUT", uri, lb)
|
||||||
|
if err != nil {
|
||||||
|
return LoadBalancer{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r loadBalancerResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return LoadBalancer{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoolHealthDetails fetches the latest healtcheck details for a single pool.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#load-balancer-pools-pool-health-details
|
||||||
|
func (api *API) PoolHealthDetails(poolID string) (LoadBalancerPoolHealth, error) {
|
||||||
|
uri := api.userBaseURL("/user") + "/load_balancers/pools/" + poolID + "/health"
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return LoadBalancerPoolHealth{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r loadBalancerPoolHealthResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return LoadBalancerPoolHealth{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
151
vendor/github.com/cloudflare/cloudflare-go/lockdown.go
generated
vendored
Normal file
151
vendor/github.com/cloudflare/cloudflare-go/lockdown.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ZoneLockdown represents a Zone Lockdown rule. A rule only permits access to
|
||||||
|
// the provided URL pattern(s) from the given IP address(es) or subnet(s).
|
||||||
|
type ZoneLockdown struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
URLs []string `json:"urls"`
|
||||||
|
Configurations []ZoneLockdownConfig `json:"configurations"`
|
||||||
|
Paused bool `json:"paused"`
|
||||||
|
Priority int `json:"priority,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneLockdownConfig represents a Zone Lockdown config, which comprises
|
||||||
|
// a Target ("ip" or "ip_range") and a Value (an IP address or IP+mask,
|
||||||
|
// respectively.)
|
||||||
|
type ZoneLockdownConfig struct {
|
||||||
|
Target string `json:"target"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneLockdownResponse represents a response from the Zone Lockdown endpoint.
|
||||||
|
type ZoneLockdownResponse struct {
|
||||||
|
Result ZoneLockdown `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneLockdownListResponse represents a response from the List Zone Lockdown
|
||||||
|
// endpoint.
|
||||||
|
type ZoneLockdownListResponse struct {
|
||||||
|
Result []ZoneLockdown `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateZoneLockdown creates a Zone ZoneLockdown rule for the given zone ID.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-create-a-ZoneLockdown-rule
|
||||||
|
func (api *API) CreateZoneLockdown(zoneID string, ld ZoneLockdown) (*ZoneLockdownResponse, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/lockdowns"
|
||||||
|
res, err := api.makeRequest("POST", uri, ld)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &ZoneLockdownResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateZoneLockdown updates a Zone ZoneLockdown rule (based on the ID) for the
|
||||||
|
// given zone ID.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-update-ZoneLockdown-rule
|
||||||
|
func (api *API) UpdateZoneLockdown(zoneID string, id string, ld ZoneLockdown) (*ZoneLockdownResponse, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/lockdowns/" + id
|
||||||
|
res, err := api.makeRequest("PUT", uri, ld)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &ZoneLockdownResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteZoneLockdown deletes a Zone ZoneLockdown rule (based on the ID) for the
|
||||||
|
// given zone ID.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-delete-ZoneLockdown-rule
|
||||||
|
func (api *API) DeleteZoneLockdown(zoneID string, id string) (*ZoneLockdownResponse, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/lockdowns/" + id
|
||||||
|
res, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &ZoneLockdownResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneLockdown retrieves a Zone ZoneLockdown rule (based on the ID) for the
|
||||||
|
// given zone ID.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-ZoneLockdown-rule-details
|
||||||
|
func (api *API) ZoneLockdown(zoneID string, id string) (*ZoneLockdownResponse, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/lockdowns/" + id
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &ZoneLockdownResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListZoneLockdowns retrieves a list of Zone ZoneLockdown rules for a given
|
||||||
|
// zone ID by page number.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-list-ZoneLockdown-rules
|
||||||
|
func (api *API) ListZoneLockdowns(zoneID string, page int) (*ZoneLockdownListResponse, error) {
|
||||||
|
v := url.Values{}
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Set("page", strconv.Itoa(page))
|
||||||
|
v.Set("per_page", strconv.Itoa(100))
|
||||||
|
query := "?" + v.Encode()
|
||||||
|
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/lockdowns" + query
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &ZoneLockdownListResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
224
vendor/github.com/cloudflare/cloudflare-go/logpush.go
generated
vendored
Normal file
224
vendor/github.com/cloudflare/cloudflare-go/logpush.go
generated
vendored
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogpushJob describes a Logpush job.
|
||||||
|
type LogpushJob struct {
|
||||||
|
ID int `json:"id,omitempty"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
LogpullOptions string `json:"logpull_options"`
|
||||||
|
DestinationConf string `json:"destination_conf"`
|
||||||
|
OwnershipChallenge string `json:"ownership_challenge,omitempty"`
|
||||||
|
LastComplete *time.Time `json:"last_complete,omitempty"`
|
||||||
|
LastError *time.Time `json:"last_error,omitempty"`
|
||||||
|
ErrorMessage string `json:"error_message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogpushJobsResponse is the API response, containing an array of Logpush Jobs.
|
||||||
|
type LogpushJobsResponse struct {
|
||||||
|
Response
|
||||||
|
Result []LogpushJob `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogpushJobDetailsResponse is the API response, containing a single Logpush Job.
|
||||||
|
type LogpushJobDetailsResponse struct {
|
||||||
|
Response
|
||||||
|
Result LogpushJob `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogpushGetOwnershipChallenge describes a ownership validation.
|
||||||
|
type LogpushGetOwnershipChallenge struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Valid bool `json:"valid"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogpushGetOwnershipChallengeResponse is the API response, containing a ownership challenge.
|
||||||
|
type LogpushGetOwnershipChallengeResponse struct {
|
||||||
|
Response
|
||||||
|
Result LogpushGetOwnershipChallenge `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogpushGetOwnershipChallengeRequest is the API request for get ownership challenge.
|
||||||
|
type LogpushGetOwnershipChallengeRequest struct {
|
||||||
|
DestinationConf string `json:"destination_conf"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogpushOwnershipChallangeValidationResponse is the API response,
|
||||||
|
// containing a ownership challenge validation result.
|
||||||
|
type LogpushOwnershipChallangeValidationResponse struct {
|
||||||
|
Response
|
||||||
|
Result struct {
|
||||||
|
Valid bool `json:"valid"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogpushValidateOwnershipChallengeRequest is the API request for validate ownership challenge.
|
||||||
|
type LogpushValidateOwnershipChallengeRequest struct {
|
||||||
|
DestinationConf string `json:"destination_conf"`
|
||||||
|
OwnershipChallenge string `json:"ownership_challenge"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogpushDestinationExistsResponse is the API response,
|
||||||
|
// containing a destination exists check result.
|
||||||
|
type LogpushDestinationExistsResponse struct {
|
||||||
|
Response
|
||||||
|
Result struct {
|
||||||
|
Exists bool `json:"exists"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogpushDestinationExistsRequest is the API request for check destination exists.
|
||||||
|
type LogpushDestinationExistsRequest struct {
|
||||||
|
DestinationConf string `json:"destination_conf"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLogpushJob creates a new LogpushJob for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#logpush-jobs-create-logpush-job
|
||||||
|
func (api *API) CreateLogpushJob(zoneID string, job LogpushJob) (*LogpushJob, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/logpush/jobs"
|
||||||
|
res, err := api.makeRequest("POST", uri, job)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r LogpushJobDetailsResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return &r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogpushJobs returns all Logpush Jobs for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#logpush-jobs-list-logpush-jobs
|
||||||
|
func (api *API) LogpushJobs(zoneID string) ([]LogpushJob, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/logpush/jobs"
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []LogpushJob{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r LogpushJobsResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return []LogpushJob{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogpushJob fetches detail about one Logpush Job for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#logpush-jobs-logpush-job-details
|
||||||
|
func (api *API) LogpushJob(zoneID string, jobID int) (LogpushJob, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/logpush/jobs/" + strconv.Itoa(jobID)
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return LogpushJob{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r LogpushJobDetailsResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return LogpushJob{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLogpushJob lets you update a Logpush Job.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#logpush-jobs-update-logpush-job
|
||||||
|
func (api *API) UpdateLogpushJob(zoneID string, jobID int, job LogpushJob) error {
|
||||||
|
uri := "/zones/" + zoneID + "/logpush/jobs/" + strconv.Itoa(jobID)
|
||||||
|
res, err := api.makeRequest("PUT", uri, job)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r LogpushJobDetailsResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLogpushJob deletes a Logpush Job for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#logpush-jobs-delete-logpush-job
|
||||||
|
func (api *API) DeleteLogpushJob(zoneID string, jobID int) error {
|
||||||
|
uri := "/zones/" + zoneID + "/logpush/jobs/" + strconv.Itoa(jobID)
|
||||||
|
res, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r LogpushJobDetailsResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogpushOwnershipChallenge returns ownership challenge.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#logpush-jobs-get-ownership-challenge
|
||||||
|
func (api *API) GetLogpushOwnershipChallenge(zoneID, destinationConf string) (*LogpushGetOwnershipChallenge, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/logpush/ownership"
|
||||||
|
res, err := api.makeRequest("POST", uri, LogpushGetOwnershipChallengeRequest{
|
||||||
|
DestinationConf: destinationConf,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r LogpushGetOwnershipChallengeResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return &r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateLogpushOwnershipChallenge returns ownership challenge validation result.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#logpush-jobs-validate-ownership-challenge
|
||||||
|
func (api *API) ValidateLogpushOwnershipChallenge(zoneID, destinationConf, ownershipChallenge string) (bool, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/logpush/ownership/validate"
|
||||||
|
res, err := api.makeRequest("POST", uri, LogpushValidateOwnershipChallengeRequest{
|
||||||
|
DestinationConf: destinationConf,
|
||||||
|
OwnershipChallenge: ownershipChallenge,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r LogpushGetOwnershipChallengeResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result.Valid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckLogpushDestinationExists returns destination exists check result.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#logpush-jobs-check-destination-exists
|
||||||
|
func (api *API) CheckLogpushDestinationExists(zoneID, destinationConf string) (bool, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/logpush/validate/destination/exists"
|
||||||
|
res, err := api.makeRequest("POST", uri, LogpushDestinationExistsRequest{
|
||||||
|
DestinationConf: destinationConf,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r LogpushDestinationExistsResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result.Exists, nil
|
||||||
|
}
|
101
vendor/github.com/cloudflare/cloudflare-go/options.go
generated
vendored
Normal file
101
vendor/github.com/cloudflare/cloudflare-go/options.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsingAccount allows you to apply account-level changes (Load Balancing,
|
||||||
|
// Railguns) to an account instead.
|
||||||
|
func UsingAccount(accountID string) Option {
|
||||||
|
return func(api *API) error {
|
||||||
|
api.AccountID = accountID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsingRateLimit applies a non-default rate limit to client API requests
|
||||||
|
// If not specified the default of 4rps will be applied
|
||||||
|
func UsingRateLimit(rps float64) Option {
|
||||||
|
return func(api *API) error {
|
||||||
|
// because ratelimiter doesnt do any windowing
|
||||||
|
// setting burst makes it difficult to enforce a fixed rate
|
||||||
|
// so setting it equal to 1 this effectively disables bursting
|
||||||
|
// this doesn't check for sensible values, ultimately the api will enforce that the value is ok
|
||||||
|
api.rateLimiter = rate.NewLimiter(rate.Limit(rps), 1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsingRetryPolicy applies a non-default number of retries and min/max retry delays
|
||||||
|
// This will be used when the client exponentially backs off after errored requests
|
||||||
|
func UsingRetryPolicy(maxRetries int, minRetryDelaySecs int, maxRetryDelaySecs int) Option {
|
||||||
|
// seconds is very granular for a minimum delay - but this is only in case of failure
|
||||||
|
return func(api *API) error {
|
||||||
|
api.retryPolicy = RetryPolicy{
|
||||||
|
MaxRetries: maxRetries,
|
||||||
|
MinRetryDelay: time.Duration(minRetryDelaySecs) * time.Second,
|
||||||
|
MaxRetryDelay: time.Duration(maxRetryDelaySecs) * time.Second,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsingLogger can be set if you want to get log output from this API instance
|
||||||
|
// By default no log output is emitted
|
||||||
|
func UsingLogger(logger Logger) Option {
|
||||||
|
return func(api *API) error {
|
||||||
|
api.logger = logger
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAgent can be set if you want to send a software name and version for HTTP access logs.
|
||||||
|
// It is recommended to set it in order to help future Customer Support diagnostics
|
||||||
|
// and prevent collateral damage by sharing generic User-Agent string with abusive users.
|
||||||
|
// E.g. "my-software/1.2.3". By default generic Go User-Agent is used.
|
||||||
|
func UserAgent(userAgent string) Option {
|
||||||
|
return func(api *API) error {
|
||||||
|
api.UserAgent = userAgent
|
||||||
|
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
|
||||||
|
}
|
169
vendor/github.com/cloudflare/cloudflare-go/origin_ca.go
generated
vendored
Normal file
169
vendor/github.com/cloudflare/cloudflare-go/origin_ca.go
generated
vendored
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OriginCACertificate represents a Cloudflare-issued certificate.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#cloudflare-ca
|
||||||
|
type OriginCACertificate struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Certificate string `json:"certificate"`
|
||||||
|
Hostnames []string `json:"hostnames"`
|
||||||
|
ExpiresOn time.Time `json:"expires_on"`
|
||||||
|
RequestType string `json:"request_type"`
|
||||||
|
RequestValidity int `json:"requested_validity"`
|
||||||
|
CSR string `json:"csr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OriginCACertificateListOptions represents the parameters used to list Cloudflare-issued certificates.
|
||||||
|
type OriginCACertificateListOptions struct {
|
||||||
|
ZoneID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// OriginCACertificateID represents the ID of the revoked certificate from the Revoke Certificate endpoint.
|
||||||
|
type OriginCACertificateID struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// originCACertificateResponse represents the response from the Create Certificate and the Certificate Details endpoints.
|
||||||
|
type originCACertificateResponse struct {
|
||||||
|
Response
|
||||||
|
Result OriginCACertificate `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// originCACertificateResponseList represents the response from the List Certificates endpoint.
|
||||||
|
type originCACertificateResponseList struct {
|
||||||
|
Response
|
||||||
|
Result []OriginCACertificate `json:"result"`
|
||||||
|
ResultInfo ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// originCACertificateResponseRevoke represents the response from the Revoke Certificate endpoint.
|
||||||
|
type originCACertificateResponseRevoke struct {
|
||||||
|
Response
|
||||||
|
Result OriginCACertificateID `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOriginCertificate creates a Cloudflare-signed certificate.
|
||||||
|
//
|
||||||
|
// This function requires api.APIUserServiceKey be set to your Certificates API key.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#cloudflare-ca-create-certificate
|
||||||
|
func (api *API) CreateOriginCertificate(certificate OriginCACertificate) (*OriginCACertificate, error) {
|
||||||
|
uri := "/certificates"
|
||||||
|
res, err := api.makeRequestWithAuthType(context.TODO(), "POST", uri, certificate, AuthUserService)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var originResponse *originCACertificateResponse
|
||||||
|
|
||||||
|
err = json.Unmarshal(res, &originResponse)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !originResponse.Success {
|
||||||
|
return nil, errors.New(errRequestNotSuccessful)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &originResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OriginCertificates lists all Cloudflare-issued certificates.
|
||||||
|
//
|
||||||
|
// This function requires api.APIUserServiceKey be set to your Certificates API key.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#cloudflare-ca-list-certificates
|
||||||
|
func (api *API) OriginCertificates(options OriginCACertificateListOptions) ([]OriginCACertificate, error) {
|
||||||
|
v := url.Values{}
|
||||||
|
if options.ZoneID != "" {
|
||||||
|
v.Set("zone_id", options.ZoneID)
|
||||||
|
}
|
||||||
|
uri := "/certificates" + "?" + v.Encode()
|
||||||
|
res, err := api.makeRequestWithAuthType(context.TODO(), "GET", uri, nil, AuthUserService)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var originResponse *originCACertificateResponseList
|
||||||
|
|
||||||
|
err = json.Unmarshal(res, &originResponse)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !originResponse.Success {
|
||||||
|
return nil, errors.New(errRequestNotSuccessful)
|
||||||
|
}
|
||||||
|
|
||||||
|
return originResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OriginCertificate returns the details for a Cloudflare-issued certificate.
|
||||||
|
//
|
||||||
|
// This function requires api.APIUserServiceKey be set to your Certificates API key.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#cloudflare-ca-certificate-details
|
||||||
|
func (api *API) OriginCertificate(certificateID string) (*OriginCACertificate, error) {
|
||||||
|
uri := "/certificates/" + certificateID
|
||||||
|
res, err := api.makeRequestWithAuthType(context.TODO(), "GET", uri, nil, AuthUserService)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var originResponse *originCACertificateResponse
|
||||||
|
|
||||||
|
err = json.Unmarshal(res, &originResponse)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !originResponse.Success {
|
||||||
|
return nil, errors.New(errRequestNotSuccessful)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &originResponse.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeOriginCertificate revokes a created certificate for a zone.
|
||||||
|
//
|
||||||
|
// This function requires api.APIUserServiceKey be set to your Certificates API key.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#cloudflare-ca-revoke-certificate
|
||||||
|
func (api *API) RevokeOriginCertificate(certificateID string) (*OriginCACertificateID, error) {
|
||||||
|
uri := "/certificates/" + certificateID
|
||||||
|
res, err := api.makeRequestWithAuthType(context.TODO(), "DELETE", uri, nil, AuthUserService)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var originResponse *originCACertificateResponseRevoke
|
||||||
|
|
||||||
|
err = json.Unmarshal(res, &originResponse)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !originResponse.Success {
|
||||||
|
return nil, errors.New(errRequestNotSuccessful)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &originResponse.Result, nil
|
||||||
|
|
||||||
|
}
|
235
vendor/github.com/cloudflare/cloudflare-go/page_rules.go
generated
vendored
Normal file
235
vendor/github.com/cloudflare/cloudflare-go/page_rules.go
generated
vendored
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
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
|
||||||
|
automatic_https_rewrites
|
||||||
|
browser_cache_ttl
|
||||||
|
browser_check
|
||||||
|
bypass_cache_on_cookie
|
||||||
|
cache_by_device_type
|
||||||
|
cache_deception_armor
|
||||||
|
cache_level
|
||||||
|
cache_on_cookie
|
||||||
|
disable_apps
|
||||||
|
disable_performance
|
||||||
|
disable_railgun
|
||||||
|
disable_security
|
||||||
|
edge_cache_ttl
|
||||||
|
email_obfuscation
|
||||||
|
explicit_cache_control
|
||||||
|
forwarding_url
|
||||||
|
host_header_override
|
||||||
|
ip_geolocation
|
||||||
|
minify
|
||||||
|
mirage
|
||||||
|
opportunistic_encryption
|
||||||
|
origin_error_page_pass_thru
|
||||||
|
polish
|
||||||
|
resolve_override
|
||||||
|
respect_strong_etag
|
||||||
|
response_buffering
|
||||||
|
rocket_loader
|
||||||
|
security_level
|
||||||
|
server_side_exclude
|
||||||
|
sort_query_string_for_cache
|
||||||
|
ssl
|
||||||
|
true_client_ip_header
|
||||||
|
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{}
|
||||||
|
"automatic_https_rewrites": "Automatic HTTPS Rewrites", // Value of type string
|
||||||
|
"browser_cache_ttl": "Browser Cache TTL", // Value of type int
|
||||||
|
"browser_check": "Browser Integrity Check", // Value of type string
|
||||||
|
"bypass_cache_on_cookie": "Bypass Cache on Cookie", // Value of type string
|
||||||
|
"cache_by_device_type": "Cache By Device Type", // Value of type string
|
||||||
|
"cache_deception_armor": "Cache Deception Armor", // Value of type string
|
||||||
|
"cache_level": "Cache Level", // Value of type string
|
||||||
|
"cache_on_cookie": "Cache On Cookie", // 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
|
||||||
|
"explicit_cache_control": "Origin Cache Control", // Value of type string
|
||||||
|
"forwarding_url": "Forwarding URL", // Value of type map[string]interface
|
||||||
|
"host_header_override": "Host Header Override", // Value of type string
|
||||||
|
"ip_geolocation": "IP Geolocation Header", // Value of type string
|
||||||
|
"minify": "Minify", // Value of type map[string]interface
|
||||||
|
"mirage": "Mirage", // Value of type string
|
||||||
|
"opportunistic_encryption": "Opportunistic Encryption", // Value of type string
|
||||||
|
"origin_error_page_pass_thru": "Origin Error Page Pass-thru", // Value of type string
|
||||||
|
"polish": "Polish", // Value of type string
|
||||||
|
"resolve_override": "Resolve Override", // Value of type string
|
||||||
|
"respect_strong_etag": "Respect Strong ETags", // Value of type string
|
||||||
|
"response_buffering": "Response Buffering", // 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
|
||||||
|
"sort_query_string_for_cache": "Query String Sort", // Value of type string
|
||||||
|
"ssl": "SSL", // Value of type string
|
||||||
|
"true_client_ip_header": "True Client IP Header", // 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
|
||||||
|
func (api *API) CreatePageRule(zoneID string, rule PageRule) (*PageRule, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/pagerules"
|
||||||
|
res, err := api.makeRequest("POST", uri, rule)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r PageRuleDetailResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return &r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPageRules returns all Page Rules for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#page-rules-for-a-zone-list-page-rules
|
||||||
|
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
|
||||||
|
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 you 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
|
||||||
|
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
|
||||||
|
func (api *API) UpdatePageRule(zoneID, ruleID string, rule PageRule) error {
|
||||||
|
uri := "/zones/" + zoneID + "/pagerules/" + ruleID
|
||||||
|
res, err := api.makeRequest("PUT", 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePageRule deletes a Page Rule for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#page-rules-for-a-zone-delete-a-page-rule
|
||||||
|
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
|
||||||
|
}
|
297
vendor/github.com/cloudflare/cloudflare-go/railgun.go
generated
vendored
Normal file
297
vendor/github.com/cloudflare/cloudflare-go/railgun.go
generated
vendored
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
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
|
||||||
|
func (api *API) CreateRailgun(name string) (Railgun, error) {
|
||||||
|
uri := api.userBaseURL("") + "/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
|
||||||
|
func (api *API) ListRailguns(options RailgunListOptions) ([]Railgun, error) {
|
||||||
|
v := url.Values{}
|
||||||
|
if options.Direction != "" {
|
||||||
|
v.Set("direction", options.Direction)
|
||||||
|
}
|
||||||
|
uri := api.userBaseURL("") + "/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
|
||||||
|
func (api *API) RailgunDetails(railgunID string) (Railgun, error) {
|
||||||
|
uri := api.userBaseURL("") + "/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
|
||||||
|
func (api *API) RailgunZones(railgunID string) ([]Zone, error) {
|
||||||
|
uri := api.userBaseURL("") + "/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
|
||||||
|
func (api *API) enableRailgun(railgunID string, enable bool) (Railgun, error) {
|
||||||
|
uri := api.userBaseURL("") + "/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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
func (api *API) DeleteRailgun(railgunID string) error {
|
||||||
|
uri := api.userBaseURL("") + "/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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
func (api *API) DisconnectZoneRailgun(zoneID, railgunID string) (ZoneRailgun, error) {
|
||||||
|
return api.connectZoneRailgun(zoneID, railgunID, false)
|
||||||
|
}
|
210
vendor/github.com/cloudflare/cloudflare-go/rate_limiting.go
generated
vendored
Normal file
210
vendor/github.com/cloudflare/cloudflare-go/rate_limiting.go
generated
vendored
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RateLimit is a policy than can be applied to limit traffic within a customer domain
|
||||||
|
type RateLimit struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Disabled bool `json:"disabled,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Match RateLimitTrafficMatcher `json:"match"`
|
||||||
|
Bypass []RateLimitKeyValue `json:"bypass,omitempty"`
|
||||||
|
Threshold int `json:"threshold"`
|
||||||
|
Period int `json:"period"`
|
||||||
|
Action RateLimitAction `json:"action"`
|
||||||
|
Correlate *RateLimitCorrelate `json:"correlate,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimitTrafficMatcher contains the rules that will be used to apply a rate limit to traffic
|
||||||
|
type RateLimitTrafficMatcher struct {
|
||||||
|
Request RateLimitRequestMatcher `json:"request"`
|
||||||
|
Response RateLimitResponseMatcher `json:"response"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimitRequestMatcher contains the matching rules pertaining to requests
|
||||||
|
type RateLimitRequestMatcher struct {
|
||||||
|
Methods []string `json:"methods,omitempty"`
|
||||||
|
Schemes []string `json:"schemes,omitempty"`
|
||||||
|
URLPattern string `json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimitResponseMatcher contains the matching rules pertaining to responses
|
||||||
|
type RateLimitResponseMatcher struct {
|
||||||
|
Statuses []int `json:"status,omitempty"`
|
||||||
|
OriginTraffic *bool `json:"origin_traffic,omitempty"` // api defaults to true so we need an explicit empty value
|
||||||
|
Headers []RateLimitResponseMatcherHeader `json:"headers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimitResponseMatcherHeader contains the structure of the origin
|
||||||
|
// HTTP headers used in request matcher checks.
|
||||||
|
type RateLimitResponseMatcherHeader struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Op string `json:"op"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimitKeyValue is k-v formatted as expected in the rate limit description
|
||||||
|
type RateLimitKeyValue struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimitAction is the action that will be taken when the rate limit threshold is reached
|
||||||
|
type RateLimitAction struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Timeout int `json:"timeout"`
|
||||||
|
Response *RateLimitActionResponse `json:"response"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimitActionResponse is the response that will be returned when rate limit action is triggered
|
||||||
|
type RateLimitActionResponse struct {
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimitCorrelate pertainings to NAT support
|
||||||
|
type RateLimitCorrelate struct {
|
||||||
|
By string `json:"by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type rateLimitResponse struct {
|
||||||
|
Response
|
||||||
|
Result RateLimit `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type rateLimitListResponse struct {
|
||||||
|
Response
|
||||||
|
Result []RateLimit `json:"result"`
|
||||||
|
ResultInfo ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRateLimit creates a new rate limit for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#rate-limits-for-a-zone-create-a-ratelimit
|
||||||
|
func (api *API) CreateRateLimit(zoneID string, limit RateLimit) (RateLimit, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/rate_limits"
|
||||||
|
res, err := api.makeRequest("POST", uri, limit)
|
||||||
|
if err != nil {
|
||||||
|
return RateLimit{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r rateLimitResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return RateLimit{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRateLimits returns Rate Limits for a zone, paginated according to the provided options
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#rate-limits-for-a-zone-list-rate-limits
|
||||||
|
func (api *API) ListRateLimits(zoneID string, pageOpts PaginationOptions) ([]RateLimit, ResultInfo, error) {
|
||||||
|
v := url.Values{}
|
||||||
|
if pageOpts.PerPage > 0 {
|
||||||
|
v.Set("per_page", strconv.Itoa(pageOpts.PerPage))
|
||||||
|
}
|
||||||
|
if pageOpts.Page > 0 {
|
||||||
|
v.Set("page", strconv.Itoa(pageOpts.Page))
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := "/zones/" + zoneID + "/rate_limits"
|
||||||
|
if len(v) > 0 {
|
||||||
|
uri = uri + "?" + v.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []RateLimit{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r rateLimitListResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return []RateLimit{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, r.ResultInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAllRateLimits returns all Rate Limits for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#rate-limits-for-a-zone-list-rate-limits
|
||||||
|
func (api *API) ListAllRateLimits(zoneID string) ([]RateLimit, error) {
|
||||||
|
pageOpts := PaginationOptions{
|
||||||
|
PerPage: 100, // this is the max page size allowed
|
||||||
|
Page: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
allRateLimits := make([]RateLimit, 0)
|
||||||
|
for {
|
||||||
|
rateLimits, resultInfo, err := api.ListRateLimits(zoneID, pageOpts)
|
||||||
|
if err != nil {
|
||||||
|
return []RateLimit{}, err
|
||||||
|
}
|
||||||
|
allRateLimits = append(allRateLimits, rateLimits...)
|
||||||
|
// total pages is not returned on this call
|
||||||
|
// if number of records is less than the max, this must be the last page
|
||||||
|
// in case TotalCount % PerPage = 0, the last request will return an empty list
|
||||||
|
if resultInfo.Count < resultInfo.PerPage {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// continue with the next page
|
||||||
|
pageOpts.Page = pageOpts.Page + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return allRateLimits, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimit fetches detail about one Rate Limit for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#rate-limits-for-a-zone-rate-limit-details
|
||||||
|
func (api *API) RateLimit(zoneID, limitID string) (RateLimit, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/rate_limits/" + limitID
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return RateLimit{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r rateLimitResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return RateLimit{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRateLimit lets you replace a Rate Limit for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#rate-limits-for-a-zone-update-rate-limit
|
||||||
|
func (api *API) UpdateRateLimit(zoneID, limitID string, limit RateLimit) (RateLimit, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/rate_limits/" + limitID
|
||||||
|
res, err := api.makeRequest("PUT", uri, limit)
|
||||||
|
if err != nil {
|
||||||
|
return RateLimit{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r rateLimitResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return RateLimit{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRateLimit deletes a Rate Limit for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#rate-limits-for-a-zone-delete-rate-limit
|
||||||
|
func (api *API) DeleteRateLimit(zoneID, limitID string) error {
|
||||||
|
uri := "/zones/" + zoneID + "/rate_limits/" + limitID
|
||||||
|
res, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r rateLimitResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
175
vendor/github.com/cloudflare/cloudflare-go/registrar.go
generated
vendored
Normal file
175
vendor/github.com/cloudflare/cloudflare-go/registrar.go
generated
vendored
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegistrarDomain is the structure of the API response for a new
|
||||||
|
// Cloudflare Registrar domain.
|
||||||
|
type RegistrarDomain struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Available bool `json:"available"`
|
||||||
|
SupportedTLD bool `json:"supported_tld"`
|
||||||
|
CanRegister bool `json:"can_register"`
|
||||||
|
TransferIn RegistrarTransferIn `json:"transfer_in"`
|
||||||
|
CurrentRegistrar string `json:"current_registrar"`
|
||||||
|
ExpiresAt time.Time `json:"expires_at"`
|
||||||
|
RegistryStatuses string `json:"registry_statuses"`
|
||||||
|
Locked bool `json:"locked"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
RegistrantContact RegistrantContact `json:"registrant_contact"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistrarTransferIn contains the structure for a domain transfer in
|
||||||
|
// request.
|
||||||
|
type RegistrarTransferIn struct {
|
||||||
|
UnlockDomain string `json:"unlock_domain"`
|
||||||
|
DisablePrivacy string `json:"disable_privacy"`
|
||||||
|
EnterAuthCode string `json:"enter_auth_code"`
|
||||||
|
ApproveTransfer string `json:"approve_transfer"`
|
||||||
|
AcceptFoa string `json:"accept_foa"`
|
||||||
|
CanCancelTransfer bool `json:"can_cancel_transfer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistrantContact is the contact details for the domain registration.
|
||||||
|
type RegistrantContact struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
Organization string `json:"organization"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Address2 string `json:"address2"`
|
||||||
|
City string `json:"city"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Zip string `json:"zip"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Fax string `json:"fax"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistrarDomainConfiguration is the structure for making updates to
|
||||||
|
// and existing domain.
|
||||||
|
type RegistrarDomainConfiguration struct {
|
||||||
|
NameServers []string `json:"name_servers"`
|
||||||
|
Privacy bool `json:"privacy"`
|
||||||
|
Locked bool `json:"locked"`
|
||||||
|
AutoRenew bool `json:"auto_renew"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistrarDomainDetailResponse is the structure of the detailed
|
||||||
|
// response from the API for a single domain.
|
||||||
|
type RegistrarDomainDetailResponse struct {
|
||||||
|
Response
|
||||||
|
Result RegistrarDomain `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistrarDomainsDetailResponse is the structure of the detailed
|
||||||
|
// response from the API.
|
||||||
|
type RegistrarDomainsDetailResponse struct {
|
||||||
|
Response
|
||||||
|
Result []RegistrarDomain `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistrarDomain returns a single domain based on the account ID and
|
||||||
|
// domain name.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#registrar-domains-get-domain
|
||||||
|
func (api *API) RegistrarDomain(accountID, domainName string) (RegistrarDomain, error) {
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/registrar/domains/%s", accountID, domainName)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return RegistrarDomain{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r RegistrarDomainDetailResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return RegistrarDomain{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistrarDomains returns all registrar domains based on the account
|
||||||
|
// ID.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#registrar-domains-list-domains
|
||||||
|
func (api *API) RegistrarDomains(accountID string) ([]RegistrarDomain, error) {
|
||||||
|
uri := "/accounts/" + accountID + "/registrar/domains"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("POST", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []RegistrarDomain{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r RegistrarDomainsDetailResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return []RegistrarDomain{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransferRegistrarDomain initiates the transfer from another registrar
|
||||||
|
// to Cloudflare Registrar.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#registrar-domains-transfer-domain
|
||||||
|
func (api *API) TransferRegistrarDomain(accountID, domainName string) ([]RegistrarDomain, error) {
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/registrar/domains/%s/transfer", accountID, domainName)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("POST", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []RegistrarDomain{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r RegistrarDomainsDetailResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return []RegistrarDomain{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelRegistrarDomainTransfer cancels a pending domain transfer.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#registrar-domains-cancel-transfer
|
||||||
|
func (api *API) CancelRegistrarDomainTransfer(accountID, domainName string) ([]RegistrarDomain, error) {
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/registrar/domains/%s/cancel_transfer", accountID, domainName)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("POST", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []RegistrarDomain{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r RegistrarDomainsDetailResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return []RegistrarDomain{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRegistrarDomain updates an existing Registrar Domain configuration.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#registrar-domains-update-domain
|
||||||
|
func (api *API) UpdateRegistrarDomain(accountID, domainName string, domainConfiguration RegistrarDomainConfiguration) (RegistrarDomain, error) {
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/registrar/domains/%s", accountID, domainName)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, domainConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
return RegistrarDomain{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r RegistrarDomainDetailResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return RegistrarDomain{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
5
vendor/github.com/cloudflare/cloudflare-go/renovate.json
generated
vendored
Normal file
5
vendor/github.com/cloudflare/cloudflare-go/renovate.json
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"config:base"
|
||||||
|
]
|
||||||
|
}
|
158
vendor/github.com/cloudflare/cloudflare-go/spectrum.go
generated
vendored
Normal file
158
vendor/github.com/cloudflare/cloudflare-go/spectrum.go
generated
vendored
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SpectrumApplication defines a single Spectrum Application.
|
||||||
|
type SpectrumApplication struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Protocol string `json:"protocol,omitempty"`
|
||||||
|
IPv4 bool `json:"ipv4,omitempty"`
|
||||||
|
DNS SpectrumApplicationDNS `json:"dns,omitempty"`
|
||||||
|
OriginDirect []string `json:"origin_direct,omitempty"`
|
||||||
|
OriginPort int `json:"origin_port,omitempty"`
|
||||||
|
OriginDNS *SpectrumApplicationOriginDNS `json:"origin_dns,omitempty"`
|
||||||
|
IPFirewall bool `json:"ip_firewall,omitempty"`
|
||||||
|
ProxyProtocol bool `json:"proxy_protocol,omitempty"`
|
||||||
|
TLS string `json:"tls,omitempty"`
|
||||||
|
CreatedOn *time.Time `json:"created_on,omitempty"`
|
||||||
|
ModifiedOn *time.Time `json:"modified_on,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpectrumApplicationDNS holds the external DNS configuration for a Spectrum
|
||||||
|
// Application.
|
||||||
|
type SpectrumApplicationDNS struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpectrumApplicationOriginDNS holds the origin DNS configuration for a Spectrum
|
||||||
|
// Application.
|
||||||
|
type SpectrumApplicationOriginDNS struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpectrumApplicationDetailResponse is the structure of the detailed response
|
||||||
|
// from the API.
|
||||||
|
type SpectrumApplicationDetailResponse struct {
|
||||||
|
Response
|
||||||
|
Result SpectrumApplication `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpectrumApplicationsDetailResponse is the structure of the detailed response
|
||||||
|
// from the API.
|
||||||
|
type SpectrumApplicationsDetailResponse struct {
|
||||||
|
Response
|
||||||
|
Result []SpectrumApplication `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpectrumApplications fetches all of the Spectrum applications for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/spectrum/api-reference/#list-spectrum-applications
|
||||||
|
func (api *API) SpectrumApplications(zoneID string) ([]SpectrumApplication, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/spectrum/apps"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []SpectrumApplication{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var spectrumApplications SpectrumApplicationsDetailResponse
|
||||||
|
err = json.Unmarshal(res, &spectrumApplications)
|
||||||
|
if err != nil {
|
||||||
|
return []SpectrumApplication{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spectrumApplications.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpectrumApplication fetches a single Spectrum application based on the ID.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/spectrum/api-reference/#list-spectrum-applications
|
||||||
|
func (api *API) SpectrumApplication(zoneID string, applicationID string) (SpectrumApplication, error) {
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/zones/%s/spectrum/apps/%s",
|
||||||
|
zoneID,
|
||||||
|
applicationID,
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return SpectrumApplication{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var spectrumApplication SpectrumApplicationDetailResponse
|
||||||
|
err = json.Unmarshal(res, &spectrumApplication)
|
||||||
|
if err != nil {
|
||||||
|
return SpectrumApplication{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spectrumApplication.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSpectrumApplication creates a new Spectrum application.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/spectrum/api-reference/#create-a-spectrum-application
|
||||||
|
func (api *API) CreateSpectrumApplication(zoneID string, appDetails SpectrumApplication) (SpectrumApplication, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/spectrum/apps"
|
||||||
|
|
||||||
|
res, err := api.makeRequest("POST", uri, appDetails)
|
||||||
|
if err != nil {
|
||||||
|
return SpectrumApplication{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var spectrumApplication SpectrumApplicationDetailResponse
|
||||||
|
err = json.Unmarshal(res, &spectrumApplication)
|
||||||
|
if err != nil {
|
||||||
|
return SpectrumApplication{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spectrumApplication.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSpectrumApplication updates an existing Spectrum application.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/spectrum/api-reference/#update-a-spectrum-application
|
||||||
|
func (api *API) UpdateSpectrumApplication(zoneID, appID string, appDetails SpectrumApplication) (SpectrumApplication, error) {
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/zones/%s/spectrum/apps/%s",
|
||||||
|
zoneID,
|
||||||
|
appID,
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := api.makeRequest("PUT", uri, appDetails)
|
||||||
|
if err != nil {
|
||||||
|
return SpectrumApplication{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var spectrumApplication SpectrumApplicationDetailResponse
|
||||||
|
err = json.Unmarshal(res, &spectrumApplication)
|
||||||
|
if err != nil {
|
||||||
|
return SpectrumApplication{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spectrumApplication.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSpectrumApplication removes a Spectrum application based on the ID.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/spectrum/api-reference/#delete-a-spectrum-application
|
||||||
|
func (api *API) DeleteSpectrumApplication(zoneID string, applicationID string) error {
|
||||||
|
uri := fmt.Sprintf(
|
||||||
|
"/zones/%s/spectrum/apps/%s",
|
||||||
|
zoneID,
|
||||||
|
applicationID,
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
157
vendor/github.com/cloudflare/cloudflare-go/ssl.go
generated
vendored
Normal file
157
vendor/github.com/cloudflare/cloudflare-go/ssl.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
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"`
|
||||||
|
GeoRestrictions ZoneCustomSSLGeoRestrictions `json:"geo_restrictions"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneCustomSSLGeoRestrictions represents the parameter to create or update
|
||||||
|
// geographic restrictions on a custom ssl certificate.
|
||||||
|
type ZoneCustomSSLGeoRestrictions struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
GeoRestrictions ZoneCustomSSLGeoRestrictions `json:"geo_restrictions,omitempty"`
|
||||||
|
Type string `json:"type,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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
}
|
88
vendor/github.com/cloudflare/cloudflare-go/universal_ssl.go
generated
vendored
Normal file
88
vendor/github.com/cloudflare/cloudflare-go/universal_ssl.go
generated
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UniversalSSLSetting represents a universal ssl setting's properties.
|
||||||
|
type UniversalSSLSetting struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type universalSSLSettingResponse struct {
|
||||||
|
Response
|
||||||
|
Result UniversalSSLSetting `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniversalSSLVerificationDetails represents a universal ssl verifcation's properties.
|
||||||
|
type UniversalSSLVerificationDetails struct {
|
||||||
|
CertificateStatus string `json:"certificate_status"`
|
||||||
|
VerificationType string `json:"verification_type"`
|
||||||
|
ValidationMethod string `json:"validation_method"`
|
||||||
|
CertPackUUID string `json:"cert_pack_uuid"`
|
||||||
|
VerificationStatus bool `json:"verification_status"`
|
||||||
|
BrandCheck bool `json:"brand_check"`
|
||||||
|
VerificationInfo UniversalSSLVerificationInfo `json:"verification_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniversalSSLVerificationInfo represents DCV record.
|
||||||
|
type UniversalSSLVerificationInfo struct {
|
||||||
|
RecordName string `json:"record_name"`
|
||||||
|
RecordTarget string `json:"record_target"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type universalSSLVerificationResponse struct {
|
||||||
|
Response
|
||||||
|
Result []UniversalSSLVerificationDetails `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniversalSSLSettingDetails returns the details for a universal ssl setting
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#universal-ssl-settings-for-a-zone-universal-ssl-settings-details
|
||||||
|
func (api *API) UniversalSSLSettingDetails(zoneID string) (UniversalSSLSetting, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/ssl/universal/settings"
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return UniversalSSLSetting{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r universalSSLSettingResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return UniversalSSLSetting{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditUniversalSSLSetting edits the uniersal ssl setting for a zone
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#universal-ssl-settings-for-a-zone-edit-universal-ssl-settings
|
||||||
|
func (api *API) EditUniversalSSLSetting(zoneID string, setting UniversalSSLSetting) (UniversalSSLSetting, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/ssl/universal/settings"
|
||||||
|
res, err := api.makeRequest("PATCH", uri, setting)
|
||||||
|
if err != nil {
|
||||||
|
return UniversalSSLSetting{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r universalSSLSettingResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return UniversalSSLSetting{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniversalSSLVerificationDetails returns the details for a universal ssl verifcation
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#ssl-verification-ssl-verification-details
|
||||||
|
func (api *API) UniversalSSLVerificationDetails(zoneID string) ([]UniversalSSLVerificationDetails, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/ssl/verification"
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []UniversalSSLVerificationDetails{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r universalSSLVerificationResponse
|
||||||
|
if err := json.Unmarshal(res, &r); err != nil {
|
||||||
|
return []UniversalSSLVerificationDetails{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
113
vendor/github.com/cloudflare/cloudflare-go/user.go
generated
vendored
Normal file
113
vendor/github.com/cloudflare/cloudflare-go/user.go
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
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"`
|
||||||
|
Accounts []Account `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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
}
|
149
vendor/github.com/cloudflare/cloudflare-go/user_agent.go
generated
vendored
Normal file
149
vendor/github.com/cloudflare/cloudflare-go/user_agent.go
generated
vendored
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserAgentRule represents a User-Agent Block. These rules can be used to
|
||||||
|
// challenge, block or whitelist specific User-Agents for a given zone.
|
||||||
|
type UserAgentRule struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Configuration UserAgentRuleConfig `json:"configuration"`
|
||||||
|
Paused bool `json:"paused"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAgentRuleConfig represents a Zone Lockdown config, which comprises
|
||||||
|
// a Target ("ip" or "ip_range") and a Value (an IP address or IP+mask,
|
||||||
|
// respectively.)
|
||||||
|
type UserAgentRuleConfig ZoneLockdownConfig
|
||||||
|
|
||||||
|
// UserAgentRuleResponse represents a response from the Zone Lockdown endpoint.
|
||||||
|
type UserAgentRuleResponse struct {
|
||||||
|
Result UserAgentRule `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAgentRuleListResponse represents a response from the List Zone Lockdown endpoint.
|
||||||
|
type UserAgentRuleListResponse struct {
|
||||||
|
Result []UserAgentRule `json:"result"`
|
||||||
|
Response
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserAgentRule creates a User-Agent Block rule for the given zone ID.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#user-agent-blocking-rules-create-a-useragent-rule
|
||||||
|
func (api *API) CreateUserAgentRule(zoneID string, ld UserAgentRule) (*UserAgentRuleResponse, error) {
|
||||||
|
switch ld.Mode {
|
||||||
|
case "block", "challenge", "js_challenge", "whitelist":
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return nil, errors.New(`the User-Agent Block rule mode must be one of "block", "challenge", "js_challenge", "whitelist"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/ua_rules"
|
||||||
|
res, err := api.makeRequest("POST", uri, ld)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &UserAgentRuleResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserAgentRule updates a User-Agent Block rule (based on the ID) for the given zone ID.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#user-agent-blocking-rules-update-useragent-rule
|
||||||
|
func (api *API) UpdateUserAgentRule(zoneID string, id string, ld UserAgentRule) (*UserAgentRuleResponse, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/ua_rules/" + id
|
||||||
|
res, err := api.makeRequest("PUT", uri, ld)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &UserAgentRuleResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUserAgentRule deletes a User-Agent Block rule (based on the ID) for the given zone ID.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#user-agent-blocking-rules-delete-useragent-rule
|
||||||
|
func (api *API) DeleteUserAgentRule(zoneID string, id string) (*UserAgentRuleResponse, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/ua_rules/" + id
|
||||||
|
res, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &UserAgentRuleResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAgentRule retrieves a User-Agent Block rule (based on the ID) for the given zone ID.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#user-agent-blocking-rules-useragent-rule-details
|
||||||
|
func (api *API) UserAgentRule(zoneID string, id string) (*UserAgentRuleResponse, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/ua_rules/" + id
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &UserAgentRuleResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUserAgentRules retrieves a list of User-Agent Block rules for a given zone ID by page number.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#user-agent-blocking-rules-list-useragent-rules
|
||||||
|
func (api *API) ListUserAgentRules(zoneID string, page int) (*UserAgentRuleListResponse, error) {
|
||||||
|
v := url.Values{}
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Set("page", strconv.Itoa(page))
|
||||||
|
v.Set("per_page", strconv.Itoa(100))
|
||||||
|
query := "?" + v.Encode()
|
||||||
|
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/ua_rules" + query
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &UserAgentRuleListResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
192
vendor/github.com/cloudflare/cloudflare-go/virtualdns.go
generated
vendored
Normal file
192
vendor/github.com/cloudflare/cloudflare-go/virtualdns.go
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VirtualDNSAnalyticsMetrics respresents a group of aggregated Virtual DNS metrics.
|
||||||
|
type VirtualDNSAnalyticsMetrics struct {
|
||||||
|
QueryCount *int64 `json:"queryCount"`
|
||||||
|
UncachedCount *int64 `json:"uncachedCount"`
|
||||||
|
StaleCount *int64 `json:"staleCount"`
|
||||||
|
ResponseTimeAvg *float64 `json:"responseTimeAvg"`
|
||||||
|
ResponseTimeMedian *float64 `json:"responseTimeMedian"`
|
||||||
|
ResponseTime90th *float64 `json:"responseTime90th"`
|
||||||
|
ResponseTime99th *float64 `json:"responseTime99th"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VirtualDNSAnalytics represents a set of aggregated Virtual DNS metrics.
|
||||||
|
// TODO: Add the queried data and not only the aggregated values.
|
||||||
|
type VirtualDNSAnalytics struct {
|
||||||
|
Totals VirtualDNSAnalyticsMetrics `json:"totals"`
|
||||||
|
Min VirtualDNSAnalyticsMetrics `json:"min"`
|
||||||
|
Max VirtualDNSAnalyticsMetrics `json:"max"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VirtualDNSUserAnalyticsOptions represents range and dimension selection on analytics endpoint
|
||||||
|
type VirtualDNSUserAnalyticsOptions struct {
|
||||||
|
Metrics []string
|
||||||
|
Since *time.Time
|
||||||
|
Until *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VirtualDNSAnalyticsResponse represents a Virtual DNS analytics response.
|
||||||
|
type VirtualDNSAnalyticsResponse struct {
|
||||||
|
Response
|
||||||
|
Result VirtualDNSAnalytics `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateVirtualDNS creates a new Virtual DNS cluster.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#virtual-dns-users--create-a-virtual-dns-cluster
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode encodes non-nil fields into URL encoded form.
|
||||||
|
func (o VirtualDNSUserAnalyticsOptions) encode() string {
|
||||||
|
v := url.Values{}
|
||||||
|
if o.Since != nil {
|
||||||
|
v.Set("since", (*o.Since).UTC().Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
if o.Until != nil {
|
||||||
|
v.Set("until", (*o.Until).UTC().Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
if o.Metrics != nil {
|
||||||
|
v.Set("metrics", strings.Join(o.Metrics, ","))
|
||||||
|
}
|
||||||
|
return v.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// VirtualDNSUserAnalytics retrieves analytics report for a specified dimension and time range
|
||||||
|
func (api *API) VirtualDNSUserAnalytics(virtualDNSID string, o VirtualDNSUserAnalyticsOptions) (VirtualDNSAnalytics, error) {
|
||||||
|
uri := "/user/virtual_dns/" + virtualDNSID + "/dns_analytics/report?" + o.encode()
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return VirtualDNSAnalytics{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := VirtualDNSAnalyticsResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return VirtualDNSAnalytics{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Result, nil
|
||||||
|
}
|
300
vendor/github.com/cloudflare/cloudflare-go/waf.go
generated
vendored
Normal file
300
vendor/github.com/cloudflare/cloudflare-go/waf.go
generated
vendored
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WAFPackageResponse represents the response from the WAF package endpoint.
|
||||||
|
type WAFPackageResponse struct {
|
||||||
|
Response
|
||||||
|
Result WAFPackage `json:"result"`
|
||||||
|
ResultInfo ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WAFPackageOptions represents options to edit a WAF package.
|
||||||
|
type WAFPackageOptions struct {
|
||||||
|
Sensitivity string `json:"sensitivity,omitempty"`
|
||||||
|
ActionMode string `json:"action_mode,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WAFGroup represents a WAF rule group.
|
||||||
|
type WAFGroup struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
RulesCount int `json:"rules_count"`
|
||||||
|
ModifiedRulesCount int `json:"modified_rules_count"`
|
||||||
|
PackageID string `json:"package_id"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
AllowedModes []string `json:"allowed_modes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WAFGroupsResponse represents the response from the WAF groups endpoint.
|
||||||
|
type WAFGroupsResponse struct {
|
||||||
|
Response
|
||||||
|
Result []WAFGroup `json:"result"`
|
||||||
|
ResultInfo ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WAFGroupResponse represents the response from the WAF group endpoint.
|
||||||
|
type WAFGroupResponse struct {
|
||||||
|
Response
|
||||||
|
Result WAFGroup `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 rules endpoint.
|
||||||
|
type WAFRulesResponse struct {
|
||||||
|
Response
|
||||||
|
Result []WAFRule `json:"result"`
|
||||||
|
ResultInfo ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WAFRuleResponse represents the response from the WAF rule endpoint.
|
||||||
|
type WAFRuleResponse struct {
|
||||||
|
Response
|
||||||
|
Result WAFRule `json:"result"`
|
||||||
|
ResultInfo ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WAFRuleOptions is a subset of WAFRule, for editable options.
|
||||||
|
type WAFRuleOptions struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWAFPackages returns a slice of the WAF packages for the given zone.
|
||||||
|
//
|
||||||
|
// API Reference: https://api.cloudflare.com/#waf-rule-packages-list-firewall-packages
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// WAFPackage returns a WAF package for the given zone.
|
||||||
|
//
|
||||||
|
// API Reference: https://api.cloudflare.com/#waf-rule-packages-firewall-package-details
|
||||||
|
func (api *API) WAFPackage(zoneID, packageID string) (WAFPackage, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/waf/packages/" + packageID
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return WAFPackage{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r WAFPackageResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return WAFPackage{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWAFPackage lets you update the a WAF Package.
|
||||||
|
//
|
||||||
|
// API Reference: https://api.cloudflare.com/#waf-rule-packages-edit-firewall-package
|
||||||
|
func (api *API) UpdateWAFPackage(zoneID, packageID string, opts WAFPackageOptions) (WAFPackage, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/waf/packages/" + packageID
|
||||||
|
res, err := api.makeRequest("PATCH", uri, opts)
|
||||||
|
if err != nil {
|
||||||
|
return WAFPackage{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r WAFPackageResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return WAFPackage{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWAFGroups returns a slice of the WAF groups for the given WAF package.
|
||||||
|
//
|
||||||
|
// API Reference: https://api.cloudflare.com/#waf-rule-groups-list-rule-groups
|
||||||
|
func (api *API) ListWAFGroups(zoneID, packageID string) ([]WAFGroup, error) {
|
||||||
|
var groups []WAFGroup
|
||||||
|
var res []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/waf/packages/" + packageID + "/groups"
|
||||||
|
res, err = api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []WAFGroup{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r WAFGroupsResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return []WAFGroup{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.Success {
|
||||||
|
// TODO: Provide an actual error message instead of always returning nil
|
||||||
|
return []WAFGroup{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for gi := range r.Result {
|
||||||
|
groups = append(groups, r.Result[gi])
|
||||||
|
}
|
||||||
|
return groups, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WAFGroup returns a WAF rule group from the given WAF package.
|
||||||
|
//
|
||||||
|
// API Reference: https://api.cloudflare.com/#waf-rule-groups-rule-group-details
|
||||||
|
func (api *API) WAFGroup(zoneID, packageID, groupID string) (WAFGroup, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/waf/packages/" + packageID + "/groups/" + groupID
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return WAFGroup{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r WAFGroupResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return WAFGroup{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWAFGroup lets you update the mode of a WAF Group.
|
||||||
|
//
|
||||||
|
// API Reference: https://api.cloudflare.com/#waf-rule-groups-edit-rule-group
|
||||||
|
func (api *API) UpdateWAFGroup(zoneID, packageID, groupID, mode string) (WAFGroup, error) {
|
||||||
|
opts := WAFRuleOptions{Mode: mode}
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/waf/packages/" + packageID + "/groups/" + groupID
|
||||||
|
res, err := api.makeRequest("PATCH", uri, opts)
|
||||||
|
if err != nil {
|
||||||
|
return WAFGroup{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r WAFGroupResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return WAFGroup{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWAFRules returns a slice of the WAF rules for the given WAF package.
|
||||||
|
//
|
||||||
|
// API Reference: https://api.cloudflare.com/#waf-rules-list-rules
|
||||||
|
func (api *API) ListWAFRules(zoneID, packageID string) ([]WAFRule, error) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r WAFRulesResponse
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// WAFRule returns a WAF rule from the given WAF package.
|
||||||
|
//
|
||||||
|
// API Reference: https://api.cloudflare.com/#waf-rules-rule-details
|
||||||
|
func (api *API) WAFRule(zoneID, packageID, ruleID string) (WAFRule, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/waf/packages/" + packageID + "/rules/" + ruleID
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return WAFRule{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r WAFRuleResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return WAFRule{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWAFRule lets you update the mode of a WAF Rule.
|
||||||
|
//
|
||||||
|
// API Reference: https://api.cloudflare.com/#waf-rules-edit-rule
|
||||||
|
func (api *API) UpdateWAFRule(zoneID, packageID, ruleID, mode string) (WAFRule, error) {
|
||||||
|
opts := WAFRuleOptions{Mode: mode}
|
||||||
|
uri := "/zones/" + zoneID + "/firewall/waf/packages/" + packageID + "/rules/" + ruleID
|
||||||
|
res, err := api.makeRequest("PATCH", uri, opts)
|
||||||
|
if err != nil {
|
||||||
|
return WAFRule{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r WAFRuleResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return WAFRule{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
314
vendor/github.com/cloudflare/cloudflare-go/workers.go
generated
vendored
Normal file
314
vendor/github.com/cloudflare/cloudflare-go/workers.go
generated
vendored
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WorkerRequestParams provides parameters for worker requests for both enterprise and standard requests
|
||||||
|
type WorkerRequestParams struct {
|
||||||
|
ZoneID string
|
||||||
|
ScriptName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkerRoute aka filters are patterns used to enable or disable workers that match requests.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#worker-filters-properties
|
||||||
|
type WorkerRoute struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Pattern string `json:"pattern"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Script string `json:"script,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkerRoutesResponse embeds Response struct and slice of WorkerRoutes
|
||||||
|
type WorkerRoutesResponse struct {
|
||||||
|
Response
|
||||||
|
Routes []WorkerRoute `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkerRouteResponse embeds Response struct and a single WorkerRoute
|
||||||
|
type WorkerRouteResponse struct {
|
||||||
|
Response
|
||||||
|
WorkerRoute `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkerScript Cloudflare Worker struct with metadata
|
||||||
|
type WorkerScript struct {
|
||||||
|
WorkerMetaData
|
||||||
|
Script string `json:"script"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkerMetaData contains worker script information such as size, creation & modification dates
|
||||||
|
type WorkerMetaData struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
ETAG string `json:"etag,omitempty"`
|
||||||
|
Size int `json:"size,omitempty"`
|
||||||
|
CreatedOn time.Time `json:"created_on,omitempty"`
|
||||||
|
ModifiedOn time.Time `json:"modified_on,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkerListResponse wrapper struct for API response to worker script list API call
|
||||||
|
type WorkerListResponse struct {
|
||||||
|
Response
|
||||||
|
WorkerList []WorkerMetaData `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkerScriptResponse wrapper struct for API response to worker script calls
|
||||||
|
type WorkerScriptResponse struct {
|
||||||
|
Response
|
||||||
|
WorkerScript `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteWorker deletes worker for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#worker-script-delete-worker
|
||||||
|
func (api *API) DeleteWorker(requestParams *WorkerRequestParams) (WorkerScriptResponse, error) {
|
||||||
|
// if ScriptName is provided we will treat as org request
|
||||||
|
if requestParams.ScriptName != "" {
|
||||||
|
return api.deleteWorkerWithName(requestParams.ScriptName)
|
||||||
|
}
|
||||||
|
uri := "/zones/" + requestParams.ZoneID + "/workers/script"
|
||||||
|
res, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
var r WorkerScriptResponse
|
||||||
|
if err != nil {
|
||||||
|
return r, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return r, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteWorkerWithName deletes worker for a zone.
|
||||||
|
// This is an enterprise only feature https://developers.cloudflare.com/workers/api/config-api-for-enterprise
|
||||||
|
// account must be specified as api option https://godoc.org/github.com/cloudflare/cloudflare-go#UsingAccount
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#worker-script-delete-worker
|
||||||
|
func (api *API) deleteWorkerWithName(scriptName string) (WorkerScriptResponse, error) {
|
||||||
|
if api.AccountID == "" {
|
||||||
|
return WorkerScriptResponse{}, errors.New("account ID required for enterprise only request")
|
||||||
|
}
|
||||||
|
uri := "/accounts/" + api.AccountID + "/workers/scripts/" + scriptName
|
||||||
|
res, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
var r WorkerScriptResponse
|
||||||
|
if err != nil {
|
||||||
|
return r, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return r, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadWorker fetch raw script content for your worker returns []byte containing worker code js
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#worker-script-download-worker
|
||||||
|
func (api *API) DownloadWorker(requestParams *WorkerRequestParams) (WorkerScriptResponse, error) {
|
||||||
|
if requestParams.ScriptName != "" {
|
||||||
|
return api.downloadWorkerWithName(requestParams.ScriptName)
|
||||||
|
}
|
||||||
|
uri := "/zones/" + requestParams.ZoneID + "/workers/script"
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
var r WorkerScriptResponse
|
||||||
|
if err != nil {
|
||||||
|
return r, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
r.Script = string(res)
|
||||||
|
r.Success = true
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadWorkerWithName fetch raw script content for your worker returns string containing worker code js
|
||||||
|
// This is an enterprise only feature https://developers.cloudflare.com/workers/api/config-api-for-enterprise/
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#worker-script-download-worker
|
||||||
|
func (api *API) downloadWorkerWithName(scriptName string) (WorkerScriptResponse, error) {
|
||||||
|
if api.AccountID == "" {
|
||||||
|
return WorkerScriptResponse{}, errors.New("account ID required for enterprise only request")
|
||||||
|
}
|
||||||
|
uri := "/accounts/" + api.AccountID + "/workers/scripts/" + scriptName
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
var r WorkerScriptResponse
|
||||||
|
if err != nil {
|
||||||
|
return r, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
r.Script = string(res)
|
||||||
|
r.Success = true
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWorkerScripts returns list of worker scripts for given account.
|
||||||
|
//
|
||||||
|
// This is an enterprise only feature https://developers.cloudflare.com/workers/api/config-api-for-enterprise
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/workers/api/config-api-for-enterprise/
|
||||||
|
func (api *API) ListWorkerScripts() (WorkerListResponse, error) {
|
||||||
|
if api.AccountID == "" {
|
||||||
|
return WorkerListResponse{}, errors.New("account ID required for enterprise only request")
|
||||||
|
}
|
||||||
|
uri := "/accounts/" + api.AccountID + "/workers/scripts"
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return WorkerListResponse{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r WorkerListResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return WorkerListResponse{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadWorker push raw script content for your worker.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#worker-script-upload-worker
|
||||||
|
func (api *API) UploadWorker(requestParams *WorkerRequestParams, data string) (WorkerScriptResponse, error) {
|
||||||
|
if requestParams.ScriptName != "" {
|
||||||
|
return api.uploadWorkerWithName(requestParams.ScriptName, data)
|
||||||
|
}
|
||||||
|
uri := "/zones/" + requestParams.ZoneID + "/workers/script"
|
||||||
|
headers := make(http.Header)
|
||||||
|
headers.Set("Content-Type", "application/javascript")
|
||||||
|
res, err := api.makeRequestWithHeaders("PUT", uri, []byte(data), headers)
|
||||||
|
var r WorkerScriptResponse
|
||||||
|
if err != nil {
|
||||||
|
return r, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return r, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadWorkerWithName push raw script content for your worker.
|
||||||
|
//
|
||||||
|
// This is an enterprise only feature https://developers.cloudflare.com/workers/api/config-api-for-enterprise/
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#worker-script-upload-worker
|
||||||
|
func (api *API) uploadWorkerWithName(scriptName string, data string) (WorkerScriptResponse, error) {
|
||||||
|
if api.AccountID == "" {
|
||||||
|
return WorkerScriptResponse{}, errors.New("account ID required for enterprise only request")
|
||||||
|
}
|
||||||
|
uri := "/accounts/" + api.AccountID + "/workers/scripts/" + scriptName
|
||||||
|
headers := make(http.Header)
|
||||||
|
headers.Set("Content-Type", "application/javascript")
|
||||||
|
res, err := api.makeRequestWithHeaders("PUT", uri, []byte(data), headers)
|
||||||
|
var r WorkerScriptResponse
|
||||||
|
if err != nil {
|
||||||
|
return r, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return r, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateWorkerRoute creates worker route for a zone
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#worker-filters-create-filter
|
||||||
|
func (api *API) CreateWorkerRoute(zoneID string, route WorkerRoute) (WorkerRouteResponse, error) {
|
||||||
|
// Check whether a script name is defined in order to determine whether
|
||||||
|
// to use the single-script or multi-script endpoint.
|
||||||
|
pathComponent := "filters"
|
||||||
|
if route.Script != "" {
|
||||||
|
if api.AccountID == "" {
|
||||||
|
return WorkerRouteResponse{}, errors.New("account ID required for enterprise only request")
|
||||||
|
}
|
||||||
|
pathComponent = "routes"
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := "/zones/" + zoneID + "/workers/" + pathComponent
|
||||||
|
res, err := api.makeRequest("POST", uri, route)
|
||||||
|
if err != nil {
|
||||||
|
return WorkerRouteResponse{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r WorkerRouteResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return WorkerRouteResponse{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteWorkerRoute deletes worker route for a zone
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#worker-filters-delete-filter
|
||||||
|
func (api *API) DeleteWorkerRoute(zoneID string, routeID string) (WorkerRouteResponse, error) {
|
||||||
|
// For deleting a route, it doesn't matter whether we use the
|
||||||
|
// single-script or multi-script endpoint
|
||||||
|
uri := "/zones/" + zoneID + "/workers/filters/" + routeID
|
||||||
|
res, err := api.makeRequest("DELETE", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return WorkerRouteResponse{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r WorkerRouteResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return WorkerRouteResponse{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWorkerRoutes returns list of worker routes
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#worker-filters-list-filters
|
||||||
|
func (api *API) ListWorkerRoutes(zoneID string) (WorkerRoutesResponse, error) {
|
||||||
|
pathComponent := "filters"
|
||||||
|
if api.AccountID != "" {
|
||||||
|
pathComponent = "routes"
|
||||||
|
}
|
||||||
|
uri := "/zones/" + zoneID + "/workers/" + pathComponent
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return WorkerRoutesResponse{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r WorkerRoutesResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return WorkerRoutesResponse{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
for i := range r.Routes {
|
||||||
|
route := &r.Routes[i]
|
||||||
|
// The Enabled flag will not be set in the multi-script API response
|
||||||
|
// so we manually set it to true if the script name is not empty
|
||||||
|
// in case any multi-script customers rely on the Enabled field
|
||||||
|
if route.Script != "" {
|
||||||
|
route.Enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWorkerRoute updates worker route for a zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#worker-filters-update-filter
|
||||||
|
func (api *API) UpdateWorkerRoute(zoneID string, routeID string, route WorkerRoute) (WorkerRouteResponse, error) {
|
||||||
|
// Check whether a script name is defined in order to determine whether
|
||||||
|
// to use the single-script or multi-script endpoint.
|
||||||
|
pathComponent := "filters"
|
||||||
|
if route.Script != "" {
|
||||||
|
if api.AccountID == "" {
|
||||||
|
return WorkerRouteResponse{}, errors.New("account ID required for enterprise only request")
|
||||||
|
}
|
||||||
|
pathComponent = "routes"
|
||||||
|
}
|
||||||
|
uri := "/zones/" + zoneID + "/workers/" + pathComponent + "/" + routeID
|
||||||
|
res, err := api.makeRequest("PUT", uri, route)
|
||||||
|
if err != nil {
|
||||||
|
return WorkerRouteResponse{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r WorkerRouteResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return WorkerRouteResponse{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
192
vendor/github.com/cloudflare/cloudflare-go/workers_kv.go
generated
vendored
Normal file
192
vendor/github.com/cloudflare/cloudflare-go/workers_kv.go
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WorkersKVNamespaceRequest provides parameters for creating and updating storage namespaces
|
||||||
|
type WorkersKVNamespaceRequest struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkersKVNamespaceResponse is the response received when creating storage namespaces
|
||||||
|
type WorkersKVNamespaceResponse struct {
|
||||||
|
Response
|
||||||
|
Result WorkersKVNamespace `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkersKVNamespace contains the unique identifier and title of a storage namespace
|
||||||
|
type WorkersKVNamespace struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWorkersKVNamespacesResponse contains a slice of storage namespaces associated with an
|
||||||
|
// account, pagination information, and an embedded response struct
|
||||||
|
type ListWorkersKVNamespacesResponse struct {
|
||||||
|
Response
|
||||||
|
Result []WorkersKVNamespace `json:"result"`
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageKey is a key name used to identify a storage value
|
||||||
|
type StorageKey struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListStorageKeysResponse contains a slice of keys belonging to a storage namespace,
|
||||||
|
// pagination information, and an embedded response struct
|
||||||
|
type ListStorageKeysResponse struct {
|
||||||
|
Response
|
||||||
|
Result []StorageKey `json:"result"`
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateWorkersKVNamespace creates a namespace under the given title.
|
||||||
|
// A 400 is returned if the account already owns a namespace with this title.
|
||||||
|
// A namespace must be explicitly deleted to be replaced.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#workers-kv-namespace-create-a-namespace
|
||||||
|
func (api *API) CreateWorkersKVNamespace(ctx context.Context, req *WorkersKVNamespaceRequest) (WorkersKVNamespaceResponse, error) {
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces", api.AccountID)
|
||||||
|
res, err := api.makeRequestContext(ctx, http.MethodPost, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return WorkersKVNamespaceResponse{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := WorkersKVNamespaceResponse{}
|
||||||
|
if err := json.Unmarshal(res, &result); err != nil {
|
||||||
|
return result, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWorkersKVNamespaces lists storage namespaces
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#workers-kv-namespace-list-namespaces
|
||||||
|
func (api *API) ListWorkersKVNamespaces(ctx context.Context) (ListWorkersKVNamespacesResponse, error) {
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces", api.AccountID)
|
||||||
|
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return ListWorkersKVNamespacesResponse{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ListWorkersKVNamespacesResponse{}
|
||||||
|
if err := json.Unmarshal(res, &result); err != nil {
|
||||||
|
return result, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteWorkersKVNamespace deletes the namespace corresponding to the given ID
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#workers-kv-namespace-remove-a-namespace
|
||||||
|
func (api *API) DeleteWorkersKVNamespace(ctx context.Context, namespaceID string) (Response, error) {
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s", api.AccountID, namespaceID)
|
||||||
|
res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return Response{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := Response{}
|
||||||
|
if err := json.Unmarshal(res, &result); err != nil {
|
||||||
|
return result, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWorkersKVNamespace modifies a namespace's title
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#workers-kv-namespace-rename-a-namespace
|
||||||
|
func (api *API) UpdateWorkersKVNamespace(ctx context.Context, namespaceID string, req *WorkersKVNamespaceRequest) (Response, error) {
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s", api.AccountID, namespaceID)
|
||||||
|
res, err := api.makeRequestContext(ctx, http.MethodPut, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return Response{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := Response{}
|
||||||
|
if err := json.Unmarshal(res, &result); err != nil {
|
||||||
|
return result, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteWorkersKV writes a value identified by a key.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#workers-kv-namespace-write-key-value-pair
|
||||||
|
func (api *API) WriteWorkersKV(ctx context.Context, namespaceID, key string, value []byte) (Response, error) {
|
||||||
|
key = url.PathEscape(key)
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/values/%s", api.AccountID, namespaceID, key)
|
||||||
|
res, err := api.makeRequestWithAuthTypeAndHeaders(
|
||||||
|
ctx, http.MethodPut, uri, value, api.authType, http.Header{"Content-Type": []string{"application/octet-stream"}},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return Response{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := Response{}
|
||||||
|
if err := json.Unmarshal(res, &result); err != nil {
|
||||||
|
return result, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadWorkersKV returns the value associated with the given key in the given namespace
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#workers-kv-namespace-read-key-value-pair
|
||||||
|
func (api API) ReadWorkersKV(ctx context.Context, namespaceID, key string) ([]byte, error) {
|
||||||
|
key = url.PathEscape(key)
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/values/%s", api.AccountID, namespaceID, key)
|
||||||
|
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteWorkersKV deletes a key and value for a provided storage namespace
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#workers-kv-namespace-delete-key-value-pair
|
||||||
|
func (api API) DeleteWorkersKV(ctx context.Context, namespaceID, key string) (Response, error) {
|
||||||
|
key = url.PathEscape(key)
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/values/%s", api.AccountID, namespaceID, key)
|
||||||
|
res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return Response{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := Response{}
|
||||||
|
if err := json.Unmarshal(res, &result); err != nil {
|
||||||
|
return result, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWorkersKVs lists a namespace's keys
|
||||||
|
//
|
||||||
|
// API Reference: https://api.cloudflare.com/#workers-kv-namespace-list-a-namespace-s-keys
|
||||||
|
func (api API) ListWorkersKVs(ctx context.Context, namespaceID string) (ListStorageKeysResponse, error) {
|
||||||
|
uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/keys", api.AccountID, namespaceID)
|
||||||
|
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return ListStorageKeysResponse{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ListStorageKeysResponse{}
|
||||||
|
if err := json.Unmarshal(res, &result); err != nil {
|
||||||
|
return result, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
740
vendor/github.com/cloudflare/cloudflare-go/zone.go
generated
vendored
Normal file
740
vendor/github.com/cloudflare/cloudflare-go/zone.go
generated
vendored
Normal file
@ -0,0 +1,740 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Owner describes the resource owner.
|
||||||
|
type Owner struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
OwnerType string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zone describes a Cloudflare zone.
|
||||||
|
type Zone struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
// DevMode contains the time in seconds until development expires (if
|
||||||
|
// positive) or since it expired (if negative). It will be 0 if never used.
|
||||||
|
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"`
|
||||||
|
Account Account `json:"account"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneMeta describes 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 {
|
||||||
|
ZonePlanCommon
|
||||||
|
IsSubscribed bool `json:"is_subscribed"`
|
||||||
|
CanSubscribe bool `json:"can_subscribe"`
|
||||||
|
LegacyID string `json:"legacy_id"`
|
||||||
|
LegacyDiscount bool `json:"legacy_discount"`
|
||||||
|
ExternallyManaged bool `json:"externally_managed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneRatePlan contains the plan information for a zone.
|
||||||
|
type ZoneRatePlan struct {
|
||||||
|
ZonePlanCommon
|
||||||
|
Components []zoneRatePlanComponents `json:"components,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZonePlanCommon contains fields used by various Plan endpoints
|
||||||
|
type ZonePlanCommon 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type zoneRatePlanComponents struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Default int `json:"Default"`
|
||||||
|
UnitPrice int `json:"unit_price"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneIDResponse represents the response from the Zone endpoint, containing only a zone ID.
|
||||||
|
type ZoneIDResponse struct {
|
||||||
|
Response
|
||||||
|
Result ZoneID `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AvailableZoneRatePlansResponse represents the response from the Available Rate Plans endpoint.
|
||||||
|
type AvailableZoneRatePlansResponse struct {
|
||||||
|
Response
|
||||||
|
Result []ZoneRatePlan `json:"result"`
|
||||||
|
ResultInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AvailableZonePlansResponse represents the response from the Available Plans endpoint.
|
||||||
|
type AvailableZonePlansResponse struct {
|
||||||
|
Response
|
||||||
|
Result []ZonePlan `json:"result"`
|
||||||
|
ResultInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneRatePlanResponse represents the response from the Plan Details endpoint.
|
||||||
|
type ZoneRatePlanResponse struct {
|
||||||
|
Response
|
||||||
|
Result ZoneRatePlan `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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneSSLSetting contains ssl setting for a zone.
|
||||||
|
type ZoneSSLSetting struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Editable bool `json:"editable"`
|
||||||
|
ModifiedOn string `json:"modified_on"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
CertificateStatus string `json:"certificate_status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneSSLSettingResponse represents the response from the Zone SSL Setting
|
||||||
|
// endpoint.
|
||||||
|
type ZoneSSLSettingResponse struct {
|
||||||
|
Response
|
||||||
|
Result ZoneSSLSetting `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"`
|
||||||
|
// Purge by filepath (exact match). Limit of 30
|
||||||
|
Files []string `json:"files,omitempty"`
|
||||||
|
// Purge by Tag (Enterprise only):
|
||||||
|
// https://support.cloudflare.com/hc/en-us/articles/206596608-How-to-Purge-Cache-Using-Cache-Tags-Enterprise-only-
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
// Purge by hostname - e.g. "assets.example.com"
|
||||||
|
Hosts []string `json:"hosts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PurgeCacheResponse represents the response from the purge endpoint.
|
||||||
|
type PurgeCacheResponse struct {
|
||||||
|
Response
|
||||||
|
Result struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newZone describes a new zone.
|
||||||
|
type newZone struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
JumpStart bool `json:"jump_start"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
// We use a pointer to get a nil type when the field is empty.
|
||||||
|
// This allows us to completely omit this with json.Marshal().
|
||||||
|
Account *Account `json:"organization,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FallbackOrigin describes a fallback origin
|
||||||
|
type FallbackOrigin struct {
|
||||||
|
Value string `json:"value"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FallbackOriginResponse represents the response from the fallback_origin endpoint
|
||||||
|
type FallbackOriginResponse struct {
|
||||||
|
Response
|
||||||
|
Result FallbackOrigin `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateZone creates a zone on an account.
|
||||||
|
//
|
||||||
|
// Setting jumpstart to true will attempt to automatically scan for existing
|
||||||
|
// DNS records. Setting this to false will create the zone with no DNS records.
|
||||||
|
//
|
||||||
|
// If account is non-empty, it must have at least the ID field populated.
|
||||||
|
// This will add the new zone to the specified multi-user account.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#zone-create-a-zone
|
||||||
|
func (api *API) CreateZone(name string, jumpstart bool, account Account, zoneType string) (Zone, error) {
|
||||||
|
var newzone newZone
|
||||||
|
newzone.Name = name
|
||||||
|
newzone.JumpStart = jumpstart
|
||||||
|
if account.ID != "" {
|
||||||
|
newzone.Account = &account
|
||||||
|
}
|
||||||
|
|
||||||
|
if zoneType == "partial" {
|
||||||
|
newzone.Type = "partial"
|
||||||
|
} else {
|
||||||
|
newzone.Type = "full"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
res, err = api.makeRequest("GET", "/zones?per_page=50", nil)
|
||||||
|
if err != nil {
|
||||||
|
return []Zone{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return []Zone{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPageCount := r.TotalPages
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(totalPageCount)
|
||||||
|
errc := make(chan error)
|
||||||
|
|
||||||
|
for i := 1; i <= totalPageCount; i++ {
|
||||||
|
go func(pageNumber int) error {
|
||||||
|
res, err = api.makeRequest("GET", fmt.Sprintf("/zones?per_page=50&page=%d", pageNumber), nil)
|
||||||
|
if err != nil {
|
||||||
|
errc <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
errc <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, zone := range r.Result {
|
||||||
|
zones = append(zones, zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errc:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
return zones, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListZonesContext lists zones on an account. Optionally takes a list of ReqOptions.
|
||||||
|
func (api *API) ListZonesContext(ctx context.Context, opts ...ReqOption) (r ZonesResponse, err error) {
|
||||||
|
var res []byte
|
||||||
|
opt := reqOption{
|
||||||
|
params: url.Values{},
|
||||||
|
}
|
||||||
|
for _, of := range opts {
|
||||||
|
of(&opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err = api.makeRequestContext(ctx, "GET", "/zones?"+opt.params.Encode(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return ZonesResponse{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return ZonesResponse{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, 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 {
|
||||||
|
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("POST", uri, PurgeCacheRequest{true, nil, 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("POST", 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// AvailableZoneRatePlans returns information about all plans available to the specified zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#zone-plan-available-plans
|
||||||
|
func (api *API) AvailableZoneRatePlans(zoneID string) ([]ZoneRatePlan, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/available_rate_plans"
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []ZoneRatePlan{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r AvailableZoneRatePlansResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return []ZoneRatePlan{}, 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-rate-plan-list-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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneSettings returns all of the settings for a given zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#zone-settings-get-all-zone-settings
|
||||||
|
func (api *API) ZoneSettings(zoneID string) (*ZoneSettingResponse, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/settings"
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &ZoneSettingResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateZoneSettings updates the settings for a given zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#zone-settings-edit-zone-settings-info
|
||||||
|
func (api *API) UpdateZoneSettings(zoneID string, settings []ZoneSetting) (*ZoneSettingResponse, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/settings"
|
||||||
|
res, err := api.makeRequest("PATCH", uri, struct {
|
||||||
|
Items []ZoneSetting `json:"items"`
|
||||||
|
}{settings})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &ZoneSettingResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneSSLSettings returns information about SSL setting to the specified zone.
|
||||||
|
//
|
||||||
|
// API reference: https://api.cloudflare.com/#zone-settings-get-ssl-setting
|
||||||
|
func (api *API) ZoneSSLSettings(zoneID string) (ZoneSSLSetting, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/settings/ssl"
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return ZoneSSLSetting{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
var r ZoneSSLSettingResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return ZoneSSLSetting{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FallbackOrigin returns information about the fallback origin for the specified zone.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/ssl/ssl-for-saas/api-calls/#fallback-origin-configuration
|
||||||
|
func (api *API) FallbackOrigin(zoneID string) (FallbackOrigin, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/fallback_origin"
|
||||||
|
res, err := api.makeRequest("GET", uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return FallbackOrigin{}, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r FallbackOriginResponse
|
||||||
|
err = json.Unmarshal(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
return FallbackOrigin{}, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFallbackOrigin updates the fallback origin for a given zone.
|
||||||
|
//
|
||||||
|
// API reference: https://developers.cloudflare.com/ssl/ssl-for-saas/api-calls/#4-example-patch-to-change-fallback-origin
|
||||||
|
func (api *API) UpdateFallbackOrigin(zoneID string, fbo FallbackOrigin) (*FallbackOriginResponse, error) {
|
||||||
|
uri := "/zones/" + zoneID + "/fallback_origin"
|
||||||
|
res, err := api.makeRequest("PATCH", uri, fbo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMakeRequestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &FallbackOriginResponse{}
|
||||||
|
err = json.Unmarshal(res, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errUnmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
27
vendor/golang.org/x/time/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/time/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. 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.
|
||||||
|
* Neither the name of Google Inc. 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
|
||||||
|
OWNER 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.
|
22
vendor/golang.org/x/time/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/time/PATENTS
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
374
vendor/golang.org/x/time/rate/rate.go
generated
vendored
Normal file
374
vendor/golang.org/x/time/rate/rate.go
generated
vendored
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package rate provides a rate limiter.
|
||||||
|
package rate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Limit defines the maximum frequency of some events.
|
||||||
|
// Limit is represented as number of events per second.
|
||||||
|
// A zero Limit allows no events.
|
||||||
|
type Limit float64
|
||||||
|
|
||||||
|
// Inf is the infinite rate limit; it allows all events (even if burst is zero).
|
||||||
|
const Inf = Limit(math.MaxFloat64)
|
||||||
|
|
||||||
|
// Every converts a minimum time interval between events to a Limit.
|
||||||
|
func Every(interval time.Duration) Limit {
|
||||||
|
if interval <= 0 {
|
||||||
|
return Inf
|
||||||
|
}
|
||||||
|
return 1 / Limit(interval.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Limiter controls how frequently events are allowed to happen.
|
||||||
|
// It implements a "token bucket" of size b, initially full and refilled
|
||||||
|
// at rate r tokens per second.
|
||||||
|
// Informally, in any large enough time interval, the Limiter limits the
|
||||||
|
// rate to r tokens per second, with a maximum burst size of b events.
|
||||||
|
// As a special case, if r == Inf (the infinite rate), b is ignored.
|
||||||
|
// See https://en.wikipedia.org/wiki/Token_bucket for more about token buckets.
|
||||||
|
//
|
||||||
|
// The zero value is a valid Limiter, but it will reject all events.
|
||||||
|
// Use NewLimiter to create non-zero Limiters.
|
||||||
|
//
|
||||||
|
// Limiter has three main methods, Allow, Reserve, and Wait.
|
||||||
|
// Most callers should use Wait.
|
||||||
|
//
|
||||||
|
// Each of the three methods consumes a single token.
|
||||||
|
// They differ in their behavior when no token is available.
|
||||||
|
// If no token is available, Allow returns false.
|
||||||
|
// If no token is available, Reserve returns a reservation for a future token
|
||||||
|
// and the amount of time the caller must wait before using it.
|
||||||
|
// If no token is available, Wait blocks until one can be obtained
|
||||||
|
// or its associated context.Context is canceled.
|
||||||
|
//
|
||||||
|
// The methods AllowN, ReserveN, and WaitN consume n tokens.
|
||||||
|
type Limiter struct {
|
||||||
|
limit Limit
|
||||||
|
burst int
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
tokens float64
|
||||||
|
// last is the last time the limiter's tokens field was updated
|
||||||
|
last time.Time
|
||||||
|
// lastEvent is the latest time of a rate-limited event (past or future)
|
||||||
|
lastEvent time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit returns the maximum overall event rate.
|
||||||
|
func (lim *Limiter) Limit() Limit {
|
||||||
|
lim.mu.Lock()
|
||||||
|
defer lim.mu.Unlock()
|
||||||
|
return lim.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Burst returns the maximum burst size. Burst is the maximum number of tokens
|
||||||
|
// that can be consumed in a single call to Allow, Reserve, or Wait, so higher
|
||||||
|
// Burst values allow more events to happen at once.
|
||||||
|
// A zero Burst allows no events, unless limit == Inf.
|
||||||
|
func (lim *Limiter) Burst() int {
|
||||||
|
return lim.burst
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLimiter returns a new Limiter that allows events up to rate r and permits
|
||||||
|
// bursts of at most b tokens.
|
||||||
|
func NewLimiter(r Limit, b int) *Limiter {
|
||||||
|
return &Limiter{
|
||||||
|
limit: r,
|
||||||
|
burst: b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow is shorthand for AllowN(time.Now(), 1).
|
||||||
|
func (lim *Limiter) Allow() bool {
|
||||||
|
return lim.AllowN(time.Now(), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowN reports whether n events may happen at time now.
|
||||||
|
// Use this method if you intend to drop / skip events that exceed the rate limit.
|
||||||
|
// Otherwise use Reserve or Wait.
|
||||||
|
func (lim *Limiter) AllowN(now time.Time, n int) bool {
|
||||||
|
return lim.reserveN(now, n, 0).ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Reservation holds information about events that are permitted by a Limiter to happen after a delay.
|
||||||
|
// A Reservation may be canceled, which may enable the Limiter to permit additional events.
|
||||||
|
type Reservation struct {
|
||||||
|
ok bool
|
||||||
|
lim *Limiter
|
||||||
|
tokens int
|
||||||
|
timeToAct time.Time
|
||||||
|
// This is the Limit at reservation time, it can change later.
|
||||||
|
limit Limit
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK returns whether the limiter can provide the requested number of tokens
|
||||||
|
// within the maximum wait time. If OK is false, Delay returns InfDuration, and
|
||||||
|
// Cancel does nothing.
|
||||||
|
func (r *Reservation) OK() bool {
|
||||||
|
return r.ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay is shorthand for DelayFrom(time.Now()).
|
||||||
|
func (r *Reservation) Delay() time.Duration {
|
||||||
|
return r.DelayFrom(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfDuration is the duration returned by Delay when a Reservation is not OK.
|
||||||
|
const InfDuration = time.Duration(1<<63 - 1)
|
||||||
|
|
||||||
|
// DelayFrom returns the duration for which the reservation holder must wait
|
||||||
|
// before taking the reserved action. Zero duration means act immediately.
|
||||||
|
// InfDuration means the limiter cannot grant the tokens requested in this
|
||||||
|
// Reservation within the maximum wait time.
|
||||||
|
func (r *Reservation) DelayFrom(now time.Time) time.Duration {
|
||||||
|
if !r.ok {
|
||||||
|
return InfDuration
|
||||||
|
}
|
||||||
|
delay := r.timeToAct.Sub(now)
|
||||||
|
if delay < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return delay
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel is shorthand for CancelAt(time.Now()).
|
||||||
|
func (r *Reservation) Cancel() {
|
||||||
|
r.CancelAt(time.Now())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelAt indicates that the reservation holder will not perform the reserved action
|
||||||
|
// and reverses the effects of this Reservation on the rate limit as much as possible,
|
||||||
|
// considering that other reservations may have already been made.
|
||||||
|
func (r *Reservation) CancelAt(now time.Time) {
|
||||||
|
if !r.ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.lim.mu.Lock()
|
||||||
|
defer r.lim.mu.Unlock()
|
||||||
|
|
||||||
|
if r.lim.limit == Inf || r.tokens == 0 || r.timeToAct.Before(now) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate tokens to restore
|
||||||
|
// The duration between lim.lastEvent and r.timeToAct tells us how many tokens were reserved
|
||||||
|
// after r was obtained. These tokens should not be restored.
|
||||||
|
restoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct))
|
||||||
|
if restoreTokens <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// advance time to now
|
||||||
|
now, _, tokens := r.lim.advance(now)
|
||||||
|
// calculate new number of tokens
|
||||||
|
tokens += restoreTokens
|
||||||
|
if burst := float64(r.lim.burst); tokens > burst {
|
||||||
|
tokens = burst
|
||||||
|
}
|
||||||
|
// update state
|
||||||
|
r.lim.last = now
|
||||||
|
r.lim.tokens = tokens
|
||||||
|
if r.timeToAct == r.lim.lastEvent {
|
||||||
|
prevEvent := r.timeToAct.Add(r.limit.durationFromTokens(float64(-r.tokens)))
|
||||||
|
if !prevEvent.Before(now) {
|
||||||
|
r.lim.lastEvent = prevEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve is shorthand for ReserveN(time.Now(), 1).
|
||||||
|
func (lim *Limiter) Reserve() *Reservation {
|
||||||
|
return lim.ReserveN(time.Now(), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReserveN returns a Reservation that indicates how long the caller must wait before n events happen.
|
||||||
|
// The Limiter takes this Reservation into account when allowing future events.
|
||||||
|
// ReserveN returns false if n exceeds the Limiter's burst size.
|
||||||
|
// Usage example:
|
||||||
|
// r := lim.ReserveN(time.Now(), 1)
|
||||||
|
// if !r.OK() {
|
||||||
|
// // Not allowed to act! Did you remember to set lim.burst to be > 0 ?
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// time.Sleep(r.Delay())
|
||||||
|
// Act()
|
||||||
|
// Use this method if you wish to wait and slow down in accordance with the rate limit without dropping events.
|
||||||
|
// If you need to respect a deadline or cancel the delay, use Wait instead.
|
||||||
|
// To drop or skip events exceeding rate limit, use Allow instead.
|
||||||
|
func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation {
|
||||||
|
r := lim.reserveN(now, n, InfDuration)
|
||||||
|
return &r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait is shorthand for WaitN(ctx, 1).
|
||||||
|
func (lim *Limiter) Wait(ctx context.Context) (err error) {
|
||||||
|
return lim.WaitN(ctx, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitN blocks until lim permits n events to happen.
|
||||||
|
// It returns an error if n exceeds the Limiter's burst size, the Context is
|
||||||
|
// canceled, or the expected wait time exceeds the Context's Deadline.
|
||||||
|
// The burst limit is ignored if the rate limit is Inf.
|
||||||
|
func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) {
|
||||||
|
if n > lim.burst && lim.limit != Inf {
|
||||||
|
return fmt.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", n, lim.burst)
|
||||||
|
}
|
||||||
|
// Check if ctx is already cancelled
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
// Determine wait limit
|
||||||
|
now := time.Now()
|
||||||
|
waitLimit := InfDuration
|
||||||
|
if deadline, ok := ctx.Deadline(); ok {
|
||||||
|
waitLimit = deadline.Sub(now)
|
||||||
|
}
|
||||||
|
// Reserve
|
||||||
|
r := lim.reserveN(now, n, waitLimit)
|
||||||
|
if !r.ok {
|
||||||
|
return fmt.Errorf("rate: Wait(n=%d) would exceed context deadline", n)
|
||||||
|
}
|
||||||
|
// Wait if necessary
|
||||||
|
delay := r.DelayFrom(now)
|
||||||
|
if delay == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t := time.NewTimer(delay)
|
||||||
|
defer t.Stop()
|
||||||
|
select {
|
||||||
|
case <-t.C:
|
||||||
|
// We can proceed.
|
||||||
|
return nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
// Context was canceled before we could proceed. Cancel the
|
||||||
|
// reservation, which may permit other events to proceed sooner.
|
||||||
|
r.Cancel()
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLimit is shorthand for SetLimitAt(time.Now(), newLimit).
|
||||||
|
func (lim *Limiter) SetLimit(newLimit Limit) {
|
||||||
|
lim.SetLimitAt(time.Now(), newLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLimitAt sets a new Limit for the limiter. The new Limit, and Burst, may be violated
|
||||||
|
// or underutilized by those which reserved (using Reserve or Wait) but did not yet act
|
||||||
|
// before SetLimitAt was called.
|
||||||
|
func (lim *Limiter) SetLimitAt(now time.Time, newLimit Limit) {
|
||||||
|
lim.mu.Lock()
|
||||||
|
defer lim.mu.Unlock()
|
||||||
|
|
||||||
|
now, _, tokens := lim.advance(now)
|
||||||
|
|
||||||
|
lim.last = now
|
||||||
|
lim.tokens = tokens
|
||||||
|
lim.limit = newLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
// reserveN is a helper method for AllowN, ReserveN, and WaitN.
|
||||||
|
// maxFutureReserve specifies the maximum reservation wait duration allowed.
|
||||||
|
// reserveN returns Reservation, not *Reservation, to avoid allocation in AllowN and WaitN.
|
||||||
|
func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
|
||||||
|
lim.mu.Lock()
|
||||||
|
|
||||||
|
if lim.limit == Inf {
|
||||||
|
lim.mu.Unlock()
|
||||||
|
return Reservation{
|
||||||
|
ok: true,
|
||||||
|
lim: lim,
|
||||||
|
tokens: n,
|
||||||
|
timeToAct: now,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
now, last, tokens := lim.advance(now)
|
||||||
|
|
||||||
|
// Calculate the remaining number of tokens resulting from the request.
|
||||||
|
tokens -= float64(n)
|
||||||
|
|
||||||
|
// Calculate the wait duration
|
||||||
|
var waitDuration time.Duration
|
||||||
|
if tokens < 0 {
|
||||||
|
waitDuration = lim.limit.durationFromTokens(-tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decide result
|
||||||
|
ok := n <= lim.burst && waitDuration <= maxFutureReserve
|
||||||
|
|
||||||
|
// Prepare reservation
|
||||||
|
r := Reservation{
|
||||||
|
ok: ok,
|
||||||
|
lim: lim,
|
||||||
|
limit: lim.limit,
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
r.tokens = n
|
||||||
|
r.timeToAct = now.Add(waitDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
if ok {
|
||||||
|
lim.last = now
|
||||||
|
lim.tokens = tokens
|
||||||
|
lim.lastEvent = r.timeToAct
|
||||||
|
} else {
|
||||||
|
lim.last = last
|
||||||
|
}
|
||||||
|
|
||||||
|
lim.mu.Unlock()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// advance calculates and returns an updated state for lim resulting from the passage of time.
|
||||||
|
// lim is not changed.
|
||||||
|
func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) {
|
||||||
|
last := lim.last
|
||||||
|
if now.Before(last) {
|
||||||
|
last = now
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid making delta overflow below when last is very old.
|
||||||
|
maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens)
|
||||||
|
elapsed := now.Sub(last)
|
||||||
|
if elapsed > maxElapsed {
|
||||||
|
elapsed = maxElapsed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the new number of tokens, due to time that passed.
|
||||||
|
delta := lim.limit.tokensFromDuration(elapsed)
|
||||||
|
tokens := lim.tokens + delta
|
||||||
|
if burst := float64(lim.burst); tokens > burst {
|
||||||
|
tokens = burst
|
||||||
|
}
|
||||||
|
|
||||||
|
return now, last, tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
// durationFromTokens is a unit conversion function from the number of tokens to the duration
|
||||||
|
// of time it takes to accumulate them at a rate of limit tokens per second.
|
||||||
|
func (limit Limit) durationFromTokens(tokens float64) time.Duration {
|
||||||
|
seconds := tokens / float64(limit)
|
||||||
|
return time.Nanosecond * time.Duration(1e9*seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokensFromDuration is a unit conversion function from a time duration to the number of tokens
|
||||||
|
// which could be accumulated during that duration at a rate of limit tokens per second.
|
||||||
|
func (limit Limit) tokensFromDuration(d time.Duration) float64 {
|
||||||
|
return d.Seconds() * float64(limit)
|
||||||
|
}
|
12
vendor/vendor.json
vendored
12
vendor/vendor.json
vendored
@ -56,6 +56,12 @@
|
|||||||
"revision": "165db2f241fd235aec29ba6d9b1ccd5f1c14637c",
|
"revision": "165db2f241fd235aec29ba6d9b1ccd5f1c14637c",
|
||||||
"revisionTime": "2015-01-22T07:26:53Z"
|
"revisionTime": "2015-01-22T07:26:53Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "WILMZlCPSNbyMzYRNo/RkDcUH2M=",
|
||||||
|
"path": "github.com/cloudflare/cloudflare-go",
|
||||||
|
"revision": "a80f83b9add9d67ca4098ccbf42cd865ebb36ffb",
|
||||||
|
"revisionTime": "2019-09-16T15:18:08Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=",
|
"checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=",
|
||||||
"path": "github.com/davecgh/go-spew/spew",
|
"path": "github.com/davecgh/go-spew/spew",
|
||||||
@ -844,6 +850,12 @@
|
|||||||
"revision": "31e7599a6c37728c25ca34167be099d072ad335d",
|
"revision": "31e7599a6c37728c25ca34167be099d072ad335d",
|
||||||
"revisionTime": "2019-04-05T05:38:27Z"
|
"revisionTime": "2019-04-05T05:38:27Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "7Ev/X4Xe8P3961myez/hBKO05ig=",
|
||||||
|
"path": "golang.org/x/time/rate",
|
||||||
|
"revision": "9d24e82272b4f38b78bc8cff74fa936d31ccd8ef",
|
||||||
|
"revisionTime": "2019-02-15T22:48:40Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "CEFTYXtWmgSh+3Ik1NmDaJcz4E0=",
|
"checksumSHA1": "CEFTYXtWmgSh+3Ik1NmDaJcz4E0=",
|
||||||
"path": "gopkg.in/check.v1",
|
"path": "gopkg.in/check.v1",
|
||||||
|
Loading…
Reference in New Issue
Block a user