Merge pull request #5814 from hashicorp/provider-fastly-v1
provider/fastly: V1 of the Fastly Provider
This commit is contained in:
commit
c9293cc832
|
@ -203,6 +203,10 @@
|
||||||
"ImportPath": "github.com/Ensighten/udnssdk",
|
"ImportPath": "github.com/Ensighten/udnssdk",
|
||||||
"Rev": "0290933f5e8afd933f2823fce32bf2847e6ea603"
|
"Rev": "0290933f5e8afd933f2823fce32bf2847e6ea603"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/ajg/form",
|
||||||
|
"Rev": "c9e1c3ae1f869d211cdaa085d23c6af2f5f83866"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/apparentlymart/go-cidr/cidr",
|
"ImportPath": "github.com/apparentlymart/go-cidr/cidr",
|
||||||
"Rev": "a3ebdb999b831ecb6ab8a226e31b07b2b9061c47"
|
"Rev": "a3ebdb999b831ecb6ab8a226e31b07b2b9061c47"
|
||||||
|
@ -1140,6 +1144,11 @@
|
||||||
"ImportPath": "github.com/satori/go.uuid",
|
"ImportPath": "github.com/satori/go.uuid",
|
||||||
"Rev": "d41af8bb6a7704f00bc3b7cba9355ae6a5a80048"
|
"Rev": "d41af8bb6a7704f00bc3b7cba9355ae6a5a80048"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/sethvargo/go-fastly",
|
||||||
|
"Rev": "382fee1e5e1adf3cc112fadf4f0a8a98e269ea3c"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/soniah/dnsmadeeasy",
|
"ImportPath": "github.com/soniah/dnsmadeeasy",
|
||||||
"Comment": "v1.1-2-g5578a8c",
|
"Comment": "v1.1-2-g5578a8c",
|
||||||
|
@ -1378,3 +1387,5 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/builtin/providers/fastly"
|
||||||
|
"github.com/hashicorp/terraform/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
plugin.Serve(&plugin.ServeOpts{
|
||||||
|
ProviderFunc: fastly.Provider,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package main
|
|
@ -0,0 +1,31 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
gofastly "github.com/sethvargo/go-fastly"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
ApiKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FastlyClient struct {
|
||||||
|
conn *gofastly.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Client() (interface{}, error) {
|
||||||
|
var client FastlyClient
|
||||||
|
|
||||||
|
if c.ApiKey == "" {
|
||||||
|
return nil, fmt.Errorf("[Err] No API key for Fastly")
|
||||||
|
}
|
||||||
|
|
||||||
|
fconn, err := gofastly.NewClient(c.ApiKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client.conn = fconn
|
||||||
|
return &client, nil
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider returns a terraform.ResourceProvider.
|
||||||
|
func Provider() terraform.ResourceProvider {
|
||||||
|
return &schema.Provider{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"api_key": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
|
||||||
|
"FASTLY_API_KEY",
|
||||||
|
}, nil),
|
||||||
|
Description: "Fastly API Key from https://app.fastly.com/#account",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
|
"fastly_service_v1": resourceServiceV1(),
|
||||||
|
},
|
||||||
|
|
||||||
|
ConfigureFunc: providerConfigure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
||||||
|
config := Config{
|
||||||
|
ApiKey: d.Get("api_key").(string),
|
||||||
|
}
|
||||||
|
return config.Client()
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testAccProviders map[string]terraform.ResourceProvider
|
||||||
|
var testAccProvider *schema.Provider
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
testAccProvider = Provider().(*schema.Provider)
|
||||||
|
testAccProviders = map[string]terraform.ResourceProvider{
|
||||||
|
"fastly": testAccProvider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider(t *testing.T) {
|
||||||
|
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider_impl(t *testing.T) {
|
||||||
|
var _ terraform.ResourceProvider = Provider()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccPreCheck(t *testing.T) {
|
||||||
|
if v := os.Getenv("FASTLY_API_KEY"); v == "" {
|
||||||
|
t.Fatal("FASTLY_API_KEY must be set for acceptance tests")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,604 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
gofastly "github.com/sethvargo/go-fastly"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fastlyNoServiceFoundErr = errors.New("No matching Fastly Service found")
|
||||||
|
|
||||||
|
func resourceServiceV1() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceServiceV1Create,
|
||||||
|
Read: resourceServiceV1Read,
|
||||||
|
Update: resourceServiceV1Update,
|
||||||
|
Delete: resourceServiceV1Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
Description: "Unique name for this Service",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Active Version represents the currently activated version in Fastly. In
|
||||||
|
// Terraform, we abstract this number away from the users and manage
|
||||||
|
// creating and activating. It's used internally, but also exported for
|
||||||
|
// users to see.
|
||||||
|
"active_version": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"domain": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Required: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
Description: "The domain that this Service will respond to",
|
||||||
|
},
|
||||||
|
|
||||||
|
"comment": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"default_ttl": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Default: 3600,
|
||||||
|
Description: "The default Time-to-live (TTL) for the version",
|
||||||
|
},
|
||||||
|
|
||||||
|
"default_host": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Description: "The default hostname for the version",
|
||||||
|
},
|
||||||
|
|
||||||
|
"backend": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Required: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
// required fields
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
Description: "A name for this Backend",
|
||||||
|
},
|
||||||
|
"address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
Description: "An IPv4, hostname, or IPv6 address for the Backend",
|
||||||
|
},
|
||||||
|
// Optional fields, defaults where they exist
|
||||||
|
"auto_loadbalance": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: true,
|
||||||
|
Description: "Should this Backend be load balanced",
|
||||||
|
},
|
||||||
|
"between_bytes_timeout": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Default: 10000,
|
||||||
|
Description: "How long to wait between bytes in milliseconds",
|
||||||
|
},
|
||||||
|
"connect_timeout": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Default: 1000,
|
||||||
|
Description: "How long to wait for a timeout in milliseconds",
|
||||||
|
},
|
||||||
|
"error_threshold": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Default: 0,
|
||||||
|
Description: "Number of errors to allow before the Backend is marked as down",
|
||||||
|
},
|
||||||
|
"first_byte_timeout": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Default: 15000,
|
||||||
|
Description: "How long to wait for the first bytes in milliseconds",
|
||||||
|
},
|
||||||
|
"max_conn": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Default: 200,
|
||||||
|
Description: "Maximum number of connections for this Backend",
|
||||||
|
},
|
||||||
|
"port": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Default: 80,
|
||||||
|
Description: "The port number Backend responds on. Default 80",
|
||||||
|
},
|
||||||
|
"ssl_check_cert": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: true,
|
||||||
|
Description: "Be strict on checking SSL certs",
|
||||||
|
},
|
||||||
|
// UseSSL is something we want to support in the future, but
|
||||||
|
// requires SSL setup we don't yet have
|
||||||
|
// TODO: Provide all SSL fields from https://docs.fastly.com/api/config#backend
|
||||||
|
// "use_ssl": &schema.Schema{
|
||||||
|
// Type: schema.TypeBool,
|
||||||
|
// Optional: true,
|
||||||
|
// Default: false,
|
||||||
|
// Description: "Whether or not to use SSL to reach the Backend",
|
||||||
|
// },
|
||||||
|
"weight": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Default: 100,
|
||||||
|
Description: "How long to wait for the first bytes in milliseconds",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"force_destroy": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceServiceV1Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
conn := meta.(*FastlyClient).conn
|
||||||
|
service, err := conn.CreateService(&gofastly.CreateServiceInput{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
Comment: "Managed by Terraform",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(service.ID)
|
||||||
|
return resourceServiceV1Update(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
conn := meta.(*FastlyClient).conn
|
||||||
|
|
||||||
|
// Update Name. No new verions is required for this
|
||||||
|
if d.HasChange("name") {
|
||||||
|
_, err := conn.UpdateService(&gofastly.UpdateServiceInput{
|
||||||
|
ID: d.Id(),
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once activated, Versions are locked and become immutable. This is true for
|
||||||
|
// versions that are no longer active. For Domains, Backends, DefaultHost and
|
||||||
|
// DefaultTTL, a new Version must be created first, and updates posted to that
|
||||||
|
// Version. Loop these attributes and determine if we need to create a new version first
|
||||||
|
var needsChange bool
|
||||||
|
for _, v := range []string{"domain", "backend", "default_host", "default_ttl"} {
|
||||||
|
if d.HasChange(v) {
|
||||||
|
needsChange = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if needsChange {
|
||||||
|
latestVersion := d.Get("active_version").(string)
|
||||||
|
if latestVersion == "" {
|
||||||
|
// If the service was just created, there is an empty Version 1 available
|
||||||
|
// that is unlocked and can be updated
|
||||||
|
latestVersion = "1"
|
||||||
|
} else {
|
||||||
|
// Clone the latest version, giving us an unlocked version we can modify
|
||||||
|
log.Printf("[DEBUG] Creating clone of version (%s) for updates", latestVersion)
|
||||||
|
newVersion, err := conn.CloneVersion(&gofastly.CloneVersionInput{
|
||||||
|
Service: d.Id(),
|
||||||
|
Version: latestVersion,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The new version number is named "Number", but it's actually a string
|
||||||
|
latestVersion = newVersion.Number
|
||||||
|
|
||||||
|
// New versions are not immediately found in the API, or are not
|
||||||
|
// immediately mutable, so we need to sleep a few and let Fastly ready
|
||||||
|
// itself. Typically, 7 seconds is enough
|
||||||
|
log.Printf("[DEBUG] Sleeping 7 seconds to allow Fastly Version to be available")
|
||||||
|
time.Sleep(7 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update general settings
|
||||||
|
if d.HasChange("default_host") || d.HasChange("default_ttl") {
|
||||||
|
opts := gofastly.UpdateSettingsInput{
|
||||||
|
Service: d.Id(),
|
||||||
|
Version: latestVersion,
|
||||||
|
// default_ttl has the same default value of 3600 that is provided by
|
||||||
|
// the Fastly API, so it's safe to include here
|
||||||
|
DefaultTTL: uint(d.Get("default_ttl").(int)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if attr, ok := d.GetOk("default_host"); ok {
|
||||||
|
opts.DefaultHost = attr.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Update Settings opts: %#v", opts)
|
||||||
|
_, err := conn.UpdateSettings(&opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find differences in domains
|
||||||
|
if d.HasChange("domain") {
|
||||||
|
// Note: we don't utilize the PUT endpoint to update a Domain, we simply
|
||||||
|
// destroy it and create a new one. This is how Terraform works with nested
|
||||||
|
// sub resources, we only get the full diff not a partial set item diff.
|
||||||
|
// Because this is done on a new version of the configuration, this is
|
||||||
|
// considered safe
|
||||||
|
od, nd := d.GetChange("domain")
|
||||||
|
if od == nil {
|
||||||
|
od = new(schema.Set)
|
||||||
|
}
|
||||||
|
if nd == nil {
|
||||||
|
nd = new(schema.Set)
|
||||||
|
}
|
||||||
|
|
||||||
|
ods := od.(*schema.Set)
|
||||||
|
nds := nd.(*schema.Set)
|
||||||
|
|
||||||
|
remove := ods.Difference(nds).List()
|
||||||
|
add := nds.Difference(ods).List()
|
||||||
|
|
||||||
|
// Delete removed domains
|
||||||
|
for _, dRaw := range remove {
|
||||||
|
df := dRaw.(map[string]interface{})
|
||||||
|
opts := gofastly.DeleteDomainInput{
|
||||||
|
Service: d.Id(),
|
||||||
|
Version: latestVersion,
|
||||||
|
Name: df["name"].(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Fastly Domain Removal opts: %#v", opts)
|
||||||
|
err := conn.DeleteDomain(&opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST new Domains
|
||||||
|
for _, dRaw := range add {
|
||||||
|
df := dRaw.(map[string]interface{})
|
||||||
|
opts := gofastly.CreateDomainInput{
|
||||||
|
Service: d.Id(),
|
||||||
|
Version: latestVersion,
|
||||||
|
Name: df["name"].(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := df["comment"]; ok {
|
||||||
|
opts.Comment = v.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Fastly Domain Addition opts: %#v", opts)
|
||||||
|
_, err := conn.CreateDomain(&opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find difference in backends
|
||||||
|
if d.HasChange("backend") {
|
||||||
|
// POST new Backends
|
||||||
|
// Note: we don't utilize the PUT endpoint to update a Backend, we simply
|
||||||
|
// destroy it and create a new one. This is how Terraform works with nested
|
||||||
|
// sub resources, we only get the full diff not a partial set item diff.
|
||||||
|
// Because this is done on a new version of the configuration, this is
|
||||||
|
// considered safe
|
||||||
|
ob, nb := d.GetChange("backend")
|
||||||
|
if ob == nil {
|
||||||
|
ob = new(schema.Set)
|
||||||
|
}
|
||||||
|
if nb == nil {
|
||||||
|
nb = new(schema.Set)
|
||||||
|
}
|
||||||
|
|
||||||
|
obs := ob.(*schema.Set)
|
||||||
|
nbs := nb.(*schema.Set)
|
||||||
|
removeBackends := obs.Difference(nbs).List()
|
||||||
|
addBackends := nbs.Difference(obs).List()
|
||||||
|
|
||||||
|
// DELETE old Backends
|
||||||
|
for _, bRaw := range removeBackends {
|
||||||
|
bf := bRaw.(map[string]interface{})
|
||||||
|
opts := gofastly.DeleteBackendInput{
|
||||||
|
Service: d.Id(),
|
||||||
|
Version: latestVersion,
|
||||||
|
Name: bf["name"].(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Fastly Backend Removal opts: %#v", opts)
|
||||||
|
err := conn.DeleteBackend(&opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dRaw := range addBackends {
|
||||||
|
df := dRaw.(map[string]interface{})
|
||||||
|
opts := gofastly.CreateBackendInput{
|
||||||
|
Service: d.Id(),
|
||||||
|
Version: latestVersion,
|
||||||
|
Name: df["name"].(string),
|
||||||
|
Address: df["address"].(string),
|
||||||
|
AutoLoadbalance: df["auto_loadbalance"].(bool),
|
||||||
|
SSLCheckCert: df["ssl_check_cert"].(bool),
|
||||||
|
Port: uint(df["port"].(int)),
|
||||||
|
BetweenBytesTimeout: uint(df["between_bytes_timeout"].(int)),
|
||||||
|
ConnectTimeout: uint(df["connect_timeout"].(int)),
|
||||||
|
ErrorThreshold: uint(df["error_threshold"].(int)),
|
||||||
|
FirstByteTimeout: uint(df["first_byte_timeout"].(int)),
|
||||||
|
MaxConn: uint(df["max_conn"].(int)),
|
||||||
|
Weight: uint(df["weight"].(int)),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create Backend Opts: %#v", opts)
|
||||||
|
_, err := conn.CreateBackend(&opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate version
|
||||||
|
log.Printf("[DEBUG] Validating Fastly Service (%s), Version (%s)", d.Id(), latestVersion)
|
||||||
|
valid, msg, err := conn.ValidateVersion(&gofastly.ValidateVersionInput{
|
||||||
|
Service: d.Id(),
|
||||||
|
Version: latestVersion,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[ERR] Error checking validation: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
return fmt.Errorf("[ERR] Invalid configuration for Fastly Service (%s): %s", d.Id(), msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Activating Fastly Service (%s), Version (%s)", d.Id(), latestVersion)
|
||||||
|
_, err = conn.ActivateVersion(&gofastly.ActivateVersionInput{
|
||||||
|
Service: d.Id(),
|
||||||
|
Version: latestVersion,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[ERR] Error activating version (%s): %s", latestVersion, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only if the version is valid and activated do we set the active_version.
|
||||||
|
// This prevents us from getting stuck in cloning an invalid version
|
||||||
|
d.Set("active_version", latestVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceServiceV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceServiceV1Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
conn := meta.(*FastlyClient).conn
|
||||||
|
|
||||||
|
// Find the Service. Discard the service because we need the ServiceDetails,
|
||||||
|
// not just a Service record
|
||||||
|
_, err := findService(d.Id(), meta)
|
||||||
|
if err != nil {
|
||||||
|
switch err {
|
||||||
|
case fastlyNoServiceFoundErr:
|
||||||
|
log.Printf("[WARN] %s for ID (%s)", err, d.Id())
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := conn.GetServiceDetails(&gofastly.GetServiceInput{
|
||||||
|
ID: d.Id(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("name", s.Name)
|
||||||
|
d.Set("active_version", s.ActiveVersion.Number)
|
||||||
|
|
||||||
|
// If CreateService succeeds, but initial updates to the Service fail, we'll
|
||||||
|
// have an empty ActiveService version (no version is active, so we can't
|
||||||
|
// query for information on it)
|
||||||
|
if s.ActiveVersion.Number != "" {
|
||||||
|
settingsOpts := gofastly.GetSettingsInput{
|
||||||
|
Service: d.Id(),
|
||||||
|
Version: s.ActiveVersion.Number,
|
||||||
|
}
|
||||||
|
if settings, err := conn.GetSettings(&settingsOpts); err == nil {
|
||||||
|
d.Set("default_host", settings.DefaultHost)
|
||||||
|
d.Set("default_ttl", settings.DefaultTTL)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("[ERR] Error looking up Version settings for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: update go-fastly to support an ActiveVersion struct, which contains
|
||||||
|
// domain and backend info in the response. Here we do 2 additional queries
|
||||||
|
// to find out that info
|
||||||
|
domainList, err := conn.ListDomains(&gofastly.ListDomainsInput{
|
||||||
|
Service: d.Id(),
|
||||||
|
Version: s.ActiveVersion.Number,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[ERR] Error looking up Domains for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh Domains
|
||||||
|
dl := flattenDomains(domainList)
|
||||||
|
|
||||||
|
if err := d.Set("domain", dl); err != nil {
|
||||||
|
log.Printf("[WARN] Error setting Domains for (%s): %s", d.Id(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh Backends
|
||||||
|
backendList, err := conn.ListBackends(&gofastly.ListBackendsInput{
|
||||||
|
Service: d.Id(),
|
||||||
|
Version: s.ActiveVersion.Number,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[ERR] Error looking up Backends for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bl := flattenBackends(backendList)
|
||||||
|
|
||||||
|
if err := d.Set("backend", bl); err != nil {
|
||||||
|
log.Printf("[WARN] Error setting Backends for (%s): %s", d.Id(), err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("[DEBUG] Active Version for Service (%s) is empty, no state to refresh", d.Id())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceServiceV1Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
conn := meta.(*FastlyClient).conn
|
||||||
|
|
||||||
|
// Fastly will fail to delete any service with an Active Version.
|
||||||
|
// If `force_destroy` is given, we deactivate the active version and then send
|
||||||
|
// the DELETE call
|
||||||
|
if d.Get("force_destroy").(bool) {
|
||||||
|
s, err := conn.GetServiceDetails(&gofastly.GetServiceInput{
|
||||||
|
ID: d.Id(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.ActiveVersion.Number != "" {
|
||||||
|
_, err := conn.DeactivateVersion(&gofastly.DeactivateVersionInput{
|
||||||
|
Service: d.Id(),
|
||||||
|
Version: s.ActiveVersion.Number,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := conn.DeleteService(&gofastly.DeleteServiceInput{
|
||||||
|
ID: d.Id(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = findService(d.Id(), meta)
|
||||||
|
if err != nil {
|
||||||
|
switch err {
|
||||||
|
// we expect no records to be found here
|
||||||
|
case fastlyNoServiceFoundErr:
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// findService above returned something and nil error, but shouldn't have
|
||||||
|
return fmt.Errorf("[WARN] Tried deleting Service (%s), but was still found", d.Id())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenDomains(list []*gofastly.Domain) []map[string]interface{} {
|
||||||
|
dl := make([]map[string]interface{}, 0, len(list))
|
||||||
|
|
||||||
|
for _, d := range list {
|
||||||
|
dl = append(dl, map[string]interface{}{
|
||||||
|
"name": d.Name,
|
||||||
|
"comment": d.Comment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return dl
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenBackends(backendList []*gofastly.Backend) []map[string]interface{} {
|
||||||
|
var bl []map[string]interface{}
|
||||||
|
for _, b := range backendList {
|
||||||
|
// Convert Backend to a map for saving to state.
|
||||||
|
nb := map[string]interface{}{
|
||||||
|
"name": b.Name,
|
||||||
|
"address": b.Address,
|
||||||
|
"auto_loadbalance": b.AutoLoadbalance,
|
||||||
|
"between_bytes_timeout": int(b.BetweenBytesTimeout),
|
||||||
|
"connect_timeout": int(b.ConnectTimeout),
|
||||||
|
"error_threshold": int(b.ErrorThreshold),
|
||||||
|
"first_byte_timeout": int(b.FirstByteTimeout),
|
||||||
|
"max_conn": int(b.MaxConn),
|
||||||
|
"port": int(b.Port),
|
||||||
|
"ssl_check_cert": b.SSLCheckCert,
|
||||||
|
"weight": int(b.Weight),
|
||||||
|
}
|
||||||
|
|
||||||
|
bl = append(bl, nb)
|
||||||
|
}
|
||||||
|
return bl
|
||||||
|
}
|
||||||
|
|
||||||
|
// findService finds a Fastly Service via the ListServices endpoint, returning
|
||||||
|
// the Service if found.
|
||||||
|
//
|
||||||
|
// Fastly API does not include any "deleted_at" type parameter to indicate
|
||||||
|
// that a Service has been deleted. GET requests to a deleted Service will
|
||||||
|
// return 200 OK and have the full output of the Service for an unknown time
|
||||||
|
// (days, in my testing). In order to determine if a Service is deleted, we
|
||||||
|
// need to hit /service and loop the returned Services, searching for the one
|
||||||
|
// in question. This endpoint only returns active or "alive" services. If the
|
||||||
|
// Service is not included, then it's "gone"
|
||||||
|
//
|
||||||
|
// Returns a fastlyNoServiceFoundErr error if the Service is not found in the
|
||||||
|
// ListServices response.
|
||||||
|
func findService(id string, meta interface{}) (*gofastly.Service, error) {
|
||||||
|
conn := meta.(*FastlyClient).conn
|
||||||
|
|
||||||
|
l, err := conn.ListServices(&gofastly.ListServicesInput{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("[WARN] Error listing servcies when deleting Fastly Service (%s): %s", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range l {
|
||||||
|
if s.ID == id {
|
||||||
|
log.Printf("[DEBUG] Found Service (%s)", id)
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fastlyNoServiceFoundErr
|
||||||
|
}
|
|
@ -0,0 +1,458 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/acctest"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
gofastly "github.com/sethvargo/go-fastly"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResourceFastlyFlattenDomains(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
remote []*gofastly.Domain
|
||||||
|
local []map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
remote: []*gofastly.Domain{
|
||||||
|
&gofastly.Domain{
|
||||||
|
Name: "test.notexample.com",
|
||||||
|
Comment: "not comment",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
local: []map[string]interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "test.notexample.com",
|
||||||
|
"comment": "not comment",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
remote: []*gofastly.Domain{
|
||||||
|
&gofastly.Domain{
|
||||||
|
Name: "test.notexample.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
local: []map[string]interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "test.notexample.com",
|
||||||
|
"comment": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
out := flattenDomains(c.remote)
|
||||||
|
if !reflect.DeepEqual(out, c.local) {
|
||||||
|
t.Fatalf("Error matching:\nexpected: %#v\ngot: %#v", c.local, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceFastlyFlattenBackend(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
remote []*gofastly.Backend
|
||||||
|
local []map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
remote: []*gofastly.Backend{
|
||||||
|
&gofastly.Backend{
|
||||||
|
Name: "test.notexample.com",
|
||||||
|
Address: "www.notexample.com",
|
||||||
|
Port: uint(80),
|
||||||
|
AutoLoadbalance: true,
|
||||||
|
BetweenBytesTimeout: uint(10000),
|
||||||
|
ConnectTimeout: uint(1000),
|
||||||
|
ErrorThreshold: uint(0),
|
||||||
|
FirstByteTimeout: uint(15000),
|
||||||
|
MaxConn: uint(200),
|
||||||
|
SSLCheckCert: true,
|
||||||
|
Weight: uint(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
local: []map[string]interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "test.notexample.com",
|
||||||
|
"address": "www.notexample.com",
|
||||||
|
"port": 80,
|
||||||
|
"auto_loadbalance": true,
|
||||||
|
"between_bytes_timeout": 10000,
|
||||||
|
"connect_timeout": 1000,
|
||||||
|
"error_threshold": 0,
|
||||||
|
"first_byte_timeout": 15000,
|
||||||
|
"max_conn": 200,
|
||||||
|
"ssl_check_cert": true,
|
||||||
|
"weight": 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
out := flattenBackends(c.remote)
|
||||||
|
if !reflect.DeepEqual(out, c.local) {
|
||||||
|
t.Fatalf("Error matching:\nexpected: %#v\ngot: %#v", c.local, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccFastlyServiceV1_updateDomain(t *testing.T) {
|
||||||
|
var service gofastly.ServiceDetail
|
||||||
|
name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||||
|
nameUpdate := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||||
|
domainName1 := fmt.Sprintf("%s.notadomain.com", acctest.RandString(10))
|
||||||
|
domainName2 := fmt.Sprintf("%s.notadomain.com", acctest.RandString(10))
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckServiceV1Destroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccServiceV1Config(name, domainName1),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckServiceV1Exists("fastly_service_v1.foo", &service),
|
||||||
|
testAccCheckFastlyServiceV1Attributes(&service, name, []string{domainName1}),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"fastly_service_v1.foo", "name", name),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"fastly_service_v1.foo", "active_version", "1"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"fastly_service_v1.foo", "domain.#", "1"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccServiceV1Config_domainUpdate(nameUpdate, domainName1, domainName2),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckServiceV1Exists("fastly_service_v1.foo", &service),
|
||||||
|
testAccCheckFastlyServiceV1Attributes(&service, nameUpdate, []string{domainName1, domainName2}),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"fastly_service_v1.foo", "name", nameUpdate),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"fastly_service_v1.foo", "active_version", "2"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"fastly_service_v1.foo", "domain.#", "2"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccFastlyServiceV1_updateBackend(t *testing.T) {
|
||||||
|
var service gofastly.ServiceDetail
|
||||||
|
name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||||
|
backendName := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))
|
||||||
|
backendName2 := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckServiceV1Destroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccServiceV1Config_backend(name, backendName),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckServiceV1Exists("fastly_service_v1.foo", &service),
|
||||||
|
testAccCheckFastlyServiceV1Attributes_backends(&service, name, []string{backendName}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccServiceV1Config_backend_update(name, backendName, backendName2),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckServiceV1Exists("fastly_service_v1.foo", &service),
|
||||||
|
testAccCheckFastlyServiceV1Attributes_backends(&service, name, []string{backendName, backendName2}),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"fastly_service_v1.foo", "active_version", "2"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"fastly_service_v1.foo", "backend.#", "2"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccFastlyServiceV1_basic(t *testing.T) {
|
||||||
|
var service gofastly.ServiceDetail
|
||||||
|
name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||||
|
domainName := fmt.Sprintf("%s.notadomain.com", acctest.RandString(10))
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckServiceV1Destroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccServiceV1Config(name, domainName),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckServiceV1Exists("fastly_service_v1.foo", &service),
|
||||||
|
testAccCheckFastlyServiceV1Attributes(&service, name, []string{domainName}),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"fastly_service_v1.foo", "name", name),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"fastly_service_v1.foo", "active_version", "1"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"fastly_service_v1.foo", "domain.#", "1"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceV1_disappears – test that a non-empty plan is returned when a Fastly
|
||||||
|
// Service is destroyed outside of Terraform, and can no longer be found,
|
||||||
|
// correctly clearing the ID field and generating a new plan
|
||||||
|
func TestAccFastlyServiceV1_disappears(t *testing.T) {
|
||||||
|
var service gofastly.ServiceDetail
|
||||||
|
name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||||
|
domainName := fmt.Sprintf("%s.notadomain.com", acctest.RandString(10))
|
||||||
|
|
||||||
|
testDestroy := func(*terraform.State) error {
|
||||||
|
// reach out and DELETE the service
|
||||||
|
conn := testAccProvider.Meta().(*FastlyClient).conn
|
||||||
|
// deactivate active version to destoy
|
||||||
|
_, err := conn.DeactivateVersion(&gofastly.DeactivateVersionInput{
|
||||||
|
Service: service.ID,
|
||||||
|
Version: service.ActiveVersion.Number,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete service
|
||||||
|
err = conn.DeleteService(&gofastly.DeleteServiceInput{
|
||||||
|
ID: service.ID,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckServiceV1Destroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccServiceV1Config(name, domainName),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckServiceV1Exists("fastly_service_v1.foo", &service),
|
||||||
|
testDestroy,
|
||||||
|
),
|
||||||
|
ExpectNonEmptyPlan: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckServiceV1Exists(n string, service *gofastly.ServiceDetail) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No Service ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := testAccProvider.Meta().(*FastlyClient).conn
|
||||||
|
latest, err := conn.GetServiceDetails(&gofastly.GetServiceInput{
|
||||||
|
ID: rs.Primary.ID,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*service = *latest
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckFastlyServiceV1Attributes(service *gofastly.ServiceDetail, name string, domains []string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
|
||||||
|
if service.Name != name {
|
||||||
|
return fmt.Errorf("Bad name, expected (%s), got (%s)", name, service.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := testAccProvider.Meta().(*FastlyClient).conn
|
||||||
|
domainList, err := conn.ListDomains(&gofastly.ListDomainsInput{
|
||||||
|
Service: service.ID,
|
||||||
|
Version: service.ActiveVersion.Number,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[ERR] Error looking up Domains for (%s), version (%s): %s", service.Name, service.ActiveVersion.Number, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := len(domains)
|
||||||
|
for _, d := range domainList {
|
||||||
|
for _, e := range domains {
|
||||||
|
if d.Name == e {
|
||||||
|
expected--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected > 0 {
|
||||||
|
return fmt.Errorf("Domain count mismatch, expected: %#v, got: %#v", domains, domainList)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckFastlyServiceV1Attributes_backends(service *gofastly.ServiceDetail, name string, backends []string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
|
||||||
|
if service.Name != name {
|
||||||
|
return fmt.Errorf("Bad name, expected (%s), got (%s)", name, service.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := testAccProvider.Meta().(*FastlyClient).conn
|
||||||
|
backendList, err := conn.ListBackends(&gofastly.ListBackendsInput{
|
||||||
|
Service: service.ID,
|
||||||
|
Version: service.ActiveVersion.Number,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[ERR] Error looking up Backends for (%s), version (%s): %s", service.Name, service.ActiveVersion.Number, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := len(backendList)
|
||||||
|
for _, b := range backendList {
|
||||||
|
for _, e := range backends {
|
||||||
|
if b.Address == e {
|
||||||
|
expected--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected > 0 {
|
||||||
|
return fmt.Errorf("Backend count mismatch, expected: %#v, got: %#v", backends, backendList)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckServiceV1Destroy(s *terraform.State) error {
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "fastly_service_v1" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := testAccProvider.Meta().(*FastlyClient).conn
|
||||||
|
l, err := conn.ListServices(&gofastly.ListServicesInput{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[WARN] Error listing servcies when deleting Fastly Service (%s): %s", rs.Primary.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range l {
|
||||||
|
if s.ID == rs.Primary.ID {
|
||||||
|
// service still found
|
||||||
|
return fmt.Errorf("[WARN] Tried deleting Service (%s), but was still found", rs.Primary.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccServiceV1Config(name, domain string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "fastly_service_v1" "foo" {
|
||||||
|
name = "%s"
|
||||||
|
|
||||||
|
domain {
|
||||||
|
name = "%s"
|
||||||
|
comment = "tf-testing-domain"
|
||||||
|
}
|
||||||
|
|
||||||
|
backend {
|
||||||
|
address = "aws.amazon.com"
|
||||||
|
name = "amazon docs"
|
||||||
|
}
|
||||||
|
|
||||||
|
force_destroy = true
|
||||||
|
}`, name, domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccServiceV1Config_domainUpdate(name, domain1, domain2 string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "fastly_service_v1" "foo" {
|
||||||
|
name = "%s"
|
||||||
|
|
||||||
|
domain {
|
||||||
|
name = "%s"
|
||||||
|
comment = "tf-testing-domain"
|
||||||
|
}
|
||||||
|
|
||||||
|
domain {
|
||||||
|
name = "%s"
|
||||||
|
comment = "tf-testing-other-domain"
|
||||||
|
}
|
||||||
|
|
||||||
|
backend {
|
||||||
|
address = "aws.amazon.com"
|
||||||
|
name = "amazon docs"
|
||||||
|
}
|
||||||
|
|
||||||
|
force_destroy = true
|
||||||
|
}`, name, domain1, domain2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccServiceV1Config_backend(name, backend string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "fastly_service_v1" "foo" {
|
||||||
|
name = "%s"
|
||||||
|
|
||||||
|
domain {
|
||||||
|
name = "test.notadomain.com"
|
||||||
|
comment = "tf-testing-domain"
|
||||||
|
}
|
||||||
|
|
||||||
|
backend {
|
||||||
|
address = "%s"
|
||||||
|
name = "tf -test backend"
|
||||||
|
}
|
||||||
|
|
||||||
|
force_destroy = true
|
||||||
|
}`, name, backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccServiceV1Config_backend_update(name, backend, backend2 string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "fastly_service_v1" "foo" {
|
||||||
|
name = "%s"
|
||||||
|
|
||||||
|
default_ttl = 3400
|
||||||
|
|
||||||
|
domain {
|
||||||
|
name = "test.notadomain.com"
|
||||||
|
comment = "tf-testing-domain"
|
||||||
|
}
|
||||||
|
|
||||||
|
backend {
|
||||||
|
address = "%s"
|
||||||
|
name = "tf-test-backend"
|
||||||
|
}
|
||||||
|
|
||||||
|
backend {
|
||||||
|
address = "%s"
|
||||||
|
name = "tf-test-backend-other"
|
||||||
|
}
|
||||||
|
|
||||||
|
force_destroy = true
|
||||||
|
}`, name, backend, backend2)
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
## Copyright 2014 Alvaro J. Genial. All rights reserved.
|
||||||
|
## Use of this source code is governed by a BSD-style
|
||||||
|
## license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- tip
|
||||||
|
- 1.3
|
||||||
|
# - 1.2
|
||||||
|
# Note: 1.2 is disabled because it seems to require that cover
|
||||||
|
# be installed from code.google.com/p/go.tools/cmd/cover
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go get -v golang.org/x/tools/cmd/cover
|
||||||
|
- go get -v golang.org/x/tools/cmd/vet
|
||||||
|
- go get -v github.com/golang/lint/golint
|
||||||
|
- export PATH=$PATH:/home/travis/gopath/bin
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go build -v ./...
|
||||||
|
- go test -v -cover ./...
|
||||||
|
- go vet ./...
|
||||||
|
- golint .
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2014 Alvaro J. Genial. 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.
|
|
@ -0,0 +1,205 @@
|
||||||
|
form
|
||||||
|
====
|
||||||
|
|
||||||
|
A Form Encoding & Decoding Package for Go, written by [Alvaro J. Genial](http://alva.ro).
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/ajg/form.png?branch=master)](https://travis-ci.org/ajg/form)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/ajg/form?status.png)](https://godoc.org/github.com/ajg/form)
|
||||||
|
|
||||||
|
Synopsis
|
||||||
|
--------
|
||||||
|
|
||||||
|
This library is designed to allow seamless, high-fidelity encoding and decoding of arbitrary data in `application/x-www-form-urlencoded` format and as [`url.Values`](http://golang.org/pkg/net/url/#Values). It is intended to be useful primarily in dealing with web forms and URI query strings, both of which natively employ said format.
|
||||||
|
|
||||||
|
Unsurprisingly, `form` is modeled after other Go [`encoding`](http://golang.org/pkg/encoding/) packages, in particular [`encoding/json`](http://golang.org/pkg/encoding/json/), and follows the same conventions (see below for more.) It aims to automatically handle any kind of concrete Go [data value](#values) (i.e., not functions, channels, etc.) while providing mechanisms for custom behavior.
|
||||||
|
|
||||||
|
Status
|
||||||
|
------
|
||||||
|
|
||||||
|
The implementation is in usable shape and is fairly well tested with its accompanying test suite. The API is unlikely to change much, but still may. Lastly, the code has not yet undergone a security review to ensure it is free of vulnerabilities. Please file an issue or send a pull request for fixes & improvements.
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
------------
|
||||||
|
|
||||||
|
The only requirement is [Go 1.2](http://golang.org/doc/go1.2) or later.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/ajg/form"
|
||||||
|
```
|
||||||
|
|
||||||
|
Given a type like the following...
|
||||||
|
|
||||||
|
```go
|
||||||
|
type User struct {
|
||||||
|
Name string `form:"name"`
|
||||||
|
Email string `form:"email"`
|
||||||
|
Joined time.Time `form:"joined,omitempty"`
|
||||||
|
Posts []int `form:"posts"`
|
||||||
|
Preferences map[string]string `form:"prefs"`
|
||||||
|
Avatar []byte `form:"avatar"`
|
||||||
|
PasswordHash int64 `form:"-"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...it is easy to encode data of that type...
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
func PostUser(url string, u User) error {
|
||||||
|
var c http.Client
|
||||||
|
_, err := c.PostForm(url, form.EncodeToValues(u))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...as well as decode it...
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var u User
|
||||||
|
|
||||||
|
d := form.NewDecoder(r.Body)
|
||||||
|
if err := d.Decode(&u); err != nil {
|
||||||
|
http.Error(w, "Form could not be decoded", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "Decoded: %#v", u)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...without having to do any grunt work.
|
||||||
|
|
||||||
|
Field Tags
|
||||||
|
----------
|
||||||
|
|
||||||
|
Like other encoding packages, `form` supports the following options for fields:
|
||||||
|
|
||||||
|
- `` `form:"-"` ``: Causes the field to be ignored during encoding and decoding.
|
||||||
|
- `` `form:"<name>"` ``: Overrides the field's name; useful especially when dealing with external identifiers in camelCase, as are commonly found on the web.
|
||||||
|
- `` `form:",omitempty"` ``: Elides the field during encoding if it is empty (typically meaning equal to the type's zero value.)
|
||||||
|
- `` `form:"<name>,omitempty"` ``: The way to combine the two options above.
|
||||||
|
|
||||||
|
Values
|
||||||
|
------
|
||||||
|
|
||||||
|
### Simple Values
|
||||||
|
|
||||||
|
Values of the following types are all considered simple:
|
||||||
|
|
||||||
|
- `bool`
|
||||||
|
- `int`, `int8`, `int16`, `int32`, `int64`, `rune`
|
||||||
|
- `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `byte`
|
||||||
|
- `float32`, `float64`
|
||||||
|
- `complex64`, `complex128`
|
||||||
|
- `string`
|
||||||
|
- `[]byte` (see note)
|
||||||
|
- [`time.Time`](http://golang.org/pkg/time/#Time)
|
||||||
|
- [`url.URL`](http://golang.org/pkg/net/url/#URL)
|
||||||
|
- An alias of any of the above
|
||||||
|
- A pointer to any of the above
|
||||||
|
|
||||||
|
### Composite Values
|
||||||
|
|
||||||
|
A composite value is one that can contain other values. Values of the following kinds...
|
||||||
|
|
||||||
|
- Maps
|
||||||
|
- Slices; except `[]byte` (see note)
|
||||||
|
- Structs; except [`time.Time`](http://golang.org/pkg/time/#Time) and [`url.URL`](http://golang.org/pkg/net/url/#URL)
|
||||||
|
- Arrays
|
||||||
|
- An alias of any of the above
|
||||||
|
- A pointer to any of the above
|
||||||
|
|
||||||
|
...are considered composites in general, unless they implement custom marshaling/unmarshaling. Composite values are encoded as a flat mapping of paths to values, where the paths are constructed by joining the parent and child paths with a period (`.`).
|
||||||
|
|
||||||
|
(Note: a byte slice is treated as a `string` by default because it's more efficient, but can also be decoded as a slice—i.e., with indexes.)
|
||||||
|
|
||||||
|
### Untyped Values
|
||||||
|
|
||||||
|
While encouraged, it is not necessary to define a type (e.g. a `struct`) in order to use `form`, since it is able to encode and decode untyped data generically using the following rules:
|
||||||
|
|
||||||
|
- Simple values will be treated as a `string`.
|
||||||
|
- Composite values will be treated as a `map[string]interface{}`, itself able to contain nested values (both scalar and compound) ad infinitum.
|
||||||
|
- However, if there is a value (of any supported type) already present in a map for a given key, then it will be used when possible, rather than being replaced with a generic value as specified above; this makes it possible to handle partially typed, dynamic or schema-less values.
|
||||||
|
|
||||||
|
### Unsupported Values
|
||||||
|
|
||||||
|
Values of the following kinds aren't supported and, if present, must be ignored.
|
||||||
|
|
||||||
|
- Channel
|
||||||
|
- Function
|
||||||
|
- Unsafe pointer
|
||||||
|
- An alias of any of the above
|
||||||
|
- A pointer to any of the above
|
||||||
|
|
||||||
|
Custom Marshaling
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
There is a default (generally lossless) marshaling & unmarshaling scheme for any concrete data value in Go, which is good enough in most cases. However, it is possible to override it and use a custom scheme. For instance, a "binary" field could be marshaled more efficiently using [base64](http://golang.org/pkg/encoding/base64/) to prevent it from being percent-escaped during serialization to `application/x-www-form-urlencoded` format.
|
||||||
|
|
||||||
|
Because `form` provides support for [`encoding.TextMarshaler`](http://golang.org/pkg/encoding/#TextMarshaler) and [`encoding.TextUnmarshaler`](http://golang.org/pkg/encoding/#TextUnmarshaler) it is easy to do that; for instance, like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "encoding"
|
||||||
|
|
||||||
|
type Binary []byte
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ encoding.TextMarshaler = &Binary{}
|
||||||
|
_ encoding.TextUnmarshaler = &Binary{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b Binary) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(base64.URLEncoding.EncodeToString([]byte(b))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Binary) UnmarshalText(text []byte) error {
|
||||||
|
bs, err := base64.URLEncoding.DecodeString(string(text))
|
||||||
|
if err == nil {
|
||||||
|
*b = Binary(bs)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now any value with type `Binary` will automatically be encoded using the [URL](http://golang.org/pkg/encoding/base64/#URLEncoding) variant of base64. It is left as an exercise to the reader to improve upon this scheme by eliminating the need for padding (which, besides being superfluous, uses `=`, a character that will end up percent-escaped.)
|
||||||
|
|
||||||
|
Keys
|
||||||
|
----
|
||||||
|
|
||||||
|
In theory any value can be a key as long as it has a string representation. However, periods have special meaning to `form`, and thus, under the hood (i.e. in encoded form) they are transparently escaped using a preceding backslash (`\`). Backslashes within keys, themselves, are also escaped in this manner (e.g. as `\\`) in order to permit representing `\.` itself (as `\\\.`).
|
||||||
|
|
||||||
|
(Note: it is normally unnecessary to deal with this issue unless keys are being constructed manually—e.g. literally embedded in HTML or in a URI.)
|
||||||
|
|
||||||
|
Limitations
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- Circular (self-referential) values are untested.
|
||||||
|
|
||||||
|
Future Work
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The following items would be nice to have in the future—though they are not being worked on yet:
|
||||||
|
|
||||||
|
- An option to treat all values as if they had been tagged with `omitempty`.
|
||||||
|
- An option to automatically treat all field names in `camelCase` or `underscore_case`.
|
||||||
|
- Built-in support for the types in [`math/big`](http://golang.org/pkg/math/big/).
|
||||||
|
- Built-in support for the types in [`image/color`](http://golang.org/pkg/image/color/).
|
||||||
|
- Improve encoding/decoding by reading/writing directly from/to the `io.Reader`/`io.Writer` when possible, rather than going through an intermediate representation (i.e. `node`) which requires more memory.
|
||||||
|
|
||||||
|
(Feel free to implement any of these and then send a pull request.)
|
||||||
|
|
||||||
|
Related Work
|
||||||
|
------------
|
||||||
|
|
||||||
|
- Package [gorilla/schema](https://github.com/gorilla/schema), which only implements decoding.
|
||||||
|
- Package [google/go-querystring](https://github.com/google/go-querystring), which only implements encoding.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
This library is distributed under a BSD-style [LICENSE](./LICENSE).
|
|
@ -0,0 +1,330 @@
|
||||||
|
// Copyright 2014 Alvaro J. Genial. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDecoder returns a new form decoder.
|
||||||
|
func NewDecoder(r io.Reader) *decoder {
|
||||||
|
return &decoder{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decoder decodes data from a form (application/x-www-form-urlencoded).
|
||||||
|
type decoder struct {
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode reads in and decodes form-encoded data into dst.
|
||||||
|
func (d decoder) Decode(dst interface{}) error {
|
||||||
|
bs, err := ioutil.ReadAll(d.r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
vs, err := url.ParseQuery(string(bs))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
return decodeNode(v, parseValues(vs, canIndexOrdinally(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeString decodes src into dst.
|
||||||
|
func DecodeString(dst interface{}, src string) error {
|
||||||
|
vs, err := url.ParseQuery(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
return decodeNode(v, parseValues(vs, canIndexOrdinally(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeValues decodes vs into dst.
|
||||||
|
func DecodeValues(dst interface{}, vs url.Values) error {
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
return decodeNode(v, parseValues(vs, canIndexOrdinally(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeNode(v reflect.Value, n node) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
err = fmt.Errorf("%v", e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if v.Kind() == reflect.Slice {
|
||||||
|
return fmt.Errorf("could not decode directly into slice; use pointer to slice")
|
||||||
|
}
|
||||||
|
decodeValue(v, n)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeValue(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
k := v.Kind()
|
||||||
|
|
||||||
|
if k == reflect.Ptr && v.IsNil() {
|
||||||
|
v.Set(reflect.New(t.Elem()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if unmarshalValue(v, x) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
empty := isEmpty(x)
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case reflect.Ptr:
|
||||||
|
decodeValue(v.Elem(), x)
|
||||||
|
return
|
||||||
|
case reflect.Interface:
|
||||||
|
if !v.IsNil() {
|
||||||
|
decodeValue(v.Elem(), x)
|
||||||
|
return
|
||||||
|
|
||||||
|
} else if empty {
|
||||||
|
return // Allow nil interfaces only if empty.
|
||||||
|
} else {
|
||||||
|
panic("form: cannot decode non-empty value into into nil interface")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if empty {
|
||||||
|
v.Set(reflect.Zero(t)) // Treat the empty string as the zero value.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case reflect.Struct:
|
||||||
|
if t.ConvertibleTo(timeType) {
|
||||||
|
decodeTime(v, x)
|
||||||
|
} else if t.ConvertibleTo(urlType) {
|
||||||
|
decodeURL(v, x)
|
||||||
|
} else {
|
||||||
|
decodeStruct(v, x)
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
decodeSlice(v, x)
|
||||||
|
case reflect.Array:
|
||||||
|
decodeArray(v, x)
|
||||||
|
case reflect.Map:
|
||||||
|
decodeMap(v, x)
|
||||||
|
case reflect.Invalid, reflect.Uintptr, reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
panic(t.String() + " has unsupported kind " + k.String())
|
||||||
|
default:
|
||||||
|
decodeBasic(v, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeStruct(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
for k, c := range getNode(x) {
|
||||||
|
if f, ok := findField(v, k); !ok && k == "" {
|
||||||
|
panic(getString(x) + " cannot be decoded as " + t.String())
|
||||||
|
} else if !ok {
|
||||||
|
panic(k + " doesn't exist in " + t.String())
|
||||||
|
} else if !f.CanSet() {
|
||||||
|
panic(k + " cannot be set in " + t.String())
|
||||||
|
} else {
|
||||||
|
decodeValue(f, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeMap(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
if v.IsNil() {
|
||||||
|
v.Set(reflect.MakeMap(t))
|
||||||
|
}
|
||||||
|
for k, c := range getNode(x) {
|
||||||
|
i := reflect.New(t.Key()).Elem()
|
||||||
|
decodeValue(i, k)
|
||||||
|
|
||||||
|
w := v.MapIndex(i)
|
||||||
|
if w.IsValid() { // We have an actual element value to decode into.
|
||||||
|
if w.Kind() == reflect.Interface {
|
||||||
|
w = w.Elem()
|
||||||
|
}
|
||||||
|
w = reflect.New(w.Type()).Elem()
|
||||||
|
} else if t.Elem().Kind() != reflect.Interface { // The map's element type is concrete.
|
||||||
|
w = reflect.New(t.Elem()).Elem()
|
||||||
|
} else {
|
||||||
|
// The best we can do here is to decode as either a string (for scalars) or a map[string]interface {} (for the rest).
|
||||||
|
// We could try to guess the type based on the string (e.g. true/false => bool) but that'll get ugly fast,
|
||||||
|
// especially if we have to guess the kind (slice vs. array vs. map) and index type (e.g. string, int, etc.)
|
||||||
|
switch c.(type) {
|
||||||
|
case node:
|
||||||
|
w = reflect.MakeMap(stringMapType)
|
||||||
|
case string:
|
||||||
|
w = reflect.New(stringType).Elem()
|
||||||
|
default:
|
||||||
|
panic("value is neither node nor string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeValue(w, c)
|
||||||
|
v.SetMapIndex(i, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeArray(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
for k, c := range getNode(x) {
|
||||||
|
i, err := strconv.Atoi(k)
|
||||||
|
if err != nil {
|
||||||
|
panic(k + " is not a valid index for type " + t.String())
|
||||||
|
}
|
||||||
|
if l := v.Len(); i >= l {
|
||||||
|
panic("index is above array size")
|
||||||
|
}
|
||||||
|
decodeValue(v.Index(i), c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeSlice(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
if t.Elem().Kind() == reflect.Uint8 {
|
||||||
|
// Allow, but don't require, byte slices to be encoded as a single string.
|
||||||
|
if s, ok := x.(string); ok {
|
||||||
|
v.SetBytes([]byte(s))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Implicit indexing is currently done at the parseValues level,
|
||||||
|
// so if if an implicitKey reaches here it will always replace the last.
|
||||||
|
implicit := 0
|
||||||
|
for k, c := range getNode(x) {
|
||||||
|
var i int
|
||||||
|
if k == implicitKey {
|
||||||
|
i = implicit
|
||||||
|
implicit++
|
||||||
|
} else {
|
||||||
|
explicit, err := strconv.Atoi(k)
|
||||||
|
if err != nil {
|
||||||
|
panic(k + " is not a valid index for type " + t.String())
|
||||||
|
}
|
||||||
|
i = explicit
|
||||||
|
implicit = explicit + 1
|
||||||
|
}
|
||||||
|
// "Extend" the slice if it's too short.
|
||||||
|
if l := v.Len(); i >= l {
|
||||||
|
delta := i - l + 1
|
||||||
|
v.Set(reflect.AppendSlice(v, reflect.MakeSlice(t, delta, delta)))
|
||||||
|
}
|
||||||
|
decodeValue(v.Index(i), c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeBasic(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
switch k, s := t.Kind(), getString(x); k {
|
||||||
|
case reflect.Bool:
|
||||||
|
if b, e := strconv.ParseBool(s); e == nil {
|
||||||
|
v.SetBool(b)
|
||||||
|
} else {
|
||||||
|
panic("could not parse bool from " + strconv.Quote(s))
|
||||||
|
}
|
||||||
|
case reflect.Int,
|
||||||
|
reflect.Int8,
|
||||||
|
reflect.Int16,
|
||||||
|
reflect.Int32,
|
||||||
|
reflect.Int64:
|
||||||
|
if i, e := strconv.ParseInt(s, 10, 64); e == nil {
|
||||||
|
v.SetInt(i)
|
||||||
|
} else {
|
||||||
|
panic("could not parse int from " + strconv.Quote(s))
|
||||||
|
}
|
||||||
|
case reflect.Uint,
|
||||||
|
reflect.Uint8,
|
||||||
|
reflect.Uint16,
|
||||||
|
reflect.Uint32,
|
||||||
|
reflect.Uint64:
|
||||||
|
if u, e := strconv.ParseUint(s, 10, 64); e == nil {
|
||||||
|
v.SetUint(u)
|
||||||
|
} else {
|
||||||
|
panic("could not parse uint from " + strconv.Quote(s))
|
||||||
|
}
|
||||||
|
case reflect.Float32,
|
||||||
|
reflect.Float64:
|
||||||
|
if f, e := strconv.ParseFloat(s, 64); e == nil {
|
||||||
|
v.SetFloat(f)
|
||||||
|
} else {
|
||||||
|
panic("could not parse float from " + strconv.Quote(s))
|
||||||
|
}
|
||||||
|
case reflect.Complex64,
|
||||||
|
reflect.Complex128:
|
||||||
|
var c complex128
|
||||||
|
if n, err := fmt.Sscanf(s, "%g", &c); n == 1 && err == nil {
|
||||||
|
v.SetComplex(c)
|
||||||
|
} else {
|
||||||
|
panic("could not parse complex from " + strconv.Quote(s))
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
v.SetString(s)
|
||||||
|
default:
|
||||||
|
panic(t.String() + " has unsupported kind " + k.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeTime(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
s := getString(x)
|
||||||
|
// TODO: Find a more efficient way to do this.
|
||||||
|
for _, f := range allowedTimeFormats {
|
||||||
|
if p, err := time.Parse(f, s); err == nil {
|
||||||
|
v.Set(reflect.ValueOf(p).Convert(v.Type()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("cannot decode string `" + s + "` as " + t.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeURL(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
s := getString(x)
|
||||||
|
if u, err := url.Parse(s); err == nil {
|
||||||
|
v.Set(reflect.ValueOf(*u).Convert(v.Type()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic("cannot decode string `" + s + "` as " + t.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowedTimeFormats = []string{
|
||||||
|
"2006-01-02T15:04:05.999999999Z07:00",
|
||||||
|
"2006-01-02T15:04:05.999999999Z07",
|
||||||
|
"2006-01-02T15:04:05.999999999Z",
|
||||||
|
"2006-01-02T15:04:05.999999999",
|
||||||
|
"2006-01-02T15:04:05Z07:00",
|
||||||
|
"2006-01-02T15:04:05Z07",
|
||||||
|
"2006-01-02T15:04:05Z",
|
||||||
|
"2006-01-02T15:04:05",
|
||||||
|
"2006-01-02T15:04Z",
|
||||||
|
"2006-01-02T15:04",
|
||||||
|
"2006-01-02T15Z",
|
||||||
|
"2006-01-02T15",
|
||||||
|
"2006-01-02",
|
||||||
|
"2006-01",
|
||||||
|
"2006",
|
||||||
|
"15:04:05.999999999Z07:00",
|
||||||
|
"15:04:05.999999999Z07",
|
||||||
|
"15:04:05.999999999Z",
|
||||||
|
"15:04:05.999999999",
|
||||||
|
"15:04:05Z07:00",
|
||||||
|
"15:04:05Z07",
|
||||||
|
"15:04:05Z",
|
||||||
|
"15:04:05",
|
||||||
|
"15:04Z",
|
||||||
|
"15:04",
|
||||||
|
"15Z",
|
||||||
|
"15",
|
||||||
|
}
|
|
@ -0,0 +1,351 @@
|
||||||
|
// Copyright 2014 Alvaro J. Genial. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewEncoder returns a new form encoder.
|
||||||
|
func NewEncoder(w io.Writer) *encoder {
|
||||||
|
return &encoder{w}
|
||||||
|
}
|
||||||
|
|
||||||
|
// encoder provides a way to encode to a Writer.
|
||||||
|
type encoder struct {
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encodes dst as form and writes it out using the encoder's Writer.
|
||||||
|
func (e encoder) Encode(dst interface{}) error {
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
n, err := encodeToNode(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s := n.Values().Encode()
|
||||||
|
l, err := io.WriteString(e.w, s)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return err
|
||||||
|
case l != len(s):
|
||||||
|
return errors.New("could not write data completely")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeToString encodes dst as a form and returns it as a string.
|
||||||
|
func EncodeToString(dst interface{}) (string, error) {
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
n, err := encodeToNode(v)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return n.Values().Encode(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeToValues encodes dst as a form and returns it as Values.
|
||||||
|
func EncodeToValues(dst interface{}) (url.Values, error) {
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
n, err := encodeToNode(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return n.Values(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeToNode(v reflect.Value) (n node, err error) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
err = fmt.Errorf("%v", e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return getNode(encodeValue(v)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeValue(v reflect.Value) interface{} {
|
||||||
|
t := v.Type()
|
||||||
|
k := v.Kind()
|
||||||
|
|
||||||
|
if s, ok := marshalValue(v); ok {
|
||||||
|
return s
|
||||||
|
} else if isEmptyValue(v) {
|
||||||
|
return "" // Treat the zero value as the empty string.
|
||||||
|
}
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
return encodeValue(v.Elem())
|
||||||
|
case reflect.Struct:
|
||||||
|
if t.ConvertibleTo(timeType) {
|
||||||
|
return encodeTime(v)
|
||||||
|
} else if t.ConvertibleTo(urlType) {
|
||||||
|
return encodeURL(v)
|
||||||
|
}
|
||||||
|
return encodeStruct(v)
|
||||||
|
case reflect.Slice:
|
||||||
|
return encodeSlice(v)
|
||||||
|
case reflect.Array:
|
||||||
|
return encodeArray(v)
|
||||||
|
case reflect.Map:
|
||||||
|
return encodeMap(v)
|
||||||
|
case reflect.Invalid, reflect.Uintptr, reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
panic(t.String() + " has unsupported kind " + t.Kind().String())
|
||||||
|
default:
|
||||||
|
return encodeBasic(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeStruct(v reflect.Value) interface{} {
|
||||||
|
t := v.Type()
|
||||||
|
n := node{}
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
k, oe := fieldInfo(f)
|
||||||
|
|
||||||
|
if k == "-" {
|
||||||
|
continue
|
||||||
|
} else if fv := v.Field(i); oe && isEmptyValue(fv) {
|
||||||
|
delete(n, k)
|
||||||
|
} else {
|
||||||
|
n[k] = encodeValue(fv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeMap(v reflect.Value) interface{} {
|
||||||
|
n := node{}
|
||||||
|
for _, i := range v.MapKeys() {
|
||||||
|
k := getString(encodeValue(i))
|
||||||
|
n[k] = encodeValue(v.MapIndex(i))
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeArray(v reflect.Value) interface{} {
|
||||||
|
n := node{}
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
n[strconv.Itoa(i)] = encodeValue(v.Index(i))
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeSlice(v reflect.Value) interface{} {
|
||||||
|
t := v.Type()
|
||||||
|
if t.Elem().Kind() == reflect.Uint8 {
|
||||||
|
return string(v.Bytes()) // Encode byte slices as a single string by default.
|
||||||
|
}
|
||||||
|
n := node{}
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
n[strconv.Itoa(i)] = encodeValue(v.Index(i))
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeTime(v reflect.Value) string {
|
||||||
|
t := v.Convert(timeType).Interface().(time.Time)
|
||||||
|
if t.Year() == 0 && (t.Month() == 0 || t.Month() == 1) && (t.Day() == 0 || t.Day() == 1) {
|
||||||
|
return t.Format("15:04:05.999999999Z07:00")
|
||||||
|
} else if t.Hour() == 0 && t.Minute() == 0 && t.Second() == 0 && t.Nanosecond() == 0 {
|
||||||
|
return t.Format("2006-01-02")
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02T15:04:05.999999999Z07:00")
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeURL(v reflect.Value) string {
|
||||||
|
u := v.Convert(urlType).Interface().(url.URL)
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeBasic(v reflect.Value) string {
|
||||||
|
t := v.Type()
|
||||||
|
switch k := t.Kind(); k {
|
||||||
|
case reflect.Bool:
|
||||||
|
return strconv.FormatBool(v.Bool())
|
||||||
|
case reflect.Int,
|
||||||
|
reflect.Int8,
|
||||||
|
reflect.Int16,
|
||||||
|
reflect.Int32,
|
||||||
|
reflect.Int64:
|
||||||
|
return strconv.FormatInt(v.Int(), 10)
|
||||||
|
case reflect.Uint,
|
||||||
|
reflect.Uint8,
|
||||||
|
reflect.Uint16,
|
||||||
|
reflect.Uint32,
|
||||||
|
reflect.Uint64:
|
||||||
|
return strconv.FormatUint(v.Uint(), 10)
|
||||||
|
case reflect.Float32:
|
||||||
|
return strconv.FormatFloat(v.Float(), 'g', -1, 32)
|
||||||
|
case reflect.Float64:
|
||||||
|
return strconv.FormatFloat(v.Float(), 'g', -1, 64)
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
s := fmt.Sprintf("%g", v.Complex())
|
||||||
|
return strings.TrimSuffix(strings.TrimPrefix(s, "("), ")")
|
||||||
|
case reflect.String:
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
panic(t.String() + " has unsupported kind " + t.Kind().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmptyValue(v reflect.Value) bool {
|
||||||
|
switch t := v.Type(); v.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return v.Complex() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
return v.IsNil()
|
||||||
|
case reflect.Struct:
|
||||||
|
if t.ConvertibleTo(timeType) {
|
||||||
|
return v.Convert(timeType).Interface().(time.Time).IsZero()
|
||||||
|
}
|
||||||
|
return reflect.DeepEqual(v, reflect.Zero(t))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// canIndexOrdinally returns whether a value contains an ordered sequence of elements.
|
||||||
|
func canIndexOrdinally(v reflect.Value) bool {
|
||||||
|
if !v.IsValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch t := v.Type(); t.Kind() {
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
return canIndexOrdinally(v.Elem())
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func fieldInfo(f reflect.StructField) (k string, oe bool) {
|
||||||
|
if f.PkgPath != "" { // Skip private fields.
|
||||||
|
return omittedKey, oe
|
||||||
|
}
|
||||||
|
|
||||||
|
k = f.Name
|
||||||
|
tag := f.Tag.Get("form")
|
||||||
|
if tag == "" {
|
||||||
|
return k, oe
|
||||||
|
}
|
||||||
|
|
||||||
|
ps := strings.SplitN(tag, ",", 2)
|
||||||
|
if ps[0] != "" {
|
||||||
|
k = ps[0]
|
||||||
|
}
|
||||||
|
if len(ps) == 2 {
|
||||||
|
oe = ps[1] == "omitempty"
|
||||||
|
}
|
||||||
|
return k, oe
|
||||||
|
}
|
||||||
|
|
||||||
|
func findField(v reflect.Value, n string) (reflect.Value, bool) {
|
||||||
|
t := v.Type()
|
||||||
|
l := v.NumField()
|
||||||
|
// First try named fields.
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
k, _ := fieldInfo(f)
|
||||||
|
if k == omittedKey {
|
||||||
|
continue
|
||||||
|
} else if n == k {
|
||||||
|
return v.Field(i), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then try anonymous (embedded) fields.
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
k, _ := fieldInfo(f)
|
||||||
|
if k == omittedKey || !f.Anonymous { // || k != "" ?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fv := v.Field(i)
|
||||||
|
fk := fv.Kind()
|
||||||
|
for fk == reflect.Ptr || fk == reflect.Interface {
|
||||||
|
fv = fv.Elem()
|
||||||
|
fk = fv.Kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
if fk != reflect.Struct {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ev, ok := findField(fv, n); ok {
|
||||||
|
return ev, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.Value{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
stringType = reflect.TypeOf(string(""))
|
||||||
|
stringMapType = reflect.TypeOf(map[string]interface{}{})
|
||||||
|
timeType = reflect.TypeOf(time.Time{})
|
||||||
|
timePtrType = reflect.TypeOf(&time.Time{})
|
||||||
|
urlType = reflect.TypeOf(url.URL{})
|
||||||
|
)
|
||||||
|
|
||||||
|
func skipTextMarshalling(t reflect.Type) bool {
|
||||||
|
/*// Skip time.Time because its text unmarshaling is overly rigid:
|
||||||
|
return t == timeType || t == timePtrType*/
|
||||||
|
// Skip time.Time & convertibles because its text unmarshaling is overly rigid:
|
||||||
|
return t.ConvertibleTo(timeType) || t.ConvertibleTo(timePtrType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalValue(v reflect.Value, x interface{}) bool {
|
||||||
|
if skipTextMarshalling(v.Type()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
tu, ok := v.Interface().(encoding.TextUnmarshaler)
|
||||||
|
if !ok && !v.CanAddr() {
|
||||||
|
return false
|
||||||
|
} else if !ok {
|
||||||
|
return unmarshalValue(v.Addr(), x)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := getString(x)
|
||||||
|
if err := tu.UnmarshalText([]byte(s)); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalValue(v reflect.Value) (string, bool) {
|
||||||
|
if skipTextMarshalling(v.Type()) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
tm, ok := v.Interface().(encoding.TextMarshaler)
|
||||||
|
if !ok && !v.CanAddr() {
|
||||||
|
return "", false
|
||||||
|
} else if !ok {
|
||||||
|
return marshalValue(v.Addr())
|
||||||
|
}
|
||||||
|
|
||||||
|
bs, err := tm.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(bs), true
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright 2014 Alvaro J. Genial. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package form implements encoding and decoding of application/x-www-form-urlencoded data.
|
||||||
|
package form
|
||||||
|
|
||||||
|
const (
|
||||||
|
implicitKey = "_"
|
||||||
|
omittedKey = "-"
|
||||||
|
)
|
|
@ -0,0 +1,148 @@
|
||||||
|
// Copyright 2014 Alvaro J. Genial. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type node map[string]interface{}
|
||||||
|
|
||||||
|
func (n node) Values() url.Values {
|
||||||
|
vs := url.Values{}
|
||||||
|
n.merge("", &vs)
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n node) merge(p string, vs *url.Values) {
|
||||||
|
for k, x := range n {
|
||||||
|
switch y := x.(type) {
|
||||||
|
case string:
|
||||||
|
vs.Add(p+escape(k), y)
|
||||||
|
case node:
|
||||||
|
y.merge(p+escape(k)+".", vs)
|
||||||
|
default:
|
||||||
|
panic("value is neither string nor node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add tests for implicit indexing.
|
||||||
|
func parseValues(vs url.Values, canIndexFirstLevelOrdinally bool) node {
|
||||||
|
// NOTE: Because of the flattening of potentially multiple strings to one key, implicit indexing works:
|
||||||
|
// i. At the first level; e.g. Foo.Bar=A&Foo.Bar=B becomes 0.Foo.Bar=A&1.Foo.Bar=B
|
||||||
|
// ii. At the last level; e.g. Foo.Bar._=A&Foo.Bar._=B becomes Foo.Bar.0=A&Foo.Bar.1=B
|
||||||
|
// TODO: At in-between levels; e.g. Foo._.Bar=A&Foo._.Bar=B becomes Foo.0.Bar=A&Foo.1.Bar=B
|
||||||
|
// (This last one requires that there only be one placeholder in order for it to be unambiguous.)
|
||||||
|
|
||||||
|
m := map[string]string{}
|
||||||
|
for k, ss := range vs {
|
||||||
|
indexLastLevelOrdinally := strings.HasSuffix(k, "."+implicitKey)
|
||||||
|
|
||||||
|
for i, s := range ss {
|
||||||
|
if canIndexFirstLevelOrdinally {
|
||||||
|
k = strconv.Itoa(i) + "." + k
|
||||||
|
} else if indexLastLevelOrdinally {
|
||||||
|
k = strings.TrimSuffix(k, implicitKey) + strconv.Itoa(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
m[k] = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n := node{}
|
||||||
|
for k, s := range m {
|
||||||
|
n = n.split(k, s)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitPath(path string) (k, rest string) {
|
||||||
|
esc := false
|
||||||
|
for i, r := range path {
|
||||||
|
switch {
|
||||||
|
case !esc && r == '\\':
|
||||||
|
esc = true
|
||||||
|
case !esc && r == '.':
|
||||||
|
return unescape(path[:i]), path[i+1:]
|
||||||
|
default:
|
||||||
|
esc = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unescape(path), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n node) split(path, s string) node {
|
||||||
|
k, rest := splitPath(path)
|
||||||
|
if rest == "" {
|
||||||
|
return add(n, k, s)
|
||||||
|
}
|
||||||
|
if _, ok := n[k]; !ok {
|
||||||
|
n[k] = node{}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := getNode(n[k])
|
||||||
|
n[k] = c.split(rest, s)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(n node, k, s string) node {
|
||||||
|
if n == nil {
|
||||||
|
return node{k: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := n[k]; ok {
|
||||||
|
panic("key " + k + " already set")
|
||||||
|
}
|
||||||
|
|
||||||
|
n[k] = s
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmpty(x interface{}) bool {
|
||||||
|
switch y := x.(type) {
|
||||||
|
case string:
|
||||||
|
return y == ""
|
||||||
|
case node:
|
||||||
|
if s, ok := y[""].(string); ok {
|
||||||
|
return s == ""
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
panic("value is neither string nor node")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNode(x interface{}) node {
|
||||||
|
switch y := x.(type) {
|
||||||
|
case string:
|
||||||
|
return node{"": y}
|
||||||
|
case node:
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
panic("value is neither string nor node")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getString(x interface{}) string {
|
||||||
|
switch y := x.(type) {
|
||||||
|
case string:
|
||||||
|
return y
|
||||||
|
case node:
|
||||||
|
if s, ok := y[""].(string); ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
panic("value is neither string nor node")
|
||||||
|
}
|
||||||
|
|
||||||
|
func escape(s string) string {
|
||||||
|
return strings.Replace(strings.Replace(s, `\`, `\\`, -1), `.`, `\.`, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unescape(s string) string {
|
||||||
|
return strings.Replace(strings.Replace(s, `\.`, `.`, -1), `\\`, `\`, -1)
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/bash -eu
|
||||||
|
|
||||||
|
# TODO: Only colorize messages given a suitable terminal.
|
||||||
|
# FIXME: Handle case in which no stash entry is created due to no changes.
|
||||||
|
|
||||||
|
printf "\e[30m=== PRE-COMMIT STARTING ===\e[m\n"
|
||||||
|
git stash save --quiet --keep-index --include-untracked
|
||||||
|
|
||||||
|
if go build -v ./... && go test -v -cover ./... && go vet ./... && golint . && travis-lint; then
|
||||||
|
result=$?
|
||||||
|
printf "\e[32m=== PRE-COMMIT SUCCEEDED ===\e[m\n"
|
||||||
|
else
|
||||||
|
result=$?
|
||||||
|
printf "\e[31m=== PRE-COMMIT FAILED ===\e[m\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git stash pop --quiet
|
||||||
|
exit $result
|
|
@ -0,0 +1,28 @@
|
||||||
|
### Go ###
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
bin/
|
||||||
|
pkg/
|
|
@ -0,0 +1,19 @@
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.4.2
|
||||||
|
- 1.5.2
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
|
||||||
|
script:
|
||||||
|
- make updatedeps
|
||||||
|
- make test
|
||||||
|
|
||||||
|
env:
|
||||||
|
# FASTLY_API_KEY
|
||||||
|
- secure: "eiYcogJFF+lK/6coFXaOOm0bDHxaK1qqZ0GinMmPXmQ6nonf56omMVxNyOsV+6jz/fdJCA7gfGv600raTAOVNxD23E/p2j6yxPSI5O6itxp0jxJm7p6MOHwkmsXFZGfLxaqVN2CHs+W3sSc4cwzCkCqlik4XLXihHvzYpjBk1AZK6QUMWqdTcDYDWMfk5CW1O6wUpmYiFwlnENwDopGQlSs1+PyEiDEbEMYu1yVUq+f83IJ176arM4XL8NS2GN1QMBKyALA+6jpT/OrFtW5tkheE+WOQ6+/ZnDCtY0i1RA8BBuyACYuf+WEAkmWfJGGk7+Ou6q2JFzIBsd6ZS3EsM4bs4P1FyhPBwK5zyFty2w7+PwVm6wrZ0NfUh6BKsfCF9MweypsKq+F+4GOcpjdCYPKZKGRjQ4iKOZVVzaVGLRanz1EHiXUcLT+DDr0kFbvrLCLqCPvujBfqeUDqVZMpsUqir9HWqVKutczAnYzFaoeeSVap14J/sd6kcgZo2bNMSRQvMoPCOvicdW8RLIV8Hyx2l0Cv596ZfinWBk2Dcmn6APLkbrBpvhv6/SUtBKHMijjFc5VvoxO3ZP6vUCueDaZVNWkX1xk+VA5PD0T/IcilLy3+nBedz+3lmiW7dnQPuWnlPBFPWvYZvW2KaDOazv5rZK+pKIq32BIyhP/n/AU="
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor 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, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2015 Seth Vargo
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,21 @@
|
||||||
|
TEST?=./...
|
||||||
|
|
||||||
|
default: test
|
||||||
|
|
||||||
|
# test runs the test suite and vets the code
|
||||||
|
test: generate
|
||||||
|
go list $(TEST) | xargs -n1 go test -timeout=30s -parallel=8 $(TESTARGS)
|
||||||
|
|
||||||
|
# updatedeps installs all the dependencies the library needs to run and build
|
||||||
|
updatedeps:
|
||||||
|
go list ./... \
|
||||||
|
| xargs go list -f '{{ join .Deps "\n" }}{{ printf "\n" }}{{ join .TestImports "\n" }}' \
|
||||||
|
| grep -v github.com/sethvargo/go-fastly \
|
||||||
|
| xargs go get -f -u -v
|
||||||
|
|
||||||
|
# generate runs `go generate` to build the dynamically generated source files
|
||||||
|
generate:
|
||||||
|
find . -type f -name '.DS_Store' -delete
|
||||||
|
go generate ./...
|
||||||
|
|
||||||
|
.PHONY: default bin dev dist test testrace updatedeps generate
|
|
@ -0,0 +1,128 @@
|
||||||
|
Go Fastly
|
||||||
|
=========
|
||||||
|
[![Build Status](http://img.shields.io/travis/sethvargo/go-fastly.svg?style=flat-square)][travis]
|
||||||
|
[![Go Documentation](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)][godocs]
|
||||||
|
|
||||||
|
[travis]: http://travis-ci.org/sethvargo/go-fastly
|
||||||
|
[godocs]: http://godoc.org/github.com/sethvargo/go-fastly
|
||||||
|
|
||||||
|
Go Fastly is a Golang API client for interacting with most facets of the
|
||||||
|
[Fastly API](https://docs.fastly.com/api).
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
This is a client library, so there is nothing to install.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
Download the library into your `$GOPATH`:
|
||||||
|
|
||||||
|
$ go get github.com/sethvargo/go-fastly
|
||||||
|
|
||||||
|
Import the library into your tool:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/sethvargo/go-fastly"
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
Fastly's API is designed to work in the following manner:
|
||||||
|
|
||||||
|
1. Create (or clone) a new configuration version for the service
|
||||||
|
2. Make any changes to the version
|
||||||
|
3. Validate the version
|
||||||
|
4. Activate the version
|
||||||
|
|
||||||
|
This flow using the Golang client looks like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create a client object. The client has no state, so it can be persisted
|
||||||
|
// and re-used. It is also safe to use concurrently due to its lack of state.
|
||||||
|
// There is also a DefaultClient() method that reads an environment variable.
|
||||||
|
// Please see the documentation for more information and details.
|
||||||
|
client, err := fastly.NewClient("YOUR_FASTLY_API_KEY")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can find the service ID in the Fastly web console.
|
||||||
|
var serviceID = "SU1Z0isxPaozGVKXdv0eY"
|
||||||
|
|
||||||
|
// Get the latest active version
|
||||||
|
latest, err := client.LatestVersion(&fastly.LatestVersionInput{
|
||||||
|
Service: serviceID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone the latest version so we can make changes without affecting the
|
||||||
|
// active configuration.
|
||||||
|
version, err := client.CloneVersion(&fastly.CloneVersionInput{
|
||||||
|
Service: serviceID,
|
||||||
|
Version: latest.Number,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now you can make any changes to the new version. In this example, we will add
|
||||||
|
// a new domain.
|
||||||
|
domain, err := client.CreateDomain(&fastly.CreateDomainInput{
|
||||||
|
Service: serviceID,
|
||||||
|
Version: version.Number,
|
||||||
|
Name: "example.com",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output: "example.com"
|
||||||
|
fmt.Println(domain.Name)
|
||||||
|
|
||||||
|
// Now we can validate that our version is valid.
|
||||||
|
valid, err := client.ValidateVersion(&fastly.ValidateVersionInput{
|
||||||
|
Service: serviceID,
|
||||||
|
Version: version.Number,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
log.Fatal("not valid version")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, activate this new version.
|
||||||
|
activeVersion, err := client.ActivateVersion(&fastly.ActivateVersionInput{
|
||||||
|
Service: serviceID,
|
||||||
|
Version: version.Number,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output: true
|
||||||
|
fmt.Printf("%b", activeVersion.Locked)
|
||||||
|
```
|
||||||
|
|
||||||
|
More information can be found in the
|
||||||
|
[Fastly Godoc](https://godoc.org/github.com/sethvargo/go-fastly).
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
```
|
||||||
|
Copyright 2015 Seth Vargo
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
```
|
|
@ -0,0 +1,266 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Backend represents a backend response from the Fastly API.
|
||||||
|
type Backend struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Address string `mapstructure:"address"`
|
||||||
|
Port uint `mapstructure:"port"`
|
||||||
|
ConnectTimeout uint `mapstructure:"connect_timeout"`
|
||||||
|
MaxConn uint `mapstructure:"max_conn"`
|
||||||
|
ErrorThreshold uint `mapstructure:"error_threshold"`
|
||||||
|
FirstByteTimeout uint `mapstructure:"first_byte_timeout"`
|
||||||
|
BetweenBytesTimeout uint `mapstructure:"between_bytes_timeout"`
|
||||||
|
AutoLoadbalance bool `mapstructure:"auto_loadbalance"`
|
||||||
|
Weight uint `mapstructure:"weight"`
|
||||||
|
RequestCondition string `mapstructure:"request_condition"`
|
||||||
|
HealthCheck string `mapstructure:"healthcheck"`
|
||||||
|
UseSSL bool `mapstructure:"use_ssl"`
|
||||||
|
SSLCheckCert bool `mapstructure:"ssl_check_cert"`
|
||||||
|
SSLHostname string `mapstructure:"ssl_hostname"`
|
||||||
|
SSLCertHostname string `mapstructure:"ssl_cert_hostname"`
|
||||||
|
SSLSNIHostname string `mapstructure:"ssl_sni_hostname"`
|
||||||
|
MinTLSVersion string `mapstructure:"min_tls_version"`
|
||||||
|
MaxTLSVersion string `mapstructure:"max_tls_version"`
|
||||||
|
SSLCiphers []string `mapstructure:"ssl_ciphers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// backendsByName is a sortable list of backends.
|
||||||
|
type backendsByName []*Backend
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s backendsByName) Len() int { return len(s) }
|
||||||
|
func (s backendsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s backendsByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListBackendsInput is used as input to the ListBackends function.
|
||||||
|
type ListBackendsInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListBackends returns the list of backends for the configuration version.
|
||||||
|
func (c *Client) ListBackends(i *ListBackendsInput) ([]*Backend, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/backend", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bs []*Backend
|
||||||
|
if err := decodeJSON(&bs, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(backendsByName(bs))
|
||||||
|
return bs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBackendInput is used as input to the CreateBackend function.
|
||||||
|
type CreateBackendInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Address string `form:"address,omitempty"`
|
||||||
|
Port uint `form:"port,omitempty"`
|
||||||
|
ConnectTimeout uint `form:"connect_timeout,omitempty"`
|
||||||
|
MaxConn uint `form:"max_conn,omitempty"`
|
||||||
|
ErrorThreshold uint `form:"error_threshold,omitempty"`
|
||||||
|
FirstByteTimeout uint `form:"first_byte_timeout,omitempty"`
|
||||||
|
BetweenBytesTimeout uint `form:"between_bytes_timeout,omitempty"`
|
||||||
|
AutoLoadbalance bool `form:"auto_loadbalance,omitempty"`
|
||||||
|
Weight uint `form:"weight,omitempty"`
|
||||||
|
RequestCondition string `form:"request_condition,omitempty"`
|
||||||
|
HealthCheck string `form:"healthcheck,omitempty"`
|
||||||
|
UseSSL bool `form:"use_ssl,omitempty"`
|
||||||
|
SSLCheckCert bool `form:"ssl_check_cert,omitempty"`
|
||||||
|
SSLHostname string `form:"ssl_hostname,omitempty"`
|
||||||
|
SSLCertHostname string `form:"ssl_cert_hostname,omitempty"`
|
||||||
|
SSLSNIHostname string `form:"ssl_sni_hostname,omitempty"`
|
||||||
|
MinTLSVersion string `form:"min_tls_version,omitempty"`
|
||||||
|
MaxTLSVersion string `form:"max_tls_version,omitempty"`
|
||||||
|
SSLCiphers []string `form:"ssl_ciphers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBackend creates a new Fastly backend.
|
||||||
|
func (c *Client) CreateBackend(i *CreateBackendInput) (*Backend, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/backend", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Backend
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBackendInput is used as input to the GetBackend function.
|
||||||
|
type GetBackendInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the backend to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBackend gets the backend configuration with the given parameters.
|
||||||
|
func (c *Client) GetBackend(i *GetBackendInput) (*Backend, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/backend/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Backend
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBackendInput is used as input to the UpdateBackend function.
|
||||||
|
type UpdateBackendInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the backend to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
Address string `form:"address,omitempty"`
|
||||||
|
Port uint `form:"port,omitempty"`
|
||||||
|
ConnectTimeout uint `form:"connect_timeout,omitempty"`
|
||||||
|
MaxConn uint `form:"max_conn,omitempty"`
|
||||||
|
ErrorThreshold uint `form:"error_threshold,omitempty"`
|
||||||
|
FirstByteTimeout uint `form:"first_byte_timeout,omitempty"`
|
||||||
|
BetweenBytesTimeout uint `form:"between_bytes_timeout,omitempty"`
|
||||||
|
AutoLoadbalance bool `form:"auto_loadbalance,omitempty"`
|
||||||
|
Weight uint `form:"weight,omitempty"`
|
||||||
|
RequestCondition string `form:"request_condition,omitempty"`
|
||||||
|
HealthCheck string `form:"healthcheck,omitempty"`
|
||||||
|
UseSSL bool `form:"use_ssl,omitempty"`
|
||||||
|
SSLCheckCert bool `form:"ssl_check_cert,omitempty"`
|
||||||
|
SSLHostname string `form:"ssl_hostname,omitempty"`
|
||||||
|
SSLCertHostname string `form:"ssl_cert_hostname,omitempty"`
|
||||||
|
SSLSNIHostname string `form:"ssl_sni_hostname,omitempty"`
|
||||||
|
MinTLSVersion string `form:"min_tls_version,omitempty"`
|
||||||
|
MaxTLSVersion string `form:"max_tls_version,omitempty"`
|
||||||
|
SSLCiphers []string `form:"ssl_ciphers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBackend updates a specific backend.
|
||||||
|
func (c *Client) UpdateBackend(i *UpdateBackendInput) (*Backend, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/backend/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Backend
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBackendInput is the input parameter to DeleteBackend.
|
||||||
|
type DeleteBackendInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the backend to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBackend deletes the given backend version.
|
||||||
|
func (c *Client) DeleteBackend(i *DeleteBackendInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/backend/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Billing is the top-level representation of a billing response from the Fastly
|
||||||
|
// API.
|
||||||
|
type Billing struct {
|
||||||
|
InvoiceID string `mapstructure:"invoice_id"`
|
||||||
|
StartTime *time.Time `mapstructure:"start_time"`
|
||||||
|
EndTime *time.Time `mapstructure:"end_time"`
|
||||||
|
Status *BillingStatus `mapstructure:"status"`
|
||||||
|
Total *BillingTotal `mapstructure:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingStatus is a representation of the status of the bill from the Fastly
|
||||||
|
// API.
|
||||||
|
type BillingStatus struct {
|
||||||
|
InvoiceID string `mapstructure:"invoice_id"`
|
||||||
|
Status string `mapstructure:"status"`
|
||||||
|
SentAt *time.Time `mapstructure:"sent_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTotal is a repsentation of the status of the usage for this bill from
|
||||||
|
// the Fastly API.
|
||||||
|
type BillingTotal struct {
|
||||||
|
PlanName string `mapstructure:"plan_name"`
|
||||||
|
PlanCode string `mapstructure:"plan_code"`
|
||||||
|
PlanMinimum string `mapstructure:"plan_minimum"`
|
||||||
|
Bandwidth float64 `mapstructure:"bandwidth"`
|
||||||
|
BandwidthCost float64 `mapstructure:"bandwidth_cost"`
|
||||||
|
Requests uint64 `mapstructure:"requests"`
|
||||||
|
RequestsCost float64 `mapstructure:"requests_cost"`
|
||||||
|
IncurredCost float64 `mapstructure:"incurred_cost"`
|
||||||
|
Overage float64 `mapstructure:"overage"`
|
||||||
|
Extras []*BillingExtra `mapstructure:"extras"`
|
||||||
|
ExtrasCost float64 `mapstructure:"extras_cost"`
|
||||||
|
CostBeforeDiscount float64 `mapstructure:"cost_before_discount"`
|
||||||
|
Discount float64 `mapstructure:"discount"`
|
||||||
|
Cost float64 `mapstructure:"cost"`
|
||||||
|
Terms string `mapstructure:"terms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingExtra is a representation of extras (such as SSL addons) from the
|
||||||
|
// Fastly API.
|
||||||
|
type BillingExtra struct {
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Setup float64 `mapstructure:"setup"`
|
||||||
|
Recurring float64 `mapstructure:"recurring"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBillingInput is used as input to the GetBilling function.
|
||||||
|
type GetBillingInput struct {
|
||||||
|
Year uint16
|
||||||
|
Month uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBilling returns the billing information for the current account.
|
||||||
|
func (c *Client) GetBilling(i *GetBillingInput) (*Billing, error) {
|
||||||
|
if i.Year == 0 {
|
||||||
|
return nil, ErrMissingYear
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Month == 0 {
|
||||||
|
return nil, ErrMissingMonth
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/billing/year/%d/month/%02d", i.Year, i.Month)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Billing
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
|
@ -0,0 +1,237 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CacheSettingActionCache sets the cache to cache.
|
||||||
|
CacheSettingActionCache CacheSettingAction = "cache"
|
||||||
|
|
||||||
|
// CacheSettingActionPass sets the cache to pass through.
|
||||||
|
CacheSettingActionPass CacheSettingAction = "pass"
|
||||||
|
|
||||||
|
// CacheSettingActionRestart sets the cache to restart the request.
|
||||||
|
CacheSettingActionRestart CacheSettingAction = "restart"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CacheSettingAction is the type of cache action.
|
||||||
|
type CacheSettingAction string
|
||||||
|
|
||||||
|
// CacheSetting represents a response from Fastly's API for cache settings.
|
||||||
|
type CacheSetting struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Action CacheSettingAction `mapstructure:"action"`
|
||||||
|
TTL uint `mapstructure:"ttl"`
|
||||||
|
StaleTTL uint `mapstructure:"stale_ttl"`
|
||||||
|
CacheCondition string `mapstructure:"cache_condition"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheSettingsByName is a sortable list of cache settings.
|
||||||
|
type cacheSettingsByName []*CacheSetting
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s cacheSettingsByName) Len() int { return len(s) }
|
||||||
|
func (s cacheSettingsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s cacheSettingsByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCacheSettingsInput is used as input to the ListCacheSettings function.
|
||||||
|
type ListCacheSettingsInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCacheSettings returns the list of cache settings for the configuration
|
||||||
|
// version.
|
||||||
|
func (c *Client) ListCacheSettings(i *ListCacheSettingsInput) ([]*CacheSetting, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/cache_settings", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cs []*CacheSetting
|
||||||
|
if err := decodeJSON(&cs, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(cacheSettingsByName(cs))
|
||||||
|
return cs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCacheSettingInput is used as input to the CreateCacheSetting function.
|
||||||
|
type CreateCacheSettingInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Action CacheSettingAction `form:"action,omitempty"`
|
||||||
|
TTL uint `form:"ttl,omitempty"`
|
||||||
|
StaleTTL uint `form:"stale_ttl,omitempty"`
|
||||||
|
CacheCondition string `form:"cache_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCacheSetting creates a new Fastly cache setting.
|
||||||
|
func (c *Client) CreateCacheSetting(i *CreateCacheSettingInput) (*CacheSetting, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/cache_settings", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cs *CacheSetting
|
||||||
|
if err := decodeJSON(&cs, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCacheSettingInput is used as input to the GetCacheSetting function.
|
||||||
|
type GetCacheSettingInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the cache setting to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCacheSetting gets the cache setting configuration with the given
|
||||||
|
// parameters.
|
||||||
|
func (c *Client) GetCacheSetting(i *GetCacheSettingInput) (*CacheSetting, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/cache_settings/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cs *CacheSetting
|
||||||
|
if err := decodeJSON(&cs, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCacheSettingInput is used as input to the UpdateCacheSetting function.
|
||||||
|
type UpdateCacheSettingInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the cache setting to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
Action CacheSettingAction `form:"action,omitempty"`
|
||||||
|
TTL uint `form:"ttl,omitempty"`
|
||||||
|
StateTTL uint `form:"stale_ttl,omitempty"`
|
||||||
|
CacheCondition string `form:"cache_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCacheSetting updates a specific cache setting.
|
||||||
|
func (c *Client) UpdateCacheSetting(i *UpdateCacheSettingInput) (*CacheSetting, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/cache_settings/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cs *CacheSetting
|
||||||
|
if err := decodeJSON(&cs, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCacheSettingInput is the input parameter to DeleteCacheSetting.
|
||||||
|
type DeleteCacheSettingInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the cache setting to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCacheSetting deletes the given cache setting version.
|
||||||
|
func (c *Client) DeleteCacheSetting(i *DeleteCacheSettingInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/cache_settings/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ajg/form"
|
||||||
|
"github.com/hashicorp/go-cleanhttp"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIKeyEnvVar is the name of the environment variable where the Fastly API
|
||||||
|
// key should be read from.
|
||||||
|
const APIKeyEnvVar = "FASTLY_API_KEY"
|
||||||
|
|
||||||
|
// APIKeyHeader is the name of the header that contains the Fastly API key.
|
||||||
|
const APIKeyHeader = "Fastly-Key"
|
||||||
|
|
||||||
|
// DefaultEndpoint is the default endpoint for Fastly. Since Fastly does not
|
||||||
|
// support an on-premise solution, this is likely to always be the default.
|
||||||
|
const DefaultEndpoint = "https://api.fastly.com"
|
||||||
|
|
||||||
|
// ProjectURL is the url for this library.
|
||||||
|
var ProjectURL = "github.com/sethvargo/go-fastly"
|
||||||
|
|
||||||
|
// ProjectVersion is the version of this library.
|
||||||
|
var ProjectVersion = "0.1"
|
||||||
|
|
||||||
|
// UserAgent is the user agent for this particular client.
|
||||||
|
var UserAgent = fmt.Sprintf("FastlyGo/%s (+%s; %s)",
|
||||||
|
ProjectVersion, ProjectURL, runtime.Version())
|
||||||
|
|
||||||
|
// Client is the main entrypoint to the Fastly golang API library.
|
||||||
|
type Client struct {
|
||||||
|
// Address is the address of Fastly's API endpoint.
|
||||||
|
Address string
|
||||||
|
|
||||||
|
// HTTPClient is the HTTP client to use. If one is not provided, a default
|
||||||
|
// client will be used.
|
||||||
|
HTTPClient *http.Client
|
||||||
|
|
||||||
|
// apiKey is the Fastly API key to authenticate requests.
|
||||||
|
apiKey string
|
||||||
|
|
||||||
|
// url is the parsed URL from Address
|
||||||
|
url *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultClient instantiates a new Fastly API client. This function requires
|
||||||
|
// the environment variable `FASTLY_API_KEY` is set and contains a valid API key
|
||||||
|
// to authenticate with Fastly.
|
||||||
|
func DefaultClient() *Client {
|
||||||
|
client, err := NewClient(os.Getenv(APIKeyEnvVar))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new API client with the given key. Because Fastly allows
|
||||||
|
// some requests without an API key, this function will not error if the API
|
||||||
|
// token is not supplied. Attempts to make a request that requires an API key
|
||||||
|
// will return a 403 response.
|
||||||
|
func NewClient(key string) (*Client, error) {
|
||||||
|
client := &Client{apiKey: key}
|
||||||
|
return client.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) init() (*Client, error) {
|
||||||
|
if len(c.Address) == 0 {
|
||||||
|
c.Address = DefaultEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(c.Address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.url = u
|
||||||
|
|
||||||
|
if c.HTTPClient == nil {
|
||||||
|
c.HTTPClient = cleanhttp.DefaultClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get issues an HTTP GET request.
|
||||||
|
func (c *Client) Get(p string, ro *RequestOptions) (*http.Response, error) {
|
||||||
|
return c.Request("GET", p, ro)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head issues an HTTP HEAD request.
|
||||||
|
func (c *Client) Head(p string, ro *RequestOptions) (*http.Response, error) {
|
||||||
|
return c.Request("HEAD", p, ro)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post issues an HTTP POST request.
|
||||||
|
func (c *Client) Post(p string, ro *RequestOptions) (*http.Response, error) {
|
||||||
|
return c.Request("POST", p, ro)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostForm issues an HTTP POST request with the given interface form-encoded.
|
||||||
|
func (c *Client) PostForm(p string, i interface{}, ro *RequestOptions) (*http.Response, error) {
|
||||||
|
return c.RequestForm("POST", p, i, ro)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put issues an HTTP PUT request.
|
||||||
|
func (c *Client) Put(p string, ro *RequestOptions) (*http.Response, error) {
|
||||||
|
return c.Request("PUT", p, ro)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutForm issues an HTTP PUT request with the given interface form-encoded.
|
||||||
|
func (c *Client) PutForm(p string, i interface{}, ro *RequestOptions) (*http.Response, error) {
|
||||||
|
return c.RequestForm("PUT", p, i, ro)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete issues an HTTP DELETE request.
|
||||||
|
func (c *Client) Delete(p string, ro *RequestOptions) (*http.Response, error) {
|
||||||
|
return c.Request("DELETE", p, ro)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request makes an HTTP request against the HTTPClient using the given verb,
|
||||||
|
// Path, and request options.
|
||||||
|
func (c *Client) Request(verb, p string, ro *RequestOptions) (*http.Response, error) {
|
||||||
|
req, err := c.RawRequest(verb, p, ro)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := checkResp(c.HTTPClient.Do(req))
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestForm makes an HTTP request with the given interface being encoded as
|
||||||
|
// form data.
|
||||||
|
func (c *Client) RequestForm(verb, p string, i interface{}, ro *RequestOptions) (*http.Response, error) {
|
||||||
|
values, err := form.EncodeToValues(i)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ro == nil {
|
||||||
|
ro = new(RequestOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ro.Headers == nil {
|
||||||
|
ro.Headers = make(map[string]string)
|
||||||
|
}
|
||||||
|
ro.Headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||||
|
|
||||||
|
// There is a super-jank implementation in the form library where fields with
|
||||||
|
// a "dot" are replaced with "/.". That is then URL encoded and Fastly just
|
||||||
|
// dies. We fix that here.
|
||||||
|
body := strings.Replace(values.Encode(), "%5C.", ".", -1)
|
||||||
|
|
||||||
|
ro.Body = strings.NewReader(body)
|
||||||
|
ro.BodyLength = int64(len(body))
|
||||||
|
|
||||||
|
return c.Request(verb, p, ro)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkResp wraps an HTTP request from the default client and verifies that the
|
||||||
|
// request was successful. A non-200 request returns an error formatted to
|
||||||
|
// included any validation problems or otherwise.
|
||||||
|
func checkResp(resp *http.Response, err error) (*http.Response, error) {
|
||||||
|
// If the err is already there, there was an error higher up the chain, so
|
||||||
|
// just return that.
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case 200, 201, 202, 204, 205, 206:
|
||||||
|
return resp, nil
|
||||||
|
default:
|
||||||
|
return resp, NewHTTPError(resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeJSON is used to decode an HTTP response body into an interface as JSON.
|
||||||
|
func decodeJSON(out interface{}, body io.ReadCloser) error {
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var parsed interface{}
|
||||||
|
dec := json.NewDecoder(body)
|
||||||
|
if err := dec.Decode(&parsed); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
|
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
||||||
|
mapToHTTPHeaderHookFunc(),
|
||||||
|
stringToTimeHookFunc(),
|
||||||
|
),
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
Result: out,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return decoder.Decode(parsed)
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Condition represents a condition response from the Fastly API.
|
||||||
|
type Condition struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Statement string `mapstructure:"statement"`
|
||||||
|
Type string `mapstructure:"type"`
|
||||||
|
Priority int `mapstructure:"priority"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// conditionsByName is a sortable list of conditions.
|
||||||
|
type conditionsByName []*Condition
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s conditionsByName) Len() int { return len(s) }
|
||||||
|
func (s conditionsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s conditionsByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListConditionsInput is used as input to the ListConditions function.
|
||||||
|
type ListConditionsInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListConditions returns the list of conditions for the configuration version.
|
||||||
|
func (c *Client) ListConditions(i *ListConditionsInput) ([]*Condition, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/condition", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cs []*Condition
|
||||||
|
if err := decodeJSON(&cs, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(conditionsByName(cs))
|
||||||
|
return cs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateConditionInput is used as input to the CreateCondition function.
|
||||||
|
type CreateConditionInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Statement string `form:"statement,omitempty"`
|
||||||
|
Type string `form:"type,omitempty"`
|
||||||
|
Priority int `form:"priority,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCondition creates a new Fastly condition.
|
||||||
|
func (c *Client) CreateCondition(i *CreateConditionInput) (*Condition, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/condition", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var co *Condition
|
||||||
|
if err := decodeJSON(&co, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return co, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConditionInput is used as input to the GetCondition function.
|
||||||
|
type GetConditionInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the condition to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCondition gets the condition configuration with the given parameters.
|
||||||
|
func (c *Client) GetCondition(i *GetConditionInput) (*Condition, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/condition/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var co *Condition
|
||||||
|
if err := decodeJSON(&co, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return co, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConditionInput is used as input to the UpdateCondition function.
|
||||||
|
type UpdateConditionInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the condition to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Statement string `form:"statement,omitempty"`
|
||||||
|
Type string `form:"type,omitempty"`
|
||||||
|
Priority int `form:"priority,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCondition updates a specific condition.
|
||||||
|
func (c *Client) UpdateCondition(i *UpdateConditionInput) (*Condition, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/condition/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var co *Condition
|
||||||
|
if err := decodeJSON(&co, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return co, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteConditionInput is the input parameter to DeleteCondition.
|
||||||
|
type DeleteConditionInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the condition to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCondition deletes the given condition version.
|
||||||
|
func (c *Client) DeleteCondition(i *DeleteConditionInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/condition/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// EdgeCheck represents an edge check response from the Fastly API.
|
||||||
|
type EdgeCheck struct {
|
||||||
|
Hash string `mapstructure:"hash"`
|
||||||
|
Server string `mapstructure:"server"`
|
||||||
|
ResponseTime float64 `mapstructure:"response_time"`
|
||||||
|
Request *EdgeCheckRequest `mapstructure:"request"`
|
||||||
|
Response *EdgeCheckResponse `mapstructure:"response"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EdgeCheckRequest is the request part of an EdgeCheck response.
|
||||||
|
type EdgeCheckRequest struct {
|
||||||
|
URL string `mapstructure:"url"`
|
||||||
|
Method string `mapstructure:"method"`
|
||||||
|
Headers *http.Header `mapstructure:"headers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EdgeCheckResponse is the response part of an EdgeCheck response.
|
||||||
|
type EdgeCheckResponse struct {
|
||||||
|
Status uint `mapstructure:"status"`
|
||||||
|
Headers *http.Header `mapstructure:"headers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EdgeCheckInput is used as input to the EdgeCheck function.
|
||||||
|
type EdgeCheckInput struct {
|
||||||
|
URL string `form:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EdgeCheck queries the edge cache for all of Fastly's servers for the given
|
||||||
|
// URL.
|
||||||
|
func (c *Client) EdgeCheck(i *EdgeCheckInput) ([]*EdgeCheck, error) {
|
||||||
|
resp, err := c.Get("/content/edge_check", &RequestOptions{
|
||||||
|
Params: map[string]string{
|
||||||
|
"url": i.URL,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e []*EdgeCheck
|
||||||
|
if err := decodeJSON(&e, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mapToHTTPHeaderHookFunc returns a function that converts maps into an
|
||||||
|
// http.Header value.
|
||||||
|
func mapToHTTPHeaderHookFunc() mapstructure.DecodeHookFunc {
|
||||||
|
return func(
|
||||||
|
f reflect.Type,
|
||||||
|
t reflect.Type,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
if f.Kind() != reflect.Map {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
if t != reflect.TypeOf(new(http.Header)) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
typed, ok := data.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("cannot convert %T to http.Header", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
n := map[string][]string{}
|
||||||
|
for k, v := range typed {
|
||||||
|
switch v.(type) {
|
||||||
|
case string:
|
||||||
|
n[k] = []string{v.(string)}
|
||||||
|
case []string:
|
||||||
|
n[k] = v.([]string)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("cannot convert %T to http.Header", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringToTimeHookFunc returns a function that converts strings to a time.Time
|
||||||
|
// value.
|
||||||
|
func stringToTimeHookFunc() mapstructure.DecodeHookFunc {
|
||||||
|
return func(
|
||||||
|
f reflect.Type,
|
||||||
|
t reflect.Type,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
if f.Kind() != reflect.String {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
if t != reflect.TypeOf(time.Now()) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert it by parsing
|
||||||
|
return time.Parse(time.RFC3339, data.(string))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,206 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dictionary represents a dictionary response from the Fastly API.
|
||||||
|
type Dictionary struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
ID string `mapstructure:"id"`
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Address string `mapstructure:"address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// dictionariesByName is a sortable list of dictionaries.
|
||||||
|
type dictionariesByName []*Dictionary
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s dictionariesByName) Len() int { return len(s) }
|
||||||
|
func (s dictionariesByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s dictionariesByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDictionariesInput is used as input to the ListDictionaries function.
|
||||||
|
type ListDictionariesInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDictionaries returns the list of dictionaries for the configuration version.
|
||||||
|
func (c *Client) ListDictionaries(i *ListDictionariesInput) ([]*Dictionary, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/dictionary", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bs []*Dictionary
|
||||||
|
if err := decodeJSON(&bs, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(dictionariesByName(bs))
|
||||||
|
return bs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDictionaryInput is used as input to the CreateDictionary function.
|
||||||
|
type CreateDictionaryInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDictionary creates a new Fastly dictionary.
|
||||||
|
func (c *Client) CreateDictionary(i *CreateDictionaryInput) (*Dictionary, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/dictionary", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Dictionary
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDictionaryInput is used as input to the GetDictionary function.
|
||||||
|
type GetDictionaryInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the dictionary to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDictionary gets the dictionary configuration with the given parameters.
|
||||||
|
func (c *Client) GetDictionary(i *GetDictionaryInput) (*Dictionary, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/dictionary/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Dictionary
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDictionaryInput is used as input to the UpdateDictionary function.
|
||||||
|
type UpdateDictionaryInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the dictionary to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDictionary updates a specific dictionary.
|
||||||
|
func (c *Client) UpdateDictionary(i *UpdateDictionaryInput) (*Dictionary, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/dictionary/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Dictionary
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDictionaryInput is the input parameter to DeleteDictionary.
|
||||||
|
type DeleteDictionaryInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the dictionary to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDictionary deletes the given dictionary version.
|
||||||
|
func (c *Client) DeleteDictionary(i *DeleteDictionaryInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/dictionary/%s", i.Service, i.Version, i.Name)
|
||||||
|
_, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlike other endpoints, the dictionary endpoint does not return a status
|
||||||
|
// response - it just returns a 200 OK.
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,207 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DictionaryItem represents a dictionary item response from the Fastly API.
|
||||||
|
type DictionaryItem struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
DictionaryID string `mapstructure:"dictionary_id"`
|
||||||
|
|
||||||
|
ItemKey string `mapstructure:"item_key"`
|
||||||
|
ItemValue string `mapstructure:"item_value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// dictionaryItemsByKey is a sortable list of dictionary items.
|
||||||
|
type dictionaryItemsByKey []*DictionaryItem
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s dictionaryItemsByKey) Len() int { return len(s) }
|
||||||
|
func (s dictionaryItemsByKey) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s dictionaryItemsByKey) Less(i, j int) bool {
|
||||||
|
return s[i].ItemKey < s[j].ItemKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDictionaryItemsInput is used as input to the ListDictionaryItems function.
|
||||||
|
type ListDictionaryItemsInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Dictionary is the ID of the dictionary to retrieve items for (required).
|
||||||
|
Dictionary string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDictionaryItems returns the list of dictionary items for the
|
||||||
|
// configuration version.
|
||||||
|
func (c *Client) ListDictionaryItems(i *ListDictionaryItemsInput) ([]*DictionaryItem, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Dictionary == "" {
|
||||||
|
return nil, ErrMissingDictionary
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/dictionary/%s/items", i.Service, i.Dictionary)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bs []*DictionaryItem
|
||||||
|
if err := decodeJSON(&bs, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(dictionaryItemsByKey(bs))
|
||||||
|
return bs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDictionaryItemInput is used as input to the CreateDictionaryItem function.
|
||||||
|
type CreateDictionaryItemInput struct {
|
||||||
|
// Service is the ID of the service. Dictionary is the ID of the dictionary.
|
||||||
|
// Both fields are required.
|
||||||
|
Service string
|
||||||
|
Dictionary string
|
||||||
|
|
||||||
|
ItemKey string `form:"item_key,omitempty"`
|
||||||
|
ItemValue string `form:"item_value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDictionaryItem creates a new Fastly dictionary item.
|
||||||
|
func (c *Client) CreateDictionaryItem(i *CreateDictionaryItemInput) (*DictionaryItem, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Dictionary == "" {
|
||||||
|
return nil, ErrMissingDictionary
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/dictionary/%s/item", i.Service, i.Dictionary)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *DictionaryItem
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDictionaryItemInput is used as input to the GetDictionaryItem function.
|
||||||
|
type GetDictionaryItemInput struct {
|
||||||
|
// Service is the ID of the service. Dictionary is the ID of the dictionary.
|
||||||
|
// Both fields are required.
|
||||||
|
Service string
|
||||||
|
Dictionary string
|
||||||
|
|
||||||
|
// ItemKey is the name of the dictionary item to fetch.
|
||||||
|
ItemKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDictionaryItem gets the dictionary item with the given parameters.
|
||||||
|
func (c *Client) GetDictionaryItem(i *GetDictionaryItemInput) (*DictionaryItem, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Dictionary == "" {
|
||||||
|
return nil, ErrMissingDictionary
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.ItemKey == "" {
|
||||||
|
return nil, ErrMissingItemKey
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/dictionary/%s/item/%s", i.Service, i.Dictionary, i.ItemKey)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *DictionaryItem
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDictionaryItemInput is used as input to the UpdateDictionaryItem function.
|
||||||
|
type UpdateDictionaryItemInput struct {
|
||||||
|
// Service is the ID of the service. Dictionary is the ID of the dictionary.
|
||||||
|
// Both fields are required.
|
||||||
|
Service string
|
||||||
|
Dictionary string
|
||||||
|
|
||||||
|
// ItemKey is the name of the dictionary item to fetch.
|
||||||
|
ItemKey string
|
||||||
|
|
||||||
|
ItemValue string `form:"item_value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDictionaryItem updates a specific dictionary item.
|
||||||
|
func (c *Client) UpdateDictionaryItem(i *UpdateDictionaryItemInput) (*DictionaryItem, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Dictionary == "" {
|
||||||
|
return nil, ErrMissingDictionary
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.ItemKey == "" {
|
||||||
|
return nil, ErrMissingItemKey
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/dictionary/%s/item/%s", i.Service, i.Dictionary, i.ItemKey)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *DictionaryItem
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDictionaryItemInput is the input parameter to DeleteDictionaryItem.
|
||||||
|
type DeleteDictionaryItemInput struct {
|
||||||
|
// Service is the ID of the service. Dictionary is the ID of the dictionary.
|
||||||
|
// Both fields are required.
|
||||||
|
Service string
|
||||||
|
Dictionary string
|
||||||
|
|
||||||
|
// ItemKey is the name of the dictionary item to delete.
|
||||||
|
ItemKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDictionaryItem deletes the given dictionary item.
|
||||||
|
func (c *Client) DeleteDictionaryItem(i *DeleteDictionaryItemInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Dictionary == "" {
|
||||||
|
return ErrMissingDictionary
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.ItemKey == "" {
|
||||||
|
return ErrMissingItemKey
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/dictionary/%s/item/%s", i.Service, i.Dictionary, i.ItemKey)
|
||||||
|
_, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlike other endpoints, the dictionary endpoint does not return a status
|
||||||
|
// response - it just returns a 200 OK.
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Diff represents a diff of two versions as a response from the Fastly API.
|
||||||
|
type Diff struct {
|
||||||
|
Format string `mapstructure:"format"`
|
||||||
|
From string `mapstructure:"from"`
|
||||||
|
To string `mapstructure:"to"`
|
||||||
|
Diff string `mapstructure:"diff"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDiffInput is used as input to the GetDiff function.
|
||||||
|
type GetDiffInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// From is the version to diff from. This can either be a string indicating a
|
||||||
|
// positive number (e.g. "1") or a negative number from "-1" down ("-1" is the
|
||||||
|
// latest version).
|
||||||
|
From string
|
||||||
|
|
||||||
|
// To is the version to diff up to. The same rules for From apply.
|
||||||
|
To string
|
||||||
|
|
||||||
|
// Format is an optional field to specify the format with which the diff will
|
||||||
|
// be returned. Acceptable values are "text" (default), "html", or
|
||||||
|
// "html_simple".
|
||||||
|
Format string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDiff returns the diff of the given versions.
|
||||||
|
func (c *Client) GetDiff(i *GetDiffInput) (*Diff, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.From == "" {
|
||||||
|
return nil, ErrMissingFrom
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.To == "" {
|
||||||
|
return nil, ErrMissingTo
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("service/%s/diff/from/%s/to/%s", i.Service, i.From, i.To)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var d *Diff
|
||||||
|
if err := decodeJSON(&d, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
|
@ -0,0 +1,238 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DirectorTypeRandom is a director that does random direction.
|
||||||
|
DirectorTypeRandom DirectorType = 1
|
||||||
|
|
||||||
|
// DirectorTypeRoundRobin is a director that does round-robin direction.
|
||||||
|
DirectorTypeRoundRobin DirectorType = 2
|
||||||
|
|
||||||
|
// DirectorTypeHash is a director that does hash direction.
|
||||||
|
DirectorTypeHash DirectorType = 3
|
||||||
|
|
||||||
|
// DirectorTypeClient is a director that does client direction.
|
||||||
|
DirectorTypeClient DirectorType = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// DirectorType is a type of director.
|
||||||
|
type DirectorType uint8
|
||||||
|
|
||||||
|
// Director represents a director response from the Fastly API.
|
||||||
|
type Director struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Comment string `mapstructure:"comment"`
|
||||||
|
Quorum uint `mapstructure:"quorum"`
|
||||||
|
Type DirectorType `mapstructure:"type"`
|
||||||
|
Retries uint `mapstructure:"retries"`
|
||||||
|
Capacity uint `mapstructure:"capacity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// directorsByName is a sortable list of directors.
|
||||||
|
type directorsByName []*Director
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s directorsByName) Len() int { return len(s) }
|
||||||
|
func (s directorsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s directorsByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDirectorsInput is used as input to the ListDirectors function.
|
||||||
|
type ListDirectorsInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDirectors returns the list of directors for the configuration version.
|
||||||
|
func (c *Client) ListDirectors(i *ListDirectorsInput) ([]*Director, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/director", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ds []*Director
|
||||||
|
if err := decodeJSON(&ds, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(directorsByName(ds))
|
||||||
|
return ds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDirectorInput is used as input to the CreateDirector function.
|
||||||
|
type CreateDirectorInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Comment string `form:"comment,omitempty"`
|
||||||
|
Quorum uint `form:"quorum,omitempty"`
|
||||||
|
Type DirectorType `form:"type,omitempty"`
|
||||||
|
Retries uint `form:"retries,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDirector creates a new Fastly director.
|
||||||
|
func (c *Client) CreateDirector(i *CreateDirectorInput) (*Director, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/director", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var d *Director
|
||||||
|
if err := decodeJSON(&d, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDirectorInput is used as input to the GetDirector function.
|
||||||
|
type GetDirectorInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the director to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDirector gets the director configuration with the given parameters.
|
||||||
|
func (c *Client) GetDirector(i *GetDirectorInput) (*Director, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/director/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var d *Director
|
||||||
|
if err := decodeJSON(&d, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDirectorInput is used as input to the UpdateDirector function.
|
||||||
|
type UpdateDirectorInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the director to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Comment string `form:"comment,omitempty"`
|
||||||
|
Quorum uint `form:"quorum,omitempty"`
|
||||||
|
Type DirectorType `form:"type,omitempty"`
|
||||||
|
Retries uint `form:"retries,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDirector updates a specific director.
|
||||||
|
func (c *Client) UpdateDirector(i *UpdateDirectorInput) (*Director, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/director/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var d *Director
|
||||||
|
if err := decodeJSON(&d, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDirectorInput is the input parameter to DeleteDirector.
|
||||||
|
type DeleteDirectorInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the director to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDirector deletes the given director version.
|
||||||
|
func (c *Client) DeleteDirector(i *DeleteDirectorInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/director/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DirectorBackend is the relationship between a director and a backend in the
|
||||||
|
// Fastly API.
|
||||||
|
type DirectorBackend struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Director string `mapstructure:"director_name"`
|
||||||
|
Backend string `mapstructure:"backend_name"`
|
||||||
|
CreatedAt *time.Time `mapstructure:"created_at"`
|
||||||
|
UpdatedAt *time.Time `mapstructure:"updated_at"`
|
||||||
|
DeletedAt *time.Time `mapstructure:"deleted_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDirectorBackendInput is used as input to the CreateDirectorBackend
|
||||||
|
// function.
|
||||||
|
type CreateDirectorBackendInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Director is the name of the director (required).
|
||||||
|
Director string
|
||||||
|
|
||||||
|
// Backend is the name of the backend (required).
|
||||||
|
Backend string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDirectorBackend creates a new Fastly backend.
|
||||||
|
func (c *Client) CreateDirectorBackend(i *CreateDirectorBackendInput) (*DirectorBackend, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Director == "" {
|
||||||
|
return nil, ErrMissingDirector
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Backend == "" {
|
||||||
|
return nil, ErrMissingBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/director/%s/backend/%s",
|
||||||
|
i.Service, i.Version, i.Director, i.Backend)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *DirectorBackend
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDirectorBackendInput is used as input to the GetDirectorBackend function.
|
||||||
|
type GetDirectorBackendInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Director is the name of the director (required).
|
||||||
|
Director string
|
||||||
|
|
||||||
|
// Backend is the name of the backend (required).
|
||||||
|
Backend string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDirectorBackend gets the backend configuration with the given parameters.
|
||||||
|
func (c *Client) GetDirectorBackend(i *GetDirectorBackendInput) (*DirectorBackend, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Director == "" {
|
||||||
|
return nil, ErrMissingDirector
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Backend == "" {
|
||||||
|
return nil, ErrMissingBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/director/%s/backend/%s",
|
||||||
|
i.Service, i.Version, i.Director, i.Backend)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *DirectorBackend
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDirectorBackendInput is the input parameter to DeleteDirectorBackend.
|
||||||
|
type DeleteDirectorBackendInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Director is the name of the director (required).
|
||||||
|
Director string
|
||||||
|
|
||||||
|
// Backend is the name of the backend (required).
|
||||||
|
Backend string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDirectorBackend deletes the given backend version.
|
||||||
|
func (c *Client) DeleteDirectorBackend(i *DeleteDirectorBackendInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Director == "" {
|
||||||
|
return ErrMissingDirector
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Backend == "" {
|
||||||
|
return ErrMissingBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/director/%s/backend/%s",
|
||||||
|
i.Service, i.Version, i.Director, i.Backend)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,211 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Domain represents the the domain name Fastly will serve content for.
|
||||||
|
type Domain struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Comment string `mapstructure:"comment"`
|
||||||
|
Locked bool `mapstructure:"locked"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// domainsByName is a sortable list of backends.
|
||||||
|
type domainsByName []*Domain
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s domainsByName) Len() int { return len(s) }
|
||||||
|
func (s domainsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s domainsByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDomainsInput is used as input to the ListDomains function.
|
||||||
|
type ListDomainsInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDomains returns the list of domains for this account.
|
||||||
|
func (c *Client) ListDomains(i *ListDomainsInput) ([]*Domain, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/domain", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ds []*Domain
|
||||||
|
if err := decodeJSON(&ds, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(domainsByName(ds))
|
||||||
|
return ds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDomainInput is used as input to the CreateDomain function.
|
||||||
|
type CreateDomainInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the domain that the service will respond to (required).
|
||||||
|
Name string `form:"name"`
|
||||||
|
|
||||||
|
// Comment is a personal, freeform descriptive note.
|
||||||
|
Comment string `form:"comment,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDomain creates a new domain with the given information.
|
||||||
|
func (c *Client) CreateDomain(i *CreateDomainInput) (*Domain, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/domain", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var d *Domain
|
||||||
|
if err := decodeJSON(&d, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomainInput is used as input to the GetDomain function.
|
||||||
|
type GetDomainInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the domain to fetch.
|
||||||
|
Name string `form:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomain retrieves information about the given domain name.
|
||||||
|
func (c *Client) GetDomain(i *GetDomainInput) (*Domain, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/domain/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var d *Domain
|
||||||
|
if err := decodeJSON(&d, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDomainInput is used as input to the UpdateDomain function.
|
||||||
|
type UpdateDomainInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the domain that the service will respond to (required).
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// NewName is the updated name of the domain
|
||||||
|
NewName string `form:"name"`
|
||||||
|
|
||||||
|
// Comment is a personal, freeform descriptive note.
|
||||||
|
Comment string `form:"comment,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDomain updates a single domain for the current service. The only allowed
|
||||||
|
// parameters are `Name` and `Comment`.
|
||||||
|
func (c *Client) UpdateDomain(i *UpdateDomainInput) (*Domain, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/domain/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var d *Domain
|
||||||
|
if err := decodeJSON(&d, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDomainInput is used as input to the DeleteDomain function.
|
||||||
|
type DeleteDomainInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the domain that the service will respond to (required).
|
||||||
|
Name string `form:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDomain removes a single domain by the given name.
|
||||||
|
func (c *Client) DeleteDomain(i *DeleteDomainInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/domain/%s", i.Service, i.Version, i.Name)
|
||||||
|
_, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrMissingService is an error that is returned when an input struct requires
|
||||||
|
// a "Service" key, but one was not set.
|
||||||
|
var ErrMissingService = errors.New("Missing required field 'Service'")
|
||||||
|
|
||||||
|
// ErrMissingVersion is an error that is returned when an input struct requires
|
||||||
|
// a "Version" key, but one was not set.
|
||||||
|
var ErrMissingVersion = errors.New("Missing required field 'Version'")
|
||||||
|
|
||||||
|
// ErrMissingName is an error that is returned when an input struct requires a
|
||||||
|
// "Name" key, but one was not set.
|
||||||
|
var ErrMissingName = errors.New("Missing required field 'Name'")
|
||||||
|
|
||||||
|
// ErrMissingKey is an error that is returned when an input struct requires a
|
||||||
|
// "Name" key, but one was not set.
|
||||||
|
var ErrMissingKey = errors.New("Missing required field 'Key'")
|
||||||
|
|
||||||
|
// ErrMissingURL is an error that is returned when an input struct requires a
|
||||||
|
// "Name" key, but one was not set.
|
||||||
|
var ErrMissingURL = errors.New("Missing required field 'URL'")
|
||||||
|
|
||||||
|
// ErrMissingID is an error that is returned when an input struct requires an
|
||||||
|
// "ID" key, but one was not set.
|
||||||
|
var ErrMissingID = errors.New("Missing required field 'ID'")
|
||||||
|
|
||||||
|
// ErrMissingDictionary is an error that is returned when an input struct
|
||||||
|
// requires a "Dictionary" key, but one was not set.
|
||||||
|
var ErrMissingDictionary = errors.New("Missing required field 'Dictionary'")
|
||||||
|
|
||||||
|
// ErrMissingItemKey is an error that is returned when an input struct
|
||||||
|
// requires a "ItemKey" key, but one was not set.
|
||||||
|
var ErrMissingItemKey = errors.New("Missing required field 'ItemKey'")
|
||||||
|
|
||||||
|
// ErrMissingFrom is an error that is returned when an input struct
|
||||||
|
// requires a "From" key, but one was not set.
|
||||||
|
var ErrMissingFrom = errors.New("Missing required field 'From'")
|
||||||
|
|
||||||
|
// ErrMissingTo is an error that is returned when an input struct
|
||||||
|
// requires a "To" key, but one was not set.
|
||||||
|
var ErrMissingTo = errors.New("Missing required field 'To'")
|
||||||
|
|
||||||
|
// ErrMissingDirector is an error that is returned when an input struct
|
||||||
|
// requires a "From" key, but one was not set.
|
||||||
|
var ErrMissingDirector = errors.New("Missing required field 'Director'")
|
||||||
|
|
||||||
|
// ErrMissingBackend is an error that is returned when an input struct
|
||||||
|
// requires a "Backend" key, but one was not set.
|
||||||
|
var ErrMissingBackend = errors.New("Missing required field 'Backend'")
|
||||||
|
|
||||||
|
// ErrMissingYear is an error that is returned when an input struct
|
||||||
|
// requires a "Year" key, but one was not set.
|
||||||
|
var ErrMissingYear = errors.New("Missing required field 'Year'")
|
||||||
|
|
||||||
|
// ErrMissingMonth is an error that is returned when an input struct
|
||||||
|
// requires a "Month" key, but one was not set.
|
||||||
|
var ErrMissingMonth = errors.New("Missing required field 'Month'")
|
||||||
|
|
||||||
|
// Ensure HTTPError is, in fact, an error.
|
||||||
|
var _ error = (*HTTPError)(nil)
|
||||||
|
|
||||||
|
// HTTPError is a custom error type that wraps an HTTP status code with some
|
||||||
|
// helper functions.
|
||||||
|
type HTTPError struct {
|
||||||
|
// StatusCode is the HTTP status code (2xx-5xx).
|
||||||
|
StatusCode int
|
||||||
|
|
||||||
|
// Message and Detail are information returned by the Fastly API.
|
||||||
|
Message string `mapstructure:"msg"`
|
||||||
|
Detail string `mapstructure:"detail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPError creates a new HTTP error from the given code.
|
||||||
|
func NewHTTPError(resp *http.Response) *HTTPError {
|
||||||
|
var e *HTTPError
|
||||||
|
if resp.Body != nil {
|
||||||
|
decodeJSON(&e, resp.Body)
|
||||||
|
}
|
||||||
|
e.StatusCode = resp.StatusCode
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface and returns the string representing the
|
||||||
|
// error text that includes the status code and the corresponding status text.
|
||||||
|
func (e *HTTPError) Error() string {
|
||||||
|
var r bytes.Buffer
|
||||||
|
fmt.Fprintf(&r, "%d - %s", e.StatusCode, http.StatusText(e.StatusCode))
|
||||||
|
|
||||||
|
if e.Message != "" {
|
||||||
|
fmt.Fprintf(&r, "\nMessage: %s", e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Detail != "" {
|
||||||
|
fmt.Fprintf(&r, "\nDetail: %s", e.Detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements the stringer interface and returns the string representing
|
||||||
|
// the string text that includes the status code and corresponding status text.
|
||||||
|
func (e *HTTPError) String() string {
|
||||||
|
return e.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotFound returns true if the HTTP error code is a 404, false otherwise.
|
||||||
|
func (e *HTTPError) IsNotFound() bool {
|
||||||
|
return e.StatusCode == 404
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding"
|
||||||
|
)
|
||||||
|
|
||||||
|
type statusResp struct {
|
||||||
|
Status string
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *statusResp) Ok() bool {
|
||||||
|
return t.Status == "ok"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure Compatibool implements the proper interfaces.
|
||||||
|
var (
|
||||||
|
_ encoding.TextMarshaler = new(Compatibool)
|
||||||
|
_ encoding.TextUnmarshaler = new(Compatibool)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compatibool is a boolean value that marshalls to 0/1 instead of true/false
|
||||||
|
// for compatability with Fastly's API.
|
||||||
|
type Compatibool bool
|
||||||
|
|
||||||
|
// MarshalText implements the encoding.TextMarshaler interface.
|
||||||
|
func (b Compatibool) MarshalText() ([]byte, error) {
|
||||||
|
if b {
|
||||||
|
return []byte("1"), nil
|
||||||
|
}
|
||||||
|
return []byte("0"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||||
|
func (b Compatibool) UnmarshalText(t []byte) error {
|
||||||
|
if bytes.Equal(t, []byte("1")) {
|
||||||
|
b = Compatibool(true)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,243 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FTP represents an FTP logging response from the Fastly API.
|
||||||
|
type FTP struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Address string `mapstructure:"address"`
|
||||||
|
Port uint `mapstructure:"port"`
|
||||||
|
Username string `mapstructure:"user"`
|
||||||
|
Password string `mapstructure:"password"`
|
||||||
|
Directory string `mapstructure:"directory"`
|
||||||
|
Period uint `mapstructure:"period"`
|
||||||
|
GzipLevel uint8 `mapstructure:"gzip_level"`
|
||||||
|
Format string `mapstructure:"format"`
|
||||||
|
ResponseCondition string `mapstructure:"response_condition"`
|
||||||
|
TimestampFormat string `mapstructure:"timestamp_format"`
|
||||||
|
CreatedAt *time.Time `mapstructure:"created_at"`
|
||||||
|
UpdatedAt *time.Time `mapstructure:"updated_at"`
|
||||||
|
DeletedAt *time.Time `mapstructure:"deleted_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ftpsByName is a sortable list of ftps.
|
||||||
|
type ftpsByName []*FTP
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s ftpsByName) Len() int { return len(s) }
|
||||||
|
func (s ftpsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s ftpsByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListFTPsInput is used as input to the ListFTPs function.
|
||||||
|
type ListFTPsInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListFTPs returns the list of ftps for the configuration version.
|
||||||
|
func (c *Client) ListFTPs(i *ListFTPsInput) ([]*FTP, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/ftp", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ftps []*FTP
|
||||||
|
if err := decodeJSON(&ftps, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(ftpsByName(ftps))
|
||||||
|
return ftps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFTPInput is used as input to the CreateFTP function.
|
||||||
|
type CreateFTPInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Address string `form:"address,omitempty"`
|
||||||
|
Port uint `form:"port,omitempty"`
|
||||||
|
Username string `form:"user,omitempty"`
|
||||||
|
Password string `form:"password,omitempty"`
|
||||||
|
Directory string `form:"directory,omitempty"`
|
||||||
|
Period uint `form:"period,omitempty"`
|
||||||
|
GzipLevel uint8 `form:"gzip_level,omitempty"`
|
||||||
|
Format string `form:"format,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
TimestampFormat string `form:"timestamp_format,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFTP creates a new Fastly FTP.
|
||||||
|
func (c *Client) CreateFTP(i *CreateFTPInput) (*FTP, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/ftp", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ftp *FTP
|
||||||
|
if err := decodeJSON(&ftp, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ftp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFTPInput is used as input to the GetFTP function.
|
||||||
|
type GetFTPInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the FTP to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFTP gets the FTP configuration with the given parameters.
|
||||||
|
func (c *Client) GetFTP(i *GetFTPInput) (*FTP, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/ftp/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *FTP
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFTPInput is used as input to the UpdateFTP function.
|
||||||
|
type UpdateFTPInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the FTP to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
Address string `form:"address,omitempty"`
|
||||||
|
Port uint `form:"port,omitempty"`
|
||||||
|
Username string `form:"user,omitempty"`
|
||||||
|
Password string `form:"password,omitempty"`
|
||||||
|
Directory string `form:"directory,omitempty"`
|
||||||
|
Period uint `form:"period,omitempty"`
|
||||||
|
GzipLevel uint8 `form:"gzip_level,omitempty"`
|
||||||
|
Format string `form:"format,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
TimestampFormat string `form:"timestamp_format,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFTP updates a specific FTP.
|
||||||
|
func (c *Client) UpdateFTP(i *UpdateFTPInput) (*FTP, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/ftp/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *FTP
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFTPInput is the input parameter to DeleteFTP.
|
||||||
|
type DeleteFTPInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the FTP to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFTP deletes the given FTP version.
|
||||||
|
func (c *Client) DeleteFTP(i *DeleteFTPInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/ftp/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,285 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// HeaderActionSet is a header action that sets or resets a header.
|
||||||
|
HeaderActionSet HeaderAction = "set"
|
||||||
|
|
||||||
|
// HeaderActionAppend is a header action that appends to an existing header.
|
||||||
|
HeaderActionAppend HeaderAction = "append"
|
||||||
|
|
||||||
|
// HeaderActionDelete is a header action that deletes a header.
|
||||||
|
HeaderActionDelete HeaderAction = "delete"
|
||||||
|
|
||||||
|
// HeaderActionRegex is a header action that performs a single regex
|
||||||
|
// replacement on a header.
|
||||||
|
HeaderActionRegex HeaderAction = "regex"
|
||||||
|
|
||||||
|
// HeaderActionRegexRepeat is a header action that performs a global regex
|
||||||
|
// replacement on a header.
|
||||||
|
HeaderActionRegexRepeat HeaderAction = "regex_repeat"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeaderAction is a type of header action.
|
||||||
|
type HeaderAction string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// HeaderTypeRequest is a header type that performs on the request before
|
||||||
|
// lookups.
|
||||||
|
HeaderTypeRequest HeaderType = "request"
|
||||||
|
|
||||||
|
// HeaderTypeFetch is a header type that performs on the request to the origin
|
||||||
|
// server.
|
||||||
|
HeaderTypeFetch HeaderType = "fetch"
|
||||||
|
|
||||||
|
// HeaderTypeCache is a header type that performs on the response before it's
|
||||||
|
// store in the cache.
|
||||||
|
HeaderTypeCache HeaderType = "cache"
|
||||||
|
|
||||||
|
// HeaderTypeResponse is a header type that performs on the response before
|
||||||
|
// delivering to the client.
|
||||||
|
HeaderTypeResponse HeaderType = "response"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeaderType is a type of header.
|
||||||
|
type HeaderType string
|
||||||
|
|
||||||
|
// Header represents a header response from the Fastly API.
|
||||||
|
type Header struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Action HeaderAction `mapstructure:"action"`
|
||||||
|
IgnoreIfSet bool `mapstructure:"ignore_if_set"`
|
||||||
|
Type HeaderType `mapstructure:"type"`
|
||||||
|
Destination string `mapstructure:"dst"`
|
||||||
|
Source string `mapstructure:"src"`
|
||||||
|
Regex string `mapstructure:"regex"`
|
||||||
|
Substitution string `mapstructure:"substitution"`
|
||||||
|
Priority uint `mapstructure:"priority"`
|
||||||
|
RequestCondition string `mapstructure:"request_condition"`
|
||||||
|
CacheCondition string `mapstructure:"cache_condition"`
|
||||||
|
ResponseCondition string `mapstructure:"response_condition"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// headersByName is a sortable list of headers.
|
||||||
|
type headersByName []*Header
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s headersByName) Len() int { return len(s) }
|
||||||
|
func (s headersByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s headersByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListHeadersInput is used as input to the ListHeaders function.
|
||||||
|
type ListHeadersInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListHeaders returns the list of headers for the configuration version.
|
||||||
|
func (c *Client) ListHeaders(i *ListHeadersInput) ([]*Header, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/header", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bs []*Header
|
||||||
|
if err := decodeJSON(&bs, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(headersByName(bs))
|
||||||
|
return bs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHeaderInput is used as input to the CreateHeader function.
|
||||||
|
type CreateHeaderInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Action HeaderAction `form:"action,omitempty"`
|
||||||
|
IgnoreIfSet bool `form:"ignore_if_set,omitempty"`
|
||||||
|
Type HeaderType `form:"type,omitempty"`
|
||||||
|
Destination string `form:"dst,omitempty"`
|
||||||
|
Source string `form:"src,omitempty"`
|
||||||
|
Regex string `form:"regex,omitempty"`
|
||||||
|
Substitution string `form:"substitution,omitempty"`
|
||||||
|
Priority uint `form:"priority,omitempty"`
|
||||||
|
RequestCondition string `form:"request_condition,omitempty"`
|
||||||
|
CacheCondition string `form:"cache_condition,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHeader creates a new Fastly header.
|
||||||
|
func (c *Client) CreateHeader(i *CreateHeaderInput) (*Header, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/header", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Header
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeaderInput is used as input to the GetHeader function.
|
||||||
|
type GetHeaderInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the header to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeader gets the header configuration with the given parameters.
|
||||||
|
func (c *Client) GetHeader(i *GetHeaderInput) (*Header, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/header/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Header
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHeaderInput is used as input to the UpdateHeader function.
|
||||||
|
type UpdateHeaderInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the header to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
Action HeaderAction `form:"action,omitempty"`
|
||||||
|
IgnoreIfSet bool `form:"ignore_if_set,omitempty"`
|
||||||
|
Type HeaderType `form:"type,omitempty"`
|
||||||
|
Destination string `form:"dst,omitempty"`
|
||||||
|
Source string `form:"src,omitempty"`
|
||||||
|
Regex string `form:"regex,omitempty"`
|
||||||
|
Substitution string `form:"substitution,omitempty"`
|
||||||
|
Priority uint `form:"priority,omitempty"`
|
||||||
|
RequestCondition string `form:"request_condition,omitempty"`
|
||||||
|
CacheCondition string `form:"cache_condition,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHeader updates a specific header.
|
||||||
|
func (c *Client) UpdateHeader(i *UpdateHeaderInput) (*Header, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/header/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Header
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteHeaderInput is the input parameter to DeleteHeader.
|
||||||
|
type DeleteHeaderInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the header to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteHeader deletes the given header version.
|
||||||
|
func (c *Client) DeleteHeader(i *DeleteHeaderInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/header/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,240 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HealthCheck represents a health check response from the Fastly API.
|
||||||
|
type HealthCheck struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Method string `mapstructure:"method"`
|
||||||
|
Host string `mapstructure:"host"`
|
||||||
|
Path string `mapstructure:"path"`
|
||||||
|
HTTPVersion string `mapstructure:"http_version"`
|
||||||
|
Timeout uint `mapstructure:"timeout"`
|
||||||
|
CheckInterval uint `mapstructure:"check_interval"`
|
||||||
|
ExpectedResponse uint `mapstructure:"expected_response"`
|
||||||
|
Window uint `mapstructure:"window"`
|
||||||
|
Threshold uint `mapstructure:"threshold"`
|
||||||
|
Initial uint `mapstructure:"initial"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// healthChecksByName is a sortable list of health checks.
|
||||||
|
type healthChecksByName []*HealthCheck
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s healthChecksByName) Len() int { return len(s) }
|
||||||
|
func (s healthChecksByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s healthChecksByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListHealthChecksInput is used as input to the ListHealthChecks function.
|
||||||
|
type ListHealthChecksInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListHealthChecks returns the list of health checks for the configuration
|
||||||
|
// version.
|
||||||
|
func (c *Client) ListHealthChecks(i *ListHealthChecksInput) ([]*HealthCheck, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/healthcheck", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var hcs []*HealthCheck
|
||||||
|
if err := decodeJSON(&hcs, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(healthChecksByName(hcs))
|
||||||
|
return hcs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHealthCheckInput is used as input to the CreateHealthCheck function.
|
||||||
|
type CreateHealthCheckInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Method string `form:"method,omitempty"`
|
||||||
|
Host string `form:"host,omitempty"`
|
||||||
|
Path string `form:"path,omitempty"`
|
||||||
|
HTTPVersion string `form:"http_version,omitempty"`
|
||||||
|
Timeout uint `form:"timeout,omitempty"`
|
||||||
|
CheckInterval uint `form:"check_interval,omitempty"`
|
||||||
|
ExpectedResponse uint `form:"expected_response,omitempty"`
|
||||||
|
Window uint `form:"window,omitempty"`
|
||||||
|
Threshold uint `form:"threshold,omitempty"`
|
||||||
|
Initial uint `form:"initial,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHealthCheck creates a new Fastly health check.
|
||||||
|
func (c *Client) CreateHealthCheck(i *CreateHealthCheckInput) (*HealthCheck, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/healthcheck", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var h *HealthCheck
|
||||||
|
if err := decodeJSON(&h, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHealthCheckInput is used as input to the GetHealthCheck function.
|
||||||
|
type GetHealthCheckInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the health check to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHealthCheck gets the health check configuration with the given parameters.
|
||||||
|
func (c *Client) GetHealthCheck(i *GetHealthCheckInput) (*HealthCheck, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/healthcheck/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var h *HealthCheck
|
||||||
|
if err := decodeJSON(&h, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHealthCheckInput is used as input to the UpdateHealthCheck function.
|
||||||
|
type UpdateHealthCheckInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the health check to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
Method string `form:"method,omitempty"`
|
||||||
|
Host string `form:"host,omitempty"`
|
||||||
|
Path string `form:"path,omitempty"`
|
||||||
|
HTTPVersion string `form:"http_version,omitempty"`
|
||||||
|
Timeout uint `form:"timeout,omitempty"`
|
||||||
|
CheckInterval uint `form:"check_interval,omitempty"`
|
||||||
|
ExpectedResponse uint `form:"expected_response,omitempty"`
|
||||||
|
Window uint `form:"window,omitempty"`
|
||||||
|
Threshold uint `form:"threshold,omitempty"`
|
||||||
|
Initial uint `form:"initial,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHealthCheck updates a specific health check.
|
||||||
|
func (c *Client) UpdateHealthCheck(i *UpdateHealthCheckInput) (*HealthCheck, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/healthcheck/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var h *HealthCheck
|
||||||
|
if err := decodeJSON(&h, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteHealthCheckInput is the input parameter to DeleteHealthCheck.
|
||||||
|
type DeleteHealthCheckInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the health check to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteHealthCheck deletes the given health check.
|
||||||
|
func (c *Client) DeleteHealthCheck(i *DeleteHealthCheckInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/healthcheck/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
// IPAddrs is a sortable list of IP addresses returned by the Fastly API.
|
||||||
|
type IPAddrs []string
|
||||||
|
|
||||||
|
// IPs returns the list of public IP addresses for Fastly's network.
|
||||||
|
func (c *Client) IPs() (IPAddrs, error) {
|
||||||
|
resp, err := c.Get("/public-ip-list", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var m map[string][]string
|
||||||
|
if err := decodeJSON(&m, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return IPAddrs(m["addresses"]), nil
|
||||||
|
}
|
|
@ -0,0 +1,228 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logentries represents a logentries response from the Fastly API.
|
||||||
|
type Logentries struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Port uint `mapstructure:"port"`
|
||||||
|
UseTLS bool `mapstructure:"use_tls"`
|
||||||
|
Token string `mapstructure:"token"`
|
||||||
|
Format string `mapstructure:"format"`
|
||||||
|
ResponseCondition string `mapstructure:"response_condition"`
|
||||||
|
CreatedAt *time.Time `mapstructure:"created_at"`
|
||||||
|
UpdatedAt *time.Time `mapstructure:"updated_at"`
|
||||||
|
DeletedAt *time.Time `mapstructure:"deleted_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// logentriesByName is a sortable list of logentries.
|
||||||
|
type logentriesByName []*Logentries
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s logentriesByName) Len() int { return len(s) }
|
||||||
|
func (s logentriesByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s logentriesByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLogentriesInput is used as input to the ListLogentries function.
|
||||||
|
type ListLogentriesInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLogentries returns the list of logentries for the configuration version.
|
||||||
|
func (c *Client) ListLogentries(i *ListLogentriesInput) ([]*Logentries, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/logentries", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ls []*Logentries
|
||||||
|
if err := decodeJSON(&ls, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(logentriesByName(ls))
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLogentriesInput is used as input to the CreateLogentries function.
|
||||||
|
type CreateLogentriesInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Port uint `form:"port,omitempty"`
|
||||||
|
UseTLS Compatibool `form:"use_tls,omitempty"`
|
||||||
|
Token string `form:"token,omitempty"`
|
||||||
|
Format string `form:"format,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLogentries creates a new Fastly logentries.
|
||||||
|
func (c *Client) CreateLogentries(i *CreateLogentriesInput) (*Logentries, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/logentries", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var l *Logentries
|
||||||
|
if err := decodeJSON(&l, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogentriesInput is used as input to the GetLogentries function.
|
||||||
|
type GetLogentriesInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the logentries to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogentries gets the logentries configuration with the given parameters.
|
||||||
|
func (c *Client) GetLogentries(i *GetLogentriesInput) (*Logentries, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/logentries/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var l *Logentries
|
||||||
|
if err := decodeJSON(&l, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLogentriesInput is used as input to the UpdateLogentries function.
|
||||||
|
type UpdateLogentriesInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the logentries to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
Port uint `form:"port,omitempty"`
|
||||||
|
UseTLS Compatibool `form:"use_tls,omitempty"`
|
||||||
|
Token string `form:"token,omitempty"`
|
||||||
|
Format string `form:"format,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLogentries updates a specific logentries.
|
||||||
|
func (c *Client) UpdateLogentries(i *UpdateLogentriesInput) (*Logentries, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/logentries/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var l *Logentries
|
||||||
|
if err := decodeJSON(&l, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLogentriesInput is the input parameter to DeleteLogentries.
|
||||||
|
type DeleteLogentriesInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the logentries to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLogentries deletes the given logentries version.
|
||||||
|
func (c *Client) DeleteLogentries(i *DeleteLogentriesInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/logentries/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,231 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Papertrail represents a papertrail response from the Fastly API.
|
||||||
|
type Papertrail struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Address string `mapstructure:"address"`
|
||||||
|
Port uint `mapstructure:"port"`
|
||||||
|
Format string `mapstructure:"format"`
|
||||||
|
ResponseCondition string `mapstructure:"response_condition"`
|
||||||
|
CreatedAt *time.Time `mapstructure:"created_at"`
|
||||||
|
UpdatedAt *time.Time `mapstructure:"updated_at"`
|
||||||
|
DeletedAt *time.Time `mapstructure:"deleted_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// papertrailsByName is a sortable list of papertrails.
|
||||||
|
type papertrailsByName []*Papertrail
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s papertrailsByName) Len() int { return len(s) }
|
||||||
|
func (s papertrailsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s papertrailsByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPapertrailsInput is used as input to the ListPapertrails function.
|
||||||
|
type ListPapertrailsInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPapertrails returns the list of papertrails for the configuration version.
|
||||||
|
func (c *Client) ListPapertrails(i *ListPapertrailsInput) ([]*Papertrail, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/papertrail", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ps []*Papertrail
|
||||||
|
if err := decodeJSON(&ps, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(papertrailsByName(ps))
|
||||||
|
return ps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePapertrailInput is used as input to the CreatePapertrail function.
|
||||||
|
type CreatePapertrailInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Address string `form:"address,omitempty"`
|
||||||
|
Port uint `form:"port,omitempty"`
|
||||||
|
Format string `form:"format,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
CreatedAt *time.Time `form:"created_at,omitempty"`
|
||||||
|
UpdatedAt *time.Time `form:"updated_at,omitempty"`
|
||||||
|
DeletedAt *time.Time `form:"deleted_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePapertrail creates a new Fastly papertrail.
|
||||||
|
func (c *Client) CreatePapertrail(i *CreatePapertrailInput) (*Papertrail, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/papertrail", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var p *Papertrail
|
||||||
|
if err := decodeJSON(&p, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPapertrailInput is used as input to the GetPapertrail function.
|
||||||
|
type GetPapertrailInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the papertrail to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPapertrail gets the papertrail configuration with the given parameters.
|
||||||
|
func (c *Client) GetPapertrail(i *GetPapertrailInput) (*Papertrail, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/papertrail/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var p *Papertrail
|
||||||
|
if err := decodeJSON(&p, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePapertrailInput is used as input to the UpdatePapertrail function.
|
||||||
|
type UpdatePapertrailInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the papertrail to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
Address string `form:"address,omitempty"`
|
||||||
|
Port uint `form:"port,omitempty"`
|
||||||
|
Format string `form:"format,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
CreatedAt *time.Time `form:"created_at,omitempty"`
|
||||||
|
UpdatedAt *time.Time `form:"updated_at,omitempty"`
|
||||||
|
DeletedAt *time.Time `form:"deleted_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePapertrail updates a specific papertrail.
|
||||||
|
func (c *Client) UpdatePapertrail(i *UpdatePapertrailInput) (*Papertrail, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/papertrail/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var p *Papertrail
|
||||||
|
if err := decodeJSON(&p, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePapertrailInput is the input parameter to DeletePapertrail.
|
||||||
|
type DeletePapertrailInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the papertrail to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePapertrail deletes the given papertrail version.
|
||||||
|
func (c *Client) DeletePapertrail(i *DeletePapertrailInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/papertrail/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Purge is a response from a purge request.
|
||||||
|
type Purge struct {
|
||||||
|
// Status is the status of the purge, usually "ok".
|
||||||
|
Status string `mapstructure:"status"`
|
||||||
|
|
||||||
|
// ID is the unique ID of the purge request.
|
||||||
|
ID string `mapstructure:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PurgeInput is used as input to the Purge function.
|
||||||
|
type PurgeInput struct {
|
||||||
|
// URL is the URL to purge (required).
|
||||||
|
URL string
|
||||||
|
|
||||||
|
// Soft performs a soft purge.
|
||||||
|
Soft bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge instantly purges an individual URL.
|
||||||
|
func (c *Client) Purge(i *PurgeInput) (*Purge, error) {
|
||||||
|
if i.URL == "" {
|
||||||
|
return nil, ErrMissingURL
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := c.RawRequest("PURGE", i.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Soft {
|
||||||
|
req.Header.Set("Fastly-Soft-Purge", "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := checkResp(c.HTTPClient.Do(req))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *Purge
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PurgeKeyInput is used as input to the Purge function.
|
||||||
|
type PurgeKeyInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Key is the key to purge (required).
|
||||||
|
Key string
|
||||||
|
|
||||||
|
// Soft performs a soft purge.
|
||||||
|
Soft bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// PurgeKey instantly purges a particular service of items tagged with a key.
|
||||||
|
func (c *Client) PurgeKey(i *PurgeKeyInput) (*Purge, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Key == "" {
|
||||||
|
return nil, ErrMissingKey
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/purge/%s", i.Service, i.Key)
|
||||||
|
req, err := c.RawRequest("POST", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Soft {
|
||||||
|
req.Header.Set("Fastly-Soft-Purge", "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := checkResp(c.HTTPClient.Do(req))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *Purge
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PurgeAllInput is used as input to the Purge function.
|
||||||
|
type PurgeAllInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Soft performs a soft purge.
|
||||||
|
Soft bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// PurgeAll instantly purges everything from a service.
|
||||||
|
func (c *Client) PurgeAll(i *PurgeAllInput) (*Purge, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/purge_all", i.Service)
|
||||||
|
req, err := c.RawRequest("POST", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Soft {
|
||||||
|
req.Header.Set("Fastly-Soft-Purge", "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := checkResp(c.HTTPClient.Do(req))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *Purge
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestOptions is the list of options to pass to the request.
|
||||||
|
type RequestOptions struct {
|
||||||
|
// Params is a map of key-value pairs that will be added to the Request.
|
||||||
|
Params map[string]string
|
||||||
|
|
||||||
|
// Headers is a map of key-value pairs that will be added to the Request.
|
||||||
|
Headers map[string]string
|
||||||
|
|
||||||
|
// Body is an io.Reader object that will be streamed or uploaded with the
|
||||||
|
// Request. BodyLength is the final size of the Body.
|
||||||
|
Body io.Reader
|
||||||
|
BodyLength int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawRequest accepts a verb, URL, and RequestOptions struct and returns the
|
||||||
|
// constructed http.Request and any errors that occurred
|
||||||
|
func (c *Client) RawRequest(verb, p string, ro *RequestOptions) (*http.Request, error) {
|
||||||
|
// Ensure we have request options.
|
||||||
|
if ro == nil {
|
||||||
|
ro = new(RequestOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the path to the URL.
|
||||||
|
u := *c.url
|
||||||
|
u.Path = path.Join(c.url.Path, p)
|
||||||
|
|
||||||
|
// Add the token and other params.
|
||||||
|
var params = make(url.Values)
|
||||||
|
for k, v := range ro.Params {
|
||||||
|
params.Add(k, v)
|
||||||
|
}
|
||||||
|
u.RawQuery = params.Encode()
|
||||||
|
|
||||||
|
// Create the request object.
|
||||||
|
request, err := http.NewRequest(verb, u.String(), ro.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the API key.
|
||||||
|
if len(c.apiKey) > 0 {
|
||||||
|
request.Header.Set(APIKeyHeader, c.apiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the User-Agent.
|
||||||
|
request.Header.Set("User-Agent", UserAgent)
|
||||||
|
|
||||||
|
// Add any custom headers.
|
||||||
|
for k, v := range ro.Headers {
|
||||||
|
request.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Content-Length if we have it.
|
||||||
|
if ro.BodyLength > 0 {
|
||||||
|
request.ContentLength = ro.BodyLength
|
||||||
|
}
|
||||||
|
|
||||||
|
return request, nil
|
||||||
|
}
|
|
@ -0,0 +1,279 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RequestSettingActionLookup sets request handling to lookup via the cache.
|
||||||
|
RequestSettingActionLookup RequestSettingAction = "lookup"
|
||||||
|
|
||||||
|
// RequestSettingActionPass sets request handling to pass the cache.
|
||||||
|
RequestSettingActionPass RequestSettingAction = "pass"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestSettingAction is a type of request setting action.
|
||||||
|
type RequestSettingAction string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RequestSettingXFFClear clears any X-Forwarded-For headers.
|
||||||
|
RequestSettingXFFClear RequestSettingXFF = "clear"
|
||||||
|
|
||||||
|
// RequestSettingXFFLeave leaves any X-Forwarded-For headers untouched.
|
||||||
|
RequestSettingXFFLeave RequestSettingXFF = "leave"
|
||||||
|
|
||||||
|
// RequestSettingXFFAppend adds Fastly X-Forwarded-For headers.
|
||||||
|
RequestSettingXFFAppend RequestSettingXFF = "append"
|
||||||
|
|
||||||
|
// RequestSettingXFFAppendAll appends all Fastly X-Forwarded-For headers.
|
||||||
|
RequestSettingXFFAppendAll RequestSettingXFF = "append_all"
|
||||||
|
|
||||||
|
// RequestSettingXFFOverwrite clears any X-Forwarded-For headers and replaces
|
||||||
|
// with Fastly ones.
|
||||||
|
RequestSettingXFFOverwrite RequestSettingXFF = "overwrite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestSettingXFF is a type of X-Forwarded-For value to set.
|
||||||
|
type RequestSettingXFF string
|
||||||
|
|
||||||
|
// RequestSetting represents a request setting response from the Fastly API.
|
||||||
|
type RequestSetting struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
ForceMiss bool `mapstructure:"force_miss"`
|
||||||
|
ForceSSL bool `mapstructure:"force_ssl"`
|
||||||
|
Action RequestSettingAction `mapstructure:"action"`
|
||||||
|
BypassBusyWait bool `mapstructure:"bypass_busy_wait"`
|
||||||
|
MaxStaleAge uint `mapstructure:"max_stale_age"`
|
||||||
|
HashKeys string `mapstructure:"hash_keys"`
|
||||||
|
XForwardedFor RequestSettingXFF `mapstructure:"xff"`
|
||||||
|
TimerSupport bool `mapstructure:"timer_support"`
|
||||||
|
GeoHeaders bool `mapstructure:"geo_headers"`
|
||||||
|
DefaultHost string `mapstructure:"default_host"`
|
||||||
|
RequestCondition string `mapstructure:"request_condition"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestSettingsByName is a sortable list of request settings.
|
||||||
|
type requestSettingsByName []*RequestSetting
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s requestSettingsByName) Len() int { return len(s) }
|
||||||
|
func (s requestSettingsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s requestSettingsByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequestSettingsInput is used as input to the ListRequestSettings
|
||||||
|
// function.
|
||||||
|
type ListRequestSettingsInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequestSettings returns the list of request settings for the
|
||||||
|
// configuration version.
|
||||||
|
func (c *Client) ListRequestSettings(i *ListRequestSettingsInput) ([]*RequestSetting, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/request_settings", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bs []*RequestSetting
|
||||||
|
if err := decodeJSON(&bs, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(requestSettingsByName(bs))
|
||||||
|
return bs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRequestSettingInput is used as input to the CreateRequestSetting
|
||||||
|
// function.
|
||||||
|
type CreateRequestSettingInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
ForceMiss Compatibool `form:"force_miss,omitempty"`
|
||||||
|
ForceSSL Compatibool `form:"force_ssl,omitempty"`
|
||||||
|
Action RequestSettingAction `form:"action,omitempty"`
|
||||||
|
BypassBusyWait Compatibool `form:"bypass_busy_wait,omitempty"`
|
||||||
|
MaxStaleAge uint `form:"max_stale_age,omitempty"`
|
||||||
|
HashKeys string `form:"hash_keys,omitempty"`
|
||||||
|
XForwardedFor RequestSettingXFF `form:"xff,omitempty"`
|
||||||
|
TimerSupport Compatibool `form:"timer_support,omitempty"`
|
||||||
|
GeoHeaders Compatibool `form:"geo_headers,omitempty"`
|
||||||
|
DefaultHost string `form:"default_host,omitempty"`
|
||||||
|
RequestCondition string `form:"request_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRequestSetting creates a new Fastly request settings.
|
||||||
|
func (c *Client) CreateRequestSetting(i *CreateRequestSettingInput) (*RequestSetting, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/request_settings", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *RequestSetting
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequestSettingInput is used as input to the GetRequestSetting function.
|
||||||
|
type GetRequestSettingInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the request settings to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequestSetting gets the request settings configuration with the given
|
||||||
|
// parameters.
|
||||||
|
func (c *Client) GetRequestSetting(i *GetRequestSettingInput) (*RequestSetting, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/request_settings/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *RequestSetting
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRequestSettingInput is used as input to the UpdateRequestSetting
|
||||||
|
// function.
|
||||||
|
type UpdateRequestSettingInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the request settings to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
ForceMiss Compatibool `form:"force_miss,omitempty"`
|
||||||
|
ForceSSL Compatibool `form:"force_ssl,omitempty"`
|
||||||
|
Action RequestSettingAction `form:"action,omitempty"`
|
||||||
|
BypassBusyWait Compatibool `form:"bypass_busy_wait,omitempty"`
|
||||||
|
MaxStaleAge uint `form:"max_stale_age,omitempty"`
|
||||||
|
HashKeys string `form:"hash_keys,omitempty"`
|
||||||
|
XForwardedFor RequestSettingXFF `form:"xff,omitempty"`
|
||||||
|
TimerSupport Compatibool `form:"timer_support,omitempty"`
|
||||||
|
GeoHeaders Compatibool `form:"geo_headers,omitempty"`
|
||||||
|
DefaultHost string `form:"default_host,omitempty"`
|
||||||
|
RequestCondition string `form:"request_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRequestSetting updates a specific request settings.
|
||||||
|
func (c *Client) UpdateRequestSetting(i *UpdateRequestSettingInput) (*RequestSetting, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/request_settings/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *RequestSetting
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRequestSettingInput is the input parameter to DeleteRequestSetting.
|
||||||
|
type DeleteRequestSettingInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the request settings to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRequestSetting deletes the given request settings version.
|
||||||
|
func (c *Client) DeleteRequestSetting(i *DeleteRequestSettingInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/request_settings/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,232 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResponseObject represents a response object response from the Fastly API.
|
||||||
|
type ResponseObject struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Status uint `mapstructure:"status"`
|
||||||
|
Response string `mapstructure:"response"`
|
||||||
|
Content string `mapstructure:"content"`
|
||||||
|
ContentType string `mapstructure:"content_type"`
|
||||||
|
RequestCondition string `mapstructure:"request_condition"`
|
||||||
|
CacheCondition string `mapstructure:"cache_condition"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// responseObjectsByName is a sortable list of response objects.
|
||||||
|
type responseObjectsByName []*ResponseObject
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s responseObjectsByName) Len() int { return len(s) }
|
||||||
|
func (s responseObjectsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s responseObjectsByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListResponseObjectsInput is used as input to the ListResponseObjects
|
||||||
|
// function.
|
||||||
|
type ListResponseObjectsInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListResponseObjects returns the list of response objects for the
|
||||||
|
// configuration version.
|
||||||
|
func (c *Client) ListResponseObjects(i *ListResponseObjectsInput) ([]*ResponseObject, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/response_object", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bs []*ResponseObject
|
||||||
|
if err := decodeJSON(&bs, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(responseObjectsByName(bs))
|
||||||
|
return bs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResponseObjectInput is used as input to the CreateResponseObject
|
||||||
|
// function.
|
||||||
|
type CreateResponseObjectInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Status uint `form:"status,omitempty"`
|
||||||
|
Response string `form:"response,omitempty"`
|
||||||
|
Content string `form:"content,omitempty"`
|
||||||
|
ContentType string `form:"content_type,omitempty"`
|
||||||
|
RequestCondition string `form:"request_condition,omitempty"`
|
||||||
|
CacheCondition string `form:"cache_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResponseObject creates a new Fastly response object.
|
||||||
|
func (c *Client) CreateResponseObject(i *CreateResponseObjectInput) (*ResponseObject, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/response_object", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *ResponseObject
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResponseObjectInput is used as input to the GetResponseObject function.
|
||||||
|
type GetResponseObjectInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the response object to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResponseObject gets the response object configuration with the given
|
||||||
|
// parameters.
|
||||||
|
func (c *Client) GetResponseObject(i *GetResponseObjectInput) (*ResponseObject, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/response_object/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *ResponseObject
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateResponseObjectInput is used as input to the UpdateResponseObject
|
||||||
|
// function.
|
||||||
|
type UpdateResponseObjectInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the response object to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
Status uint `form:"status,omitempty"`
|
||||||
|
Response string `form:"response,omitempty"`
|
||||||
|
Content string `form:"content,omitempty"`
|
||||||
|
ContentType string `form:"content_type,omitempty"`
|
||||||
|
RequestCondition string `form:"request_condition,omitempty"`
|
||||||
|
CacheCondition string `form:"cache_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateResponseObject updates a specific response object.
|
||||||
|
func (c *Client) UpdateResponseObject(i *UpdateResponseObjectInput) (*ResponseObject, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/response_object/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *ResponseObject
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteResponseObjectInput is the input parameter to DeleteResponseObject.
|
||||||
|
type DeleteResponseObjectInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the response object to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteResponseObject deletes the given response object version.
|
||||||
|
func (c *Client) DeleteResponseObject(i *DeleteResponseObjectInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/response_object/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,240 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// S3 represents a S3 response from the Fastly API.
|
||||||
|
type S3 struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
BucketName string `mapstructure:"bucket_name"`
|
||||||
|
AccessKey string `mapstructure:"access_key"`
|
||||||
|
SecretKey string `mapstructure:"secret_key"`
|
||||||
|
Path string `mapstructure:"path"`
|
||||||
|
Period uint `mapstructure:"period"`
|
||||||
|
GzipLevel uint `mapstructure:"gzip_level"`
|
||||||
|
Format string `mapstructure:"format"`
|
||||||
|
ResponseCondition string `mapstructure:"response_condition"`
|
||||||
|
TimestampFormat string `mapstructure:"timestamp_format"`
|
||||||
|
CreatedAt *time.Time `mapstructure:"created_at"`
|
||||||
|
UpdatedAt *time.Time `mapstructure:"updated_at"`
|
||||||
|
DeletedAt *time.Time `mapstructure:"deleted_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// s3sByName is a sortable list of S3s.
|
||||||
|
type s3sByName []*S3
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s s3sByName) Len() int { return len(s) }
|
||||||
|
func (s s3sByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s s3sByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListS3sInput is used as input to the ListS3s function.
|
||||||
|
type ListS3sInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListS3s returns the list of S3s for the configuration version.
|
||||||
|
func (c *Client) ListS3s(i *ListS3sInput) ([]*S3, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/s3", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s3s []*S3
|
||||||
|
if err := decodeJSON(&s3s, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(s3sByName(s3s))
|
||||||
|
return s3s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateS3Input is used as input to the CreateS3 function.
|
||||||
|
type CreateS3Input struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
BucketName string `form:"bucket_name,omitempty"`
|
||||||
|
AccessKey string `form:"access_key,omitempty"`
|
||||||
|
SecretKey string `form:"secret_key,omitempty"`
|
||||||
|
Path string `form:"path,omitempty"`
|
||||||
|
Period uint `form:"period,omitempty"`
|
||||||
|
GzipLevel uint `form:"gzip_level,omitempty"`
|
||||||
|
Format string `form:"format,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
TimestampFormat string `form:"timestamp_format,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateS3 creates a new Fastly S3.
|
||||||
|
func (c *Client) CreateS3(i *CreateS3Input) (*S3, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/s3", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s3 *S3
|
||||||
|
if err := decodeJSON(&s3, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s3, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetS3Input is used as input to the GetS3 function.
|
||||||
|
type GetS3Input struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the S3 to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetS3 gets the S3 configuration with the given parameters.
|
||||||
|
func (c *Client) GetS3(i *GetS3Input) (*S3, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/s3/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s3 *S3
|
||||||
|
if err := decodeJSON(&s3, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s3, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateS3Input is used as input to the UpdateS3 function.
|
||||||
|
type UpdateS3Input struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the S3 to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
BucketName string `form:"bucket_name,omitempty"`
|
||||||
|
AccessKey string `form:"access_key,omitempty"`
|
||||||
|
SecretKey string `form:"secret_key,omitempty"`
|
||||||
|
Path string `form:"path,omitempty"`
|
||||||
|
Period uint `form:"period,omitempty"`
|
||||||
|
GzipLevel uint `form:"gzip_level,omitempty"`
|
||||||
|
Format string `form:"format,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
TimestampFormat string `form:"timestamp_format,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateS3 updates a specific S3.
|
||||||
|
func (c *Client) UpdateS3(i *UpdateS3Input) (*S3, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/s3/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s3 *S3
|
||||||
|
if err := decodeJSON(&s3, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s3, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteS3Input is the input parameter to DeleteS3.
|
||||||
|
type DeleteS3Input struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the S3 to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteS3 deletes the given S3 version.
|
||||||
|
func (c *Client) DeleteS3(i *DeleteS3Input) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/s3/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service represents a single service for the Fastly account.
|
||||||
|
type Service struct {
|
||||||
|
ID string `mapstructure:"id"`
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Comment string `mapstructure:"comment"`
|
||||||
|
CustomerID string `mapstructure:"customer_id"`
|
||||||
|
ActiveVersion uint `mapstructure:"version"`
|
||||||
|
Versions []*Version `mapstructure:"versions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceDetail struct {
|
||||||
|
ID string `mapstructure:"id"`
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Comment string `mapstructure:"comment"`
|
||||||
|
CustomerID string `mapstructure:"customer_id"`
|
||||||
|
ActiveVersion Version `mapstructure:"active_version"`
|
||||||
|
Version Version `mapstructure:"version"`
|
||||||
|
Versions []*Version `mapstructure:"versions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// servicesByName is a sortable list of services.
|
||||||
|
type servicesByName []*Service
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s servicesByName) Len() int { return len(s) }
|
||||||
|
func (s servicesByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s servicesByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListServicesInput is used as input to the ListServices function.
|
||||||
|
type ListServicesInput struct{}
|
||||||
|
|
||||||
|
// ListServices returns the full list of services for the current account.
|
||||||
|
func (c *Client) ListServices(i *ListServicesInput) ([]*Service, error) {
|
||||||
|
resp, err := c.Get("/service", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s []*Service
|
||||||
|
if err := decodeJSON(&s, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(servicesByName(s))
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateServiceInput is used as input to the CreateService function.
|
||||||
|
type CreateServiceInput struct {
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Comment string `form:"comment,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateService creates a new service with the given information.
|
||||||
|
func (c *Client) CreateService(i *CreateServiceInput) (*Service, error) {
|
||||||
|
resp, err := c.PostForm("/service", i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s *Service
|
||||||
|
if err := decodeJSON(&s, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServiceInput is used as input to the GetService function.
|
||||||
|
type GetServiceInput struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetService retrieves the service information for the service with the given
|
||||||
|
// id. If no service exists for the given id, the API returns a 400 response
|
||||||
|
// (not a 404).
|
||||||
|
func (c *Client) GetService(i *GetServiceInput) (*Service, error) {
|
||||||
|
if i.ID == "" {
|
||||||
|
return nil, ErrMissingID
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s", i.ID)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s *Service
|
||||||
|
if err := decodeJSON(&s, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetService retrieves the details for the service with the given id. If no
|
||||||
|
// service exists for the given id, the API returns a 400 response (not a 404).
|
||||||
|
func (c *Client) GetServiceDetails(i *GetServiceInput) (*ServiceDetail, error) {
|
||||||
|
if i.ID == "" {
|
||||||
|
return nil, ErrMissingID
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/details", i.ID)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s *ServiceDetail
|
||||||
|
if err := decodeJSON(&s, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateServiceInput is used as input to the UpdateService function.
|
||||||
|
type UpdateServiceInput struct {
|
||||||
|
ID string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Comment string `form:"comment,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateService updates the service with the given input.
|
||||||
|
func (c *Client) UpdateService(i *UpdateServiceInput) (*Service, error) {
|
||||||
|
if i.ID == "" {
|
||||||
|
return nil, ErrMissingID
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s", i.ID)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s *Service
|
||||||
|
if err := decodeJSON(&s, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteServiceInput is used as input to the DeleteService function.
|
||||||
|
type DeleteServiceInput struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteService updates the service with the given input.
|
||||||
|
func (c *Client) DeleteService(i *DeleteServiceInput) error {
|
||||||
|
if i.ID == "" {
|
||||||
|
return ErrMissingID
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s", i.ID)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchServiceInput is used as input to the SearchService function.
|
||||||
|
type SearchServiceInput struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchService gets a specific service by name. If no service exists by that
|
||||||
|
// name, the API returns a 400 response (not a 404).
|
||||||
|
func (c *Client) SearchService(i *SearchServiceInput) (*Service, error) {
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.Get("/service/search", &RequestOptions{
|
||||||
|
Params: map[string]string{
|
||||||
|
"name": i.Name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s *Service
|
||||||
|
if err := decodeJSON(&s, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Settings represents a backend response from the Fastly API.
|
||||||
|
type Settings struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
DefaultTTL uint `mapstructure:"general.default_ttl"`
|
||||||
|
DefaultHost string `mapstructure:"general.default_host"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSettingsInput is used as input to the GetSettings function.
|
||||||
|
type GetSettingsInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSettings gets the backend configuration with the given parameters.
|
||||||
|
func (c *Client) GetSettings(i *GetSettingsInput) (*Settings, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/settings", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Settings
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSettingsInput is used as input to the UpdateSettings function.
|
||||||
|
type UpdateSettingsInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
DefaultTTL uint `form:"general.default_ttl,omitempty"`
|
||||||
|
DefaultHost string `form:"general.default_host,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSettings updates a specific backend.
|
||||||
|
func (c *Client) UpdateSettings(i *UpdateSettingsInput) (*Settings, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/settings", i.Service, i.Version)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Settings
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
|
@ -0,0 +1,225 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sumologic represents a sumologic response from the Fastly API.
|
||||||
|
type Sumologic struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Address string `mapstructure:"address"`
|
||||||
|
URL string `mapstructure:"url"`
|
||||||
|
Format string `mapstructure:"format"`
|
||||||
|
ResponseCondition string `mapstructure:"response_condition"`
|
||||||
|
CreatedAt *time.Time `mapstructure:"created_at"`
|
||||||
|
UpdatedAt *time.Time `mapstructure:"updated_at"`
|
||||||
|
DeletedAt *time.Time `mapstructure:"deleted_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// sumologicsByName is a sortable list of sumologics.
|
||||||
|
type sumologicsByName []*Sumologic
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s sumologicsByName) Len() int { return len(s) }
|
||||||
|
func (s sumologicsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s sumologicsByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSumologicsInput is used as input to the ListSumologics function.
|
||||||
|
type ListSumologicsInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSumologics returns the list of sumologics for the configuration version.
|
||||||
|
func (c *Client) ListSumologics(i *ListSumologicsInput) ([]*Sumologic, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/sumologic", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ss []*Sumologic
|
||||||
|
if err := decodeJSON(&ss, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(sumologicsByName(ss))
|
||||||
|
return ss, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSumologicInput is used as input to the CreateSumologic function.
|
||||||
|
type CreateSumologicInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Address string `form:"address,omitempty"`
|
||||||
|
URL string `form:"url,omitempty"`
|
||||||
|
Format string `form:"format,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSumologic creates a new Fastly sumologic.
|
||||||
|
func (c *Client) CreateSumologic(i *CreateSumologicInput) (*Sumologic, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/sumologic", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s *Sumologic
|
||||||
|
if err := decodeJSON(&s, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSumologicInput is used as input to the GetSumologic function.
|
||||||
|
type GetSumologicInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the sumologic to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSumologic gets the sumologic configuration with the given parameters.
|
||||||
|
func (c *Client) GetSumologic(i *GetSumologicInput) (*Sumologic, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/sumologic/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s *Sumologic
|
||||||
|
if err := decodeJSON(&s, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSumologicInput is used as input to the UpdateSumologic function.
|
||||||
|
type UpdateSumologicInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the sumologic to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
Address string `form:"address,omitempty"`
|
||||||
|
URL string `form:"url,omitempty"`
|
||||||
|
Format string `form:"format,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSumologic updates a specific sumologic.
|
||||||
|
func (c *Client) UpdateSumologic(i *UpdateSumologicInput) (*Sumologic, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/sumologic/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s *Sumologic
|
||||||
|
if err := decodeJSON(&s, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSumologicInput is the input parameter to DeleteSumologic.
|
||||||
|
type DeleteSumologicInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the sumologic to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSumologic deletes the given sumologic version.
|
||||||
|
func (c *Client) DeleteSumologic(i *DeleteSumologicInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/sumologic/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Syslog represents a syslog response from the Fastly API.
|
||||||
|
type Syslog struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Address string `mapstructure:"address"`
|
||||||
|
Port uint `mapstructure:"port"`
|
||||||
|
UseTLS bool `mapstructure:"use_tls"`
|
||||||
|
TLSCACert string `mapstructure:"tls_ca_cert"`
|
||||||
|
Token string `mapstructure:"token"`
|
||||||
|
Format string `mapstructure:"format"`
|
||||||
|
ResponseCondition string `mapstructure:"response_condition"`
|
||||||
|
CreatedAt *time.Time `mapstructure:"created_at"`
|
||||||
|
UpdatedAt *time.Time `mapstructure:"updated_at"`
|
||||||
|
DeletedAt *time.Time `mapstructure:"deleted_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// syslogsByName is a sortable list of syslogs.
|
||||||
|
type syslogsByName []*Syslog
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s syslogsByName) Len() int { return len(s) }
|
||||||
|
func (s syslogsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s syslogsByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSyslogsInput is used as input to the ListSyslogs function.
|
||||||
|
type ListSyslogsInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSyslogs returns the list of syslogs for the configuration version.
|
||||||
|
func (c *Client) ListSyslogs(i *ListSyslogsInput) ([]*Syslog, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/syslog", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ss []*Syslog
|
||||||
|
if err := decodeJSON(&ss, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(syslogsByName(ss))
|
||||||
|
return ss, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSyslogInput is used as input to the CreateSyslog function.
|
||||||
|
type CreateSyslogInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Address string `form:"address,omitempty"`
|
||||||
|
Port uint `form:"port,omitempty"`
|
||||||
|
UseTLS Compatibool `form:"use_tls,omitempty"`
|
||||||
|
TLSCACert string `form:"tls_ca_cert,omitempty"`
|
||||||
|
Token string `form:"token,omitempty"`
|
||||||
|
Format string `form:"format,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSyslog creates a new Fastly syslog.
|
||||||
|
func (c *Client) CreateSyslog(i *CreateSyslogInput) (*Syslog, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/syslog", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s *Syslog
|
||||||
|
if err := decodeJSON(&s, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSyslogInput is used as input to the GetSyslog function.
|
||||||
|
type GetSyslogInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the syslog to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSyslog gets the syslog configuration with the given parameters.
|
||||||
|
func (c *Client) GetSyslog(i *GetSyslogInput) (*Syslog, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/syslog/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s *Syslog
|
||||||
|
if err := decodeJSON(&s, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSyslogInput is used as input to the UpdateSyslog function.
|
||||||
|
type UpdateSyslogInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the syslog to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
Address string `form:"address,omitempty"`
|
||||||
|
Port uint `form:"port,omitempty"`
|
||||||
|
UseTLS Compatibool `form:"use_tls,omitempty"`
|
||||||
|
TLSCACert string `form:"tls_ca_cert,omitempty"`
|
||||||
|
Token string `form:"token,omitempty"`
|
||||||
|
Format string `form:"format,omitempty"`
|
||||||
|
ResponseCondition string `form:"response_condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSyslog updates a specific syslog.
|
||||||
|
func (c *Client) UpdateSyslog(i *UpdateSyslogInput) (*Syslog, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/syslog/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var s *Syslog
|
||||||
|
if err := decodeJSON(&s, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSyslogInput is the input parameter to DeleteSyslog.
|
||||||
|
type DeleteSyslogInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the syslog to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSyslog deletes the given syslog version.
|
||||||
|
func (c *Client) DeleteSyslog(i *DeleteSyslogInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/logging/syslog/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,282 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VCL represents a response about VCL from the Fastly API.
|
||||||
|
type VCL struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Main bool `mapstructure:"main"`
|
||||||
|
Content string `mapstructure:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// vclsByName is a sortable list of VCLs.
|
||||||
|
type vclsByName []*VCL
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s vclsByName) Len() int { return len(s) }
|
||||||
|
func (s vclsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s vclsByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListVCLsInput is used as input to the ListVCLs function.
|
||||||
|
type ListVCLsInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the specific configuration version (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListVCLs returns the list of VCLs for the configuration version.
|
||||||
|
func (c *Client) ListVCLs(i *ListVCLsInput) ([]*VCL, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/vcl", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var vcls []*VCL
|
||||||
|
if err := decodeJSON(&vcls, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(vclsByName(vcls))
|
||||||
|
return vcls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVCLInput is used as input to the GetVCL function.
|
||||||
|
type GetVCLInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the VCL to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVCL gets the VCL configuration with the given parameters.
|
||||||
|
func (c *Client) GetVCL(i *GetVCLInput) (*VCL, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/vcl/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var vcl *VCL
|
||||||
|
if err := decodeJSON(&vcl, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vcl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGeneratedVCLInput is used as input to the GetGeneratedVCL function.
|
||||||
|
type GetGeneratedVCLInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGeneratedVCL gets the VCL configuration with the given parameters.
|
||||||
|
func (c *Client) GetGeneratedVCL(i *GetGeneratedVCLInput) (*VCL, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/generated_vcl", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var vcl *VCL
|
||||||
|
if err := decodeJSON(&vcl, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vcl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateVCLInput is used as input to the CreateVCL function.
|
||||||
|
type CreateVCLInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Content string `form:"content,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateVCL creates a new Fastly VCL.
|
||||||
|
func (c *Client) CreateVCL(i *CreateVCLInput) (*VCL, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/vcl", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var vcl *VCL
|
||||||
|
if err := decodeJSON(&vcl, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vcl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVCLInput is used as input to the UpdateVCL function.
|
||||||
|
type UpdateVCLInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the VCL to update (required).
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
Content string `form:"content,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVCL creates a new Fastly VCL.
|
||||||
|
func (c *Client) UpdateVCL(i *UpdateVCLInput) (*VCL, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/vcl/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var vcl *VCL
|
||||||
|
if err := decodeJSON(&vcl, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vcl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivateVCLInput is used as input to the ActivateVCL function.
|
||||||
|
type ActivateVCLInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the VCL to mark as main (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivateVCL creates a new Fastly VCL.
|
||||||
|
func (c *Client) ActivateVCL(i *ActivateVCLInput) (*VCL, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/vcl/%s/main", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Put(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var vcl *VCL
|
||||||
|
if err := decodeJSON(&vcl, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vcl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteVCLInput is the input parameter to DeleteVCL.
|
||||||
|
type DeleteVCLInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the VCL to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteVCL deletes the given VCL version.
|
||||||
|
func (c *Client) DeleteVCL(i *DeleteVCLInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/vcl/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,335 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version represents a distinct configuration version.
|
||||||
|
type Version struct {
|
||||||
|
Number string `mapstructure:"number"`
|
||||||
|
Comment string `mapstructure:"comment"`
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Active bool `mapstructure:"active"`
|
||||||
|
Locked bool `mapstructure:"locked"`
|
||||||
|
Deployed bool `mapstructure:"deployed"`
|
||||||
|
Staging bool `mapstructure:"staging"`
|
||||||
|
Testing bool `mapstructure:"testing"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// versionsByNumber is a sortable list of versions. This is used by the version
|
||||||
|
// `List()` function to sort the API responses.
|
||||||
|
type versionsByNumber []*Version
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s versionsByNumber) Len() int { return len(s) }
|
||||||
|
func (s versionsByNumber) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s versionsByNumber) Less(i, j int) bool {
|
||||||
|
return s[i].Number < s[j].Number
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListVersionsInput is the input to the ListVersions function.
|
||||||
|
type ListVersionsInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListVersions returns the full list of all versions of the given service.
|
||||||
|
func (c *Client) ListVersions(i *ListVersionsInput) ([]*Version, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version", i.Service)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e []*Version
|
||||||
|
if err := decodeJSON(&e, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Sort(versionsByNumber(e))
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatestVersionInput is the input to the LatestVersion function.
|
||||||
|
type LatestVersionInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatestVersion fetches the latest version. If there are no versions, this
|
||||||
|
// function will return nil (but not an error).
|
||||||
|
func (c *Client) LatestVersion(i *LatestVersionInput) (*Version, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := c.ListVersions(&ListVersionsInput{Service: i.Service})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(list) < 1 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
e := list[len(list)-1]
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateVersionInput is the input to the CreateVersion function.
|
||||||
|
type CreateVersionInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateVersion constructs a new version. There are no request parameters, but
|
||||||
|
// you should consult the resulting version number. Note that `CloneVersion` is
|
||||||
|
// preferred in almost all scenarios, since `Create()` creates a _blank_
|
||||||
|
// configuration where `Clone()` builds off of an existing configuration.
|
||||||
|
func (c *Client) CreateVersion(i *CreateVersionInput) (*Version, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version", i.Service)
|
||||||
|
resp, err := c.Post(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e *Version
|
||||||
|
if err := decodeJSON(&e, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersionInput is the input to the GetVersion function.
|
||||||
|
type GetVersionInput struct {
|
||||||
|
// Service is the ID of the service (required).
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// Version is the version number to fetch (required).
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersion fetches a version with the given information.
|
||||||
|
func (c *Client) GetVersion(i *GetVersionInput) (*Version, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e *Version
|
||||||
|
if err := decodeJSON(&e, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVersionInput is the input to the UpdateVersion function.
|
||||||
|
type UpdateVersionInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Comment string `form:"comment,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVersion updates the given version
|
||||||
|
func (c *Client) UpdateVersion(i *UpdateVersionInput) (*Version, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s", i.Service, i.Version)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e *Version
|
||||||
|
if err := decodeJSON(&e, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivateVersionInput is the input to the ActivateVersion function.
|
||||||
|
type ActivateVersionInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivateVersion activates the given version.
|
||||||
|
func (c *Client) ActivateVersion(i *ActivateVersionInput) (*Version, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/activate", i.Service, i.Version)
|
||||||
|
resp, err := c.Put(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e *Version
|
||||||
|
if err := decodeJSON(&e, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeactivateVersionInput is the input to the DeactivateVersion function.
|
||||||
|
type DeactivateVersionInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeactivateVersion deactivates the given version.
|
||||||
|
func (c *Client) DeactivateVersion(i *DeactivateVersionInput) (*Version, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/deactivate", i.Service, i.Version)
|
||||||
|
resp, err := c.Put(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e *Version
|
||||||
|
if err := decodeJSON(&e, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneVersionInput is the input to the CloneVersion function.
|
||||||
|
type CloneVersionInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneVersion creates a clone of the version with and returns a new
|
||||||
|
// configuration version with all the same configuration options, but an
|
||||||
|
// incremented number.
|
||||||
|
func (c *Client) CloneVersion(i *CloneVersionInput) (*Version, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/clone", i.Service, i.Version)
|
||||||
|
resp, err := c.Put(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e *Version
|
||||||
|
if err := decodeJSON(&e, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateVersionInput is the input to the ValidateVersion function.
|
||||||
|
type ValidateVersionInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateVersion validates if the given version is okay.
|
||||||
|
func (c *Client) ValidateVersion(i *ValidateVersionInput) (bool, string, error) {
|
||||||
|
var msg string
|
||||||
|
|
||||||
|
if i.Service == "" {
|
||||||
|
return false, msg, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return false, msg, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/validate", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return false, msg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return false, msg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = r.Msg
|
||||||
|
return r.Ok(), msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LockVersionInput is the input to the LockVersion function.
|
||||||
|
type LockVersionInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LockVersion locks the specified version.
|
||||||
|
func (c *Client) LockVersion(i *LockVersionInput) (*Version, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/lock", i.Service, i.Version)
|
||||||
|
resp, err := c.Put(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e *Version
|
||||||
|
if err := decodeJSON(&e, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
package fastly
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wordpress represents a wordpress response from the Fastly API.
|
||||||
|
type Wordpress struct {
|
||||||
|
ServiceID string `mapstructure:"service_id"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Path string `mapstructure:"path"`
|
||||||
|
Comment string `mapstructure:"comment"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// wordpressesByName is a sortable list of wordpresses.
|
||||||
|
type wordpressesByName []*Wordpress
|
||||||
|
|
||||||
|
// Len, Swap, and Less implement the sortable interface.
|
||||||
|
func (s wordpressesByName) Len() int { return len(s) }
|
||||||
|
func (s wordpressesByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s wordpressesByName) Less(i, j int) bool {
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWordpressesInput is used as input to the ListWordpresses function.
|
||||||
|
type ListWordpressesInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWordpresses returns the list of wordpresses for the configuration version.
|
||||||
|
func (c *Client) ListWordpresses(i *ListWordpressesInput) ([]*Wordpress, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/wordpress", i.Service, i.Version)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bs []*Wordpress
|
||||||
|
if err := decodeJSON(&bs, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Stable(wordpressesByName(bs))
|
||||||
|
return bs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateWordpressInput is used as input to the CreateWordpress function.
|
||||||
|
type CreateWordpressInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
Name string `form:"name,omitempty"`
|
||||||
|
Path string `form:"path,omitempty"`
|
||||||
|
Comment string `form:"comment,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateWordpress creates a new Fastly wordpress.
|
||||||
|
func (c *Client) CreateWordpress(i *CreateWordpressInput) (*Wordpress, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/wordpress", i.Service, i.Version)
|
||||||
|
resp, err := c.PostForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Wordpress
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWordpressInput is used as input to the GetWordpress function.
|
||||||
|
type GetWordpressInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the wordpress to fetch.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWordpress gets the wordpress configuration with the given parameters.
|
||||||
|
func (c *Client) GetWordpress(i *GetWordpressInput) (*Wordpress, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/wordpress/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Get(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Wordpress
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWordpressInput is used as input to the UpdateWordpress function.
|
||||||
|
type UpdateWordpressInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the wordpress to update.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
NewName string `form:"name,omitempty"`
|
||||||
|
Path string `form:"path,omitempty"`
|
||||||
|
Comment string `form:"comment,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWordpress updates a specific wordpress.
|
||||||
|
func (c *Client) UpdateWordpress(i *UpdateWordpressInput) (*Wordpress, error) {
|
||||||
|
if i.Service == "" {
|
||||||
|
return nil, ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return nil, ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return nil, ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/wordpress/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.PutForm(path, i, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *Wordpress
|
||||||
|
if err := decodeJSON(&b, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteWordpressInput is the input parameter to DeleteWordpress.
|
||||||
|
type DeleteWordpressInput struct {
|
||||||
|
// Service is the ID of the service. Version is the specific configuration
|
||||||
|
// version. Both fields are required.
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Name is the name of the wordpress to delete (required).
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteWordpress deletes the given wordpress version.
|
||||||
|
func (c *Client) DeleteWordpress(i *DeleteWordpressInput) error {
|
||||||
|
if i.Service == "" {
|
||||||
|
return ErrMissingService
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Version == "" {
|
||||||
|
return ErrMissingVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Name == "" {
|
||||||
|
return ErrMissingName
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/service/%s/version/%s/wordpress/%s", i.Service, i.Version, i.Name)
|
||||||
|
resp, err := c.Delete(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *statusResp
|
||||||
|
if err := decodeJSON(&r, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Ok() {
|
||||||
|
return fmt.Errorf("Not Ok")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ body.layout-dnsimple,
|
||||||
body.layout-docker,
|
body.layout-docker,
|
||||||
body.layout-dyn,
|
body.layout-dyn,
|
||||||
body.layout-github,
|
body.layout-github,
|
||||||
|
body.layout-fastly,
|
||||||
body.layout-google,
|
body.layout-google,
|
||||||
body.layout-heroku,
|
body.layout-heroku,
|
||||||
body.layout-influxdb,
|
body.layout-influxdb,
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
---
|
||||||
|
layout: "fastly"
|
||||||
|
page_title: "Provider: Fastly"
|
||||||
|
sidebar_current: "docs-fastly-index"
|
||||||
|
description: |-
|
||||||
|
Fastly
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fastly Provider
|
||||||
|
|
||||||
|
The Fastly provider is used to interact with the content delivery network (CDN)
|
||||||
|
provided by Fastly.
|
||||||
|
|
||||||
|
In order to use this Provider, you must have an active account with Fastly.
|
||||||
|
Pricing and signup information can be found at https://www.fastly.com/signup
|
||||||
|
|
||||||
|
Use the navigation to the left to read about the available resources.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Configure the Fastly Provider
|
||||||
|
provider "fastly" {
|
||||||
|
api_key = "test"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a Service
|
||||||
|
resource "fastly_service_v1" "myservice" {
|
||||||
|
name = "myawesometestservice"
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
The Fastly provider offers an API key based method of providing credentials for
|
||||||
|
authentication. The following methods are supported, in this order, and
|
||||||
|
explained below:
|
||||||
|
|
||||||
|
- Static API key
|
||||||
|
- Environment variables
|
||||||
|
|
||||||
|
|
||||||
|
### Static API Key ###
|
||||||
|
|
||||||
|
Static credentials can be provided by adding a `api_key` in-line in the
|
||||||
|
Fastly provider block:
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```
|
||||||
|
provider "fastly" {
|
||||||
|
api_key = "test"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "fastly_service_v1" "myservice" {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The API key for an account can be found on the Account page: https://app.fastly.com/#account
|
||||||
|
|
||||||
|
###Environment variables
|
||||||
|
|
||||||
|
You can provide your API key via `FASTLY_API_KEY` environment variable,
|
||||||
|
representing your Fastly API key. When using this method, you may omit the
|
||||||
|
Fastly `provider` block entirely:
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "fastly_service_v1" "myservice" {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ export FASTLY_API_KEY="afastlyapikey"
|
||||||
|
$ terraform plan
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported in the `provider` block:
|
||||||
|
|
||||||
|
* `api_key` - (Optional) This is the API key. It must be provided, but
|
||||||
|
it can also be sourced from the `FASTLY_API_KEY` environment variable
|
|
@ -0,0 +1,136 @@
|
||||||
|
---
|
||||||
|
layout: "fastly"
|
||||||
|
page_title: "Fastly: aws_vpc"
|
||||||
|
sidebar_current: "docs-fastly-resource-service-v1"
|
||||||
|
description: |-
|
||||||
|
Provides an Fastly Service
|
||||||
|
---
|
||||||
|
|
||||||
|
# fastly\_service\_v1
|
||||||
|
|
||||||
|
Provides a Fastly Service, representing the configuration for a website, app,
|
||||||
|
api, or anything else to be served through Fastly. A Service encompasses Domains
|
||||||
|
and Backends.
|
||||||
|
|
||||||
|
The Service resource requires a domain name that is correctly set up to direct
|
||||||
|
traffic to the Fastly service. See Fastly's guide on [Adding CNAME Records][fastly-cname]
|
||||||
|
on their documentation site for guidance.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
Basic usage:
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "fastly_service_v1" "demo" {
|
||||||
|
name = "demofastly"
|
||||||
|
|
||||||
|
domain {
|
||||||
|
name = "demo.notexample.com"
|
||||||
|
comment = "demo"
|
||||||
|
}
|
||||||
|
|
||||||
|
backend {
|
||||||
|
address = "127.0.0.1"
|
||||||
|
name = "localhost"
|
||||||
|
port = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
force_destroy = true
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Basic usage with an Amazon S3 Website:
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "fastly_service_v1" "demo" {
|
||||||
|
name = "demofastly"
|
||||||
|
|
||||||
|
domain {
|
||||||
|
name = "demo.notexample.com"
|
||||||
|
comment = "demo"
|
||||||
|
}
|
||||||
|
|
||||||
|
backend {
|
||||||
|
address = "demo.notexample.com.s3-website-us-west-2.amazonaws.com"
|
||||||
|
name = "AWS S3 hosting"
|
||||||
|
port = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
default_host = "${aws_s3_bucket.website.name}.s3-website-us-west-2.amazonaws.com"
|
||||||
|
|
||||||
|
force_destroy = true
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_bucket" "website" {
|
||||||
|
bucket = "demo.notexample.com"
|
||||||
|
acl = "public-read"
|
||||||
|
|
||||||
|
website {
|
||||||
|
index_document = "index.html"
|
||||||
|
error_document = "error.html"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** For an AWS S3 Bucket, the Backend address is
|
||||||
|
`<domain>.s3-website-<region>.amazonaws.com`. The `default_host` attribute
|
||||||
|
should be set to `<bucket_name>.s3-website-<region>.amazonaws.com`. See the
|
||||||
|
Fastly documentation on [Amazon S3][fastly-s3]
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) The unique name for the Service to create
|
||||||
|
* `domain` - (Required) A set of Domain names to serve as entry points for your
|
||||||
|
Service. Defined below.
|
||||||
|
* `backend` - (Required) A set of Backends to service requests from your Domains.
|
||||||
|
Defined below.
|
||||||
|
* `default_host` - (Optional) The default hostname
|
||||||
|
* `default_ttl` - (Optional) The default Time-to-live (TTL) for requests
|
||||||
|
* `force_destroy` - (Optional) Services that are active cannot be destroyed. In
|
||||||
|
order to destroy the Service, set `force_destroy` to `true`. Default `false`.
|
||||||
|
|
||||||
|
|
||||||
|
The `domain` block supports:
|
||||||
|
|
||||||
|
* `name` - (Required) The domain that this Service will respond to
|
||||||
|
* `comment` - (Optional) An optional comment about the Domain
|
||||||
|
|
||||||
|
The `backend` block supports:
|
||||||
|
|
||||||
|
* `name` - (Required, string) Name for this Backend. Must be unique to this Service
|
||||||
|
* `address` - (Required, string) An IPv4, hostname, or IPv6 address for the Backend
|
||||||
|
* `auto_loadbalance` - (Optional, boolean) Denote if this Backend should be
|
||||||
|
included in the pool of backends that requests are load balanced against.
|
||||||
|
Default `true`
|
||||||
|
* `between_bytes_timeout` - (Optional) How long to wait between bytes in milliseconds. Default `10000`
|
||||||
|
* `connect_timeout` - (Optional) How long to wait for a timeout in milliseconds.
|
||||||
|
Default `1000`
|
||||||
|
* `error_threshold` - (Optional) Number of errors to allow before the Backend is marked as down. Default `0`
|
||||||
|
* `first_byte_timeout` - (Optional) How long to wait for the first bytes in milliseconds. Default `15000`
|
||||||
|
* `max_conn` - (Optional) Maximum number of connections for this Backend.
|
||||||
|
Default `200`
|
||||||
|
* `port` - (Optional) The port number Backend responds on. Default `80`
|
||||||
|
* `ssl_check_cert` - (Optional) Be strict on checking SSL certs. Default `true`
|
||||||
|
* `weight` - (Optional) How long to wait for the first bytes in milliseconds.
|
||||||
|
Default `100`
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `id` - The ID of the Service
|
||||||
|
* `name` – Name of this service
|
||||||
|
* `active_version` - The currently active version of your Fastly Service
|
||||||
|
* `domain` – Set of Domains. See above for details
|
||||||
|
* `backend` – Set of Backends. See above for details
|
||||||
|
* `default_host` – Default host specified
|
||||||
|
* `default_ttl` - Default TTL
|
||||||
|
* `force_destroy` - Force the destruction of the Service on delete
|
||||||
|
|
||||||
|
|
||||||
|
[fastly-s3]: https://docs.fastly.com/guides/integrations/amazon-s3
|
||||||
|
[fastly-cname]: https://docs.fastly.com/guides/basic-setup/adding-cname-records
|
||||||
|
|
|
@ -193,6 +193,10 @@
|
||||||
<a href="/docs/providers/github/index.html">Github</a>
|
<a href="/docs/providers/github/index.html">Github</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-providers-fastly") %>>
|
||||||
|
<a href="/docs/providers/fastly/index.html">Fastly</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-providers-google") %>>
|
<li<%= sidebar_current("docs-providers-google") %>>
|
||||||
<a href="/docs/providers/google/index.html">Google Cloud</a>
|
<a href="/docs/providers/google/index.html">Google Cloud</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<% wrap_layout :inner do %>
|
||||||
|
<% content_for :sidebar do %>
|
||||||
|
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
||||||
|
<ul class="nav docs-sidenav">
|
||||||
|
<li<%= sidebar_current("docs-home") %>>
|
||||||
|
<a href="/docs/providers/index.html">« Documentation Home</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-fastly-index") %>>
|
||||||
|
<a href="/docs/providers/fastly/index.html">Fastly Provider</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current(/^docs-fastly-resource/) %>>
|
||||||
|
<a href="#">Resources</a>
|
||||||
|
|
||||||
|
<ul class="nav nav-visible">
|
||||||
|
<li<%= sidebar_current("docs-fastly-resource-service-v1") %>>
|
||||||
|
<a href="/docs/providers/fastly/r/service_v1.html">service_v1</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= yield %>
|
||||||
|
<% end %>
|
Loading…
Reference in New Issue