├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── docker-run.sh ├── examples ├── .gitignore ├── main.tf ├── templates │ ├── cloudinit.tpl │ └── cron.tpl └── vars.tf ├── helper.go ├── helper_test.go ├── main.go ├── peers.go ├── peers_test.go ├── tables.go ├── tables_test.go └── vendor ├── github.com ├── coreos │ └── go-iptables │ │ ├── LICENSE │ │ └── iptables │ │ ├── iptables.go │ │ └── lock.go ├── digitalocean │ ├── go-metadata │ │ ├── LICENSE │ │ ├── README.md │ │ ├── all_json.go │ │ └── client.go │ └── godo │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── account.go │ │ ├── action.go │ │ ├── doc.go │ │ ├── domains.go │ │ ├── droplet_actions.go │ │ ├── droplets.go │ │ ├── errors.go │ │ ├── floating_ips.go │ │ ├── floating_ips_actions.go │ │ ├── godo.go │ │ ├── image_actions.go │ │ ├── images.go │ │ ├── keys.go │ │ ├── links.go │ │ ├── regions.go │ │ ├── sizes.go │ │ ├── storage.go │ │ ├── storage_actions.go │ │ ├── strings.go │ │ ├── tags.go │ │ └── timestamp.go ├── google │ └── go-querystring │ │ ├── LICENSE │ │ └── query │ │ └── encode.go └── tent │ └── http-link-go │ ├── LICENSE │ ├── README.md │ └── link.go ├── golang.org └── x │ ├── net │ ├── LICENSE │ ├── PATENTS │ └── context │ │ ├── context.go │ │ ├── go17.go │ │ └── pre_go17.go │ └── oauth2 │ ├── AUTHORS │ ├── CONTRIBUTING.md │ ├── CONTRIBUTORS │ ├── LICENSE │ ├── README.md │ ├── client_appengine.go │ ├── internal │ ├── oauth2.go │ ├── token.go │ └── transport.go │ ├── oauth2.go │ ├── token.go │ └── transport.go └── vendor.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.6 5 | - 1.7 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.4 2 | RUN apk add --no-cache iptables ca-certificates && \ 3 | update-ca-certificates 4 | 5 | ENV DO_KEY "" 6 | ENV DO_TAG "" 7 | 8 | ADD droplan /droplan 9 | ADD docker-run.sh /docker-run.sh 10 | 11 | CMD ["/docker-run.sh"] 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Tommy Murphy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DROPLAN_VERSION ?= latest 2 | 3 | test: 4 | go test . -cover 5 | 6 | build: 7 | go build . 8 | 9 | build-amd64: 10 | @docker run -it --rm -v `pwd`:/go/src/github.com/tam7t/droplan -w /go/src/github.com/tam7t/droplan golang:alpine env GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-X main.appVersion=${DROPLAN_VERSION}" -o droplan 11 | 12 | build-i386: 13 | @docker run -it --rm -v `pwd`:/go/src/github.com/tam7t/droplan -w /go/src/github.com/tam7t/droplan golang:alpine env GOOS=linux GOARCH=386 CGO_ENABLED=0 go build -ldflags="-X main.appVersion=${DROPLAN_VERSION}" -o droplan_i386 14 | 15 | release: build-amd64 build-i386 16 | @zip droplan_${DROPLAN_VERSION}_linux_amd64.zip droplan 17 | @tar -cvzf droplan_${DROPLAN_VERSION}_linux_amd64.tar.gz droplan 18 | @rm droplan 19 | 20 | @mv droplan_i386 droplan 21 | @zip droplan_${DROPLAN_VERSION}_linux_386.zip droplan 22 | @tar -cvzf droplan_${DROPLAN_VERSION}_linux_386.tar.gz droplan 23 | @rm droplan 24 | 25 | docker: build-amd64 docker-image clean 26 | 27 | docker-image: 28 | @docker build -t tam7t/droplan:${DROPLAN_VERSION} . 29 | 30 | clean: 31 | @rm -f droplan 32 | @rm -rf droplan_*.zip 33 | @rm -rf droplan_*.tar.gz 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # droplan [![Build Status](http://img.shields.io/travis/tam7t/droplan.svg?style=flat-square)](https://travis-ci.org/tam7t/droplan) [![Gitter](https://img.shields.io/gitter/room/tam7t/droplan.js.svg?style=flat-square)](https://gitter.im/tam7t/droplan) 2 | 3 | ## DigitalOcean Firewalls! 4 | 5 | Exciting news! DigitalOcean now has a native firewall option that integrates well with tagging. Please consider using that instead of `droplan`! 6 | 7 | https://blog.digitalocean.com/cloud-firewalls-secure-droplets-by-default/ 8 | 9 | ## About 10 | 11 | This utility helps secure the network interfaces on DigitalOcean droplets by 12 | adding `iptable` rules that only allow traffic from your other droplets. `droplan` 13 | queries the DigitalOcean API and automatically updates `iptable` rules. 14 | 15 | ## Installation 16 | 17 | The latest release is available on the github [release page](https://github.com/tam7t/droplan/releases). 18 | 19 | You can setup a cron job to run every 5 minutes in `/etc/cron.d` 20 | 21 | ``` 22 | */5 * * * * root PATH=/sbin DO_KEY=READONLY_KEY /usr/local/bin/droplan >/var/log/droplan.log 2>&1 23 | ``` 24 | 25 | ## Usage 26 | 27 | ``` 28 | DO_KEY= /path/to/droplan 29 | ``` 30 | 31 | The `iptables` rules added by `droplan` are equivalent to: 32 | 33 | ``` 34 | -N droplan-peers # create a new chain 35 | -A INPUT -i eth1 -j droplan-peers # add chain to private interface 36 | -A INPUT -i eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 37 | -A INPUT -i eth1 -j DROP # add default DROP rule to private interface 38 | -A droplan-peers -s /32 -j ACCEPT # allow traffic from PEER ip address 39 | ``` 40 | 41 | ### Tags 42 | Access can be limited to a subset of droplets using [tags](https://developers.digitalocean.com/documentation/v2/#tags). 43 | The `DO_TAG` environment variable tells `droplan` to only allow access to 44 | droplets with the specified tag. 45 | 46 | ### Public Interface 47 | Add the `PUBLIC=true` environment variable and `droplan` will maintain an 48 | iptables chain of `droplan-peers-public` with the public ip addresses of 49 | peers and add a default drop rule to the `eth0` interface. 50 | 51 | **NOTE:** This will prevent you from being able to directly ssh into your droplet. 52 | 53 | ## Development 54 | 55 | ### Dependencies 56 | 57 | Dependencies are vendored with [govendor](https://github.com/kardianos/govendor). 58 | 59 | ### Build 60 | 61 | A `Makefile` is included: 62 | * `test` - runs unit tests 63 | * `build` - builds `droplan` on the current platform 64 | * `release` - builds releasable artifacts 65 | 66 | 67 | ## Docker image: 68 | 69 | We provide a prebuilt [docker image][1] 70 | 71 | Example usage: 72 | 73 | ```sh 74 | docker run -d --restart=always --net=host --cap-add=NET_ADMIN -e DO_KEY=$your_digitalocean_api_key -e DO_INTERVAL=300 tam7t/droplan 75 | ``` 76 | 77 | - `-d --restart=always` starts the container in the background and restarts it on error (and on reboot) 78 | - `--net=host` is required because we want to affect the host's firewall rules, not the container's 79 | - `--cap-add=NET_ADMIN` to allow changing the host's firewall rules 80 | - specify `-e DO_INTERVAL=300` to change the delay (in seconds) between droplan invocations (default: execute once and exit) 81 | - you have to specify your DigitalOcean API key (using `-e DO_KEY`) 82 | - you can add `-e PUBLIC=true` or `-e DO_TAG=tagname` as described above 83 | - To manually start droplan (i.e. skip the 5 minute delay between invocations), simply use `docker restart $container-name` 84 | 85 | 86 | [1]: https://hub.docker.com/r/tam7t/droplan/ 87 | -------------------------------------------------------------------------------- /docker-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | if [ -z "$DO_INTERVAL" ]; then 6 | # if the interval is not set, only execute once 7 | ./droplan "$@" 8 | else 9 | while true; do 10 | # since we use 'set -e', this while loop will exit if droplan exits with a return value other than 0 11 | # (which in turn tells docker to restart the container (assuming the --restart option was used) 12 | # while delaying retries exponentially) 13 | ./droplan "$@" 14 | sleep "$DO_INTERVAL" 15 | done 16 | fi 17 | 18 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.tfstate 2 | *.tfstate.backup 3 | .terraform 4 | *.tfvars 5 | -------------------------------------------------------------------------------- /examples/main.tf: -------------------------------------------------------------------------------- 1 | provider "digitalocean" { 2 | token = "${var.do_token}" 3 | } 4 | 5 | resource "digitalocean_tag" "droplan" { 6 | name = "droplantag" 7 | } 8 | 9 | resource "digitalocean_ssh_key" "root_ssh" { 10 | name = "Terraform Root SSH Key" 11 | public_key = "${file(var.ssh_key_path)}" 12 | } 13 | 14 | resource "digitalocean_droplet" "droplan-ubuntu-x64" { 15 | image = "ubuntu-14-04-x64" 16 | name = "droplan-ubuntu-x64" 17 | region = "nyc3" 18 | size = "512mb" 19 | private_networking = true 20 | ssh_keys = ["${digitalocean_ssh_key.root_ssh.id}"] 21 | tags = ["${digitalocean_tag.droplan.id}"] 22 | 23 | provisioner "remote-exec" { 24 | inline = [ 25 | "cd /tmp", 26 | "curl -O -L https://github.com/tam7t/droplan/releases/download/v1.2.0/droplan_1.2.0_linux_amd64.tar.gz", 27 | "tar -zxf droplan_1.2.0_linux_amd64.tar.gz -C /usr/local/bin", 28 | "rm /tmp/droplan_1.2.0_linux_amd64.tar.gz", 29 | "echo '${data.template_file.cron.rendered}' > /etc/cron.d/droplan" 30 | ] 31 | } 32 | } 33 | 34 | resource "digitalocean_droplet" "droplan-ubuntu-x32" { 35 | image = "ubuntu-14-04-x32" 36 | name = "droplan-ubuntu-x32" 37 | region = "nyc3" 38 | size = "512mb" 39 | private_networking = true 40 | ssh_keys = ["${digitalocean_ssh_key.root_ssh.id}"] 41 | tags = ["${digitalocean_tag.droplan.id}"] 42 | 43 | provisioner "remote-exec" { 44 | inline = [ 45 | "cd /tmp", 46 | "curl -O -L https://github.com/tam7t/droplan/releases/download/v1.2.0/droplan_1.2.0_linux_386.tar.gz", 47 | "tar -zxf droplan_1.2.0_linux_386.tar.gz -C /usr/local/bin", 48 | "rm /tmp/droplan_1.2.0_linux_386.tar.gz", 49 | "echo '${data.template_file.cron.rendered}' > /etc/cron.d/droplan" 50 | ] 51 | } 52 | } 53 | 54 | resource "digitalocean_droplet" "droplan-fedora-x64" { 55 | image = "fedora-23-x64" 56 | name = "droplan-fedora-x64" 57 | region = "nyc3" 58 | size = "512mb" 59 | private_networking = true 60 | ssh_keys = ["${digitalocean_ssh_key.root_ssh.id}"] 61 | tags = ["${digitalocean_tag.droplan.id}"] 62 | 63 | provisioner "remote-exec" { 64 | inline = [ 65 | "curl -O -L https://github.com/tam7t/droplan/releases/download/v1.2.0/droplan_1.2.0_linux_amd64.tar.gz", 66 | "tar -zxf droplan_1.2.0_linux_amd64.tar.gz -C /usr/local/bin", 67 | "rm droplan_1.2.0_linux_amd64.tar.gz", 68 | "echo '${data.template_file.cron.rendered}' > /etc/cron.d/droplan" 69 | ] 70 | } 71 | } 72 | 73 | resource "digitalocean_droplet" "droplan-coreos" { 74 | image = "coreos-stable" 75 | name = "droplan-coreos" 76 | region = "nyc3" 77 | size = "512mb" 78 | private_networking = true 79 | ssh_keys = ["${digitalocean_ssh_key.root_ssh.id}"] 80 | tags = ["${digitalocean_tag.droplan.id}"] 81 | 82 | user_data = "${data.template_file.cloudinit.rendered}" 83 | } 84 | 85 | resource "digitalocean_droplet" "droplan-ubuntu-x64-notag" { 86 | image = "ubuntu-14-04-x64" 87 | name = "droplan-ubuntu-x64-notag" 88 | region = "nyc3" 89 | size = "512mb" 90 | private_networking = true 91 | ssh_keys = ["${digitalocean_ssh_key.root_ssh.id}"] 92 | 93 | provisioner "remote-exec" { 94 | inline = [ 95 | "cd /tmp", 96 | "curl -O -L https://github.com/tam7t/droplan/releases/download/v1.2.0/droplan_1.2.0_linux_amd64.tar.gz", 97 | "tar -zxf droplan_1.2.0_linux_amd64.tar.gz -C /usr/local/bin", 98 | "rm /tmp/droplan_1.2.0_linux_amd64.tar.gz", 99 | "echo '${data.template_file.cron.rendered}' > /etc/cron.d/droplan" 100 | ] 101 | } 102 | } 103 | 104 | data "template_file" "cloudinit" { 105 | template = "${file("${path.module}/templates/cloudinit.tpl")}" 106 | 107 | vars { 108 | key = "${var.droplan_token}" 109 | tag = "${digitalocean_tag.droplan.id}" 110 | } 111 | } 112 | 113 | data "template_file" "cron" { 114 | template = "${file("${path.module}/templates/cron.tpl")}" 115 | 116 | vars { 117 | key = "${var.droplan_token}" 118 | tag = "${digitalocean_tag.droplan.id}" 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /examples/templates/cloudinit.tpl: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | coreos: 4 | units: 5 | - name: droplan-setup.service 6 | command: start 7 | content: | 8 | [Unit] 9 | Description=setup droplan iptable rules for docker 10 | 11 | [Service] 12 | Type=oneshot 13 | After=docker.service 14 | ExecStart=/usr/bin/sh -c "docker ps; \ 15 | iptables -N droplan-peers; \ 16 | iptables -I FORWARD 1 -i eth1 -j DROP; \ 17 | iptables -I FORWARD 1 -i eth1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT; \ 18 | iptables -I FORWARD 1 -j droplan-peers" 19 | - name: droplan.service 20 | command: start 21 | content: | 22 | [Unit] 23 | Description=updates iptables with peer droplets 24 | After=droplan-setup.service 25 | Requires=docker.service 26 | 27 | [Service] 28 | Type=oneshot 29 | Environment=DO_KEY=${key} 30 | Environment=DO_TAG=${tag} 31 | ExecStart=/usr/bin/docker run --rm --net=host --cap-add=NET_ADMIN -e DO_KEY -e DO_TAG tam7t/droplan:latest 32 | - name: droplan.timer 33 | command: start 34 | content: | 35 | [Unit] 36 | Description=Run droplan.service every 5 minutes 37 | 38 | [Timer] 39 | OnCalendar=*:0/5 40 | -------------------------------------------------------------------------------- /examples/templates/cron.tpl: -------------------------------------------------------------------------------- 1 | */5 * * * * root PATH=/sbin DO_KEY=${key} DO_TAG=${tag} /usr/local/bin/droplan >/var/log/droplan.log 2>&1 2 | -------------------------------------------------------------------------------- /examples/vars.tf: -------------------------------------------------------------------------------- 1 | variable "do_token" {} 2 | variable "droplan_token" {} 3 | variable "ssh_key_path" {} 4 | -------------------------------------------------------------------------------- /helper.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | 7 | "github.com/digitalocean/go-metadata" 8 | ) 9 | 10 | // FindInterfaceName returns the network interface name of the provided local 11 | // ip address 12 | func FindInterfaceName(ifaces []net.Interface, local string) (string, error) { 13 | for _, i := range ifaces { 14 | addrs, err := i.Addrs() 15 | if err != nil { 16 | return "", err 17 | } 18 | 19 | for _, addr := range addrs { 20 | switch v := addr.(type) { 21 | case *net.IPAddr: 22 | ip := v.IP.String() 23 | if ip == local { 24 | return i.Name, nil 25 | } 26 | case *net.IPNet: 27 | ip := v.IP.String() 28 | if ip == local { 29 | return i.Name, nil 30 | } 31 | } 32 | } 33 | } 34 | 35 | return "", errors.New("local interface could not be found") 36 | } 37 | 38 | // PrivateAddress parses metadata to find the local private ipv4 interface 39 | // address 40 | func PrivateAddress(data *metadata.Metadata) (string, error) { 41 | privateIface := data.Interfaces["private"] 42 | if len(privateIface) >= 1 { 43 | ipV4 := privateIface[0].IPv4 44 | if ipV4 == nil { 45 | return "", errors.New("no ipv4 private iface") 46 | } 47 | 48 | return ipV4.IPAddress, nil 49 | } 50 | return "", errors.New("no private interfaces") 51 | } 52 | 53 | // PublicAddress parses metadata to find the local public ipv4 interface 54 | // address 55 | func PublicAddress(data *metadata.Metadata) (string, error) { 56 | publicIface := data.Interfaces["public"] 57 | if len(publicIface) >= 1 { 58 | ipV4 := publicIface[0].IPv4 59 | if ipV4 == nil { 60 | return "", errors.New("no ipv4 public iface") 61 | } 62 | 63 | return ipV4.IPAddress, nil 64 | } 65 | return "", errors.New("no public interfaces") 66 | } 67 | -------------------------------------------------------------------------------- /helper_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net" 7 | "reflect" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/digitalocean/go-metadata" 12 | ) 13 | 14 | func TestFindInterfaceName(t *testing.T) { 15 | ifaces, _ := net.Interfaces() 16 | addrs, _ := ifaces[0].Addrs() 17 | localAddr := addrs[0] 18 | ip := localAddr.String() 19 | switch v := localAddr.(type) { 20 | case *net.IPAddr: 21 | ip = v.IP.String() 22 | case *net.IPNet: 23 | ip = v.IP.String() 24 | } 25 | 26 | tests := []struct { 27 | name string 28 | ifaces []net.Interface 29 | local string 30 | exp string 31 | expErr error 32 | }{ 33 | { 34 | name: "valid ip", 35 | ifaces: ifaces, 36 | local: ip, 37 | exp: ifaces[0].Name, 38 | expErr: nil, 39 | }, 40 | { 41 | name: "invalid ip", 42 | ifaces: ifaces, 43 | local: "somethingbad", 44 | exp: "", 45 | expErr: errors.New("local interface could not be found"), 46 | }, 47 | } 48 | 49 | for _, test := range tests { 50 | out, err := FindInterfaceName(test.ifaces, test.local) 51 | if !reflect.DeepEqual(err, test.expErr) { 52 | t.Logf("want:%v", test.expErr) 53 | t.Logf("got:%v", err) 54 | t.Fatalf("test case failed: %s", test.name) 55 | } 56 | if out != test.exp { 57 | t.Logf("want:%v", test.exp) 58 | t.Logf("got:%v", out) 59 | t.Fatalf("test case failed: %s", test.name) 60 | } 61 | } 62 | } 63 | 64 | func TestPrivateAddress(t *testing.T) { 65 | tests := []struct { 66 | name string 67 | data *metadata.Metadata 68 | exp string 69 | expErr error 70 | }{ 71 | { 72 | name: "private ipv4 address", 73 | data: decodeMetadata(`{"interfaces": {"private": [{"ipv4": {"ip_address": "privateIP"}}]}}`), 74 | exp: "privateIP", 75 | expErr: nil, 76 | }, 77 | { 78 | name: "private ipv6 address", 79 | data: decodeMetadata(`{"interfaces": {"private": [{"ipv6": {"ip_address": "privateIP"}}]}}`), 80 | exp: "", 81 | expErr: errors.New("no ipv4 private iface"), 82 | }, 83 | { 84 | name: "no private addresses", 85 | data: &metadata.Metadata{}, 86 | exp: "", 87 | expErr: errors.New("no private interfaces"), 88 | }, 89 | } 90 | 91 | for _, test := range tests { 92 | out, err := PrivateAddress(test.data) 93 | if !reflect.DeepEqual(err, test.expErr) { 94 | t.Logf("want:%v", test.expErr) 95 | t.Logf("got:%v", err) 96 | t.Fatalf("test case failed: %s", test.name) 97 | } 98 | if out != test.exp { 99 | t.Logf("want:%v", test.exp) 100 | t.Logf("got:%v", out) 101 | t.Fatalf("test case failed: %s", test.name) 102 | } 103 | } 104 | } 105 | 106 | func TestPublicAddress(t *testing.T) { 107 | tests := []struct { 108 | name string 109 | data *metadata.Metadata 110 | exp string 111 | expErr error 112 | }{ 113 | { 114 | name: "public ipv4 address", 115 | data: decodeMetadata(`{"interfaces": {"public": [{"ipv4": {"ip_address": "publicIP"}}]}}`), 116 | exp: "publicIP", 117 | expErr: nil, 118 | }, 119 | { 120 | name: "public ipv6 address", 121 | data: decodeMetadata(`{"interfaces": {"public": [{"ipv6": {"ip_address": "publicIP"}}]}}`), 122 | exp: "", 123 | expErr: errors.New("no ipv4 public iface"), 124 | }, 125 | { 126 | name: "no public addresses", 127 | data: &metadata.Metadata{}, 128 | exp: "", 129 | expErr: errors.New("no public interfaces"), 130 | }, 131 | } 132 | 133 | for _, test := range tests { 134 | out, err := PublicAddress(test.data) 135 | if !reflect.DeepEqual(err, test.expErr) { 136 | t.Logf("want:%v", test.expErr) 137 | t.Logf("got:%v", err) 138 | t.Fatalf("test case failed: %s", test.name) 139 | } 140 | if out != test.exp { 141 | t.Logf("want:%v", test.exp) 142 | t.Logf("got:%v", out) 143 | t.Fatalf("test case failed: %s", test.name) 144 | } 145 | } 146 | } 147 | 148 | func decodeMetadata(data string) *metadata.Metadata { 149 | var output metadata.Metadata 150 | var err error 151 | 152 | err = json.NewDecoder(strings.NewReader(data)).Decode(&output) 153 | if err != nil { 154 | panic(err) 155 | } 156 | return &output 157 | } 158 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net" 7 | "os" 8 | 9 | "github.com/coreos/go-iptables/iptables" 10 | "github.com/digitalocean/go-metadata" 11 | "github.com/digitalocean/godo" 12 | "golang.org/x/oauth2" 13 | ) 14 | 15 | var appVersion string 16 | 17 | func main() { 18 | version := flag.Bool("version", false, "Print the version and exit.") 19 | flag.Parse() 20 | if *version { 21 | log.Printf(appVersion) 22 | os.Exit(0) 23 | } 24 | 25 | accessToken := os.Getenv("DO_KEY") 26 | if accessToken == "" { 27 | log.Fatal("Usage: DO_KEY environment variable must be set.") 28 | } 29 | 30 | peerTag := os.Getenv("DO_TAG") 31 | 32 | // PUBLIC=true will tell us to block traffic on the public interface 33 | public := os.Getenv("PUBLIC") 34 | 35 | // setup dependencies 36 | oauthClient := oauth2.NewClient(oauth2.NoContext, oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken})) 37 | apiClient := godo.NewClient(oauthClient) 38 | metaClient := metadata.NewClient() 39 | ipt, err := iptables.New() 40 | failIfErr(err) 41 | 42 | // collect needed metadata from metadata service 43 | region, err := metaClient.Region() 44 | failIfErr(err) 45 | mData, err := metaClient.Metadata() 46 | failIfErr(err) 47 | 48 | // collect list of all droplets 49 | var drops []godo.Droplet 50 | if peerTag != "" { 51 | drops, err = DropletListTags(apiClient.Droplets, peerTag) 52 | } else { 53 | drops, err = DropletList(apiClient.Droplets) 54 | } 55 | failIfErr(err) 56 | 57 | // collect local network interface information 58 | ifaces, err := net.Interfaces() 59 | failIfErr(err) 60 | 61 | pubAddr, err := PublicAddress(mData) 62 | failIfErr(err) 63 | 64 | if public == "true" { 65 | publicPeers := PublicDroplets(drops) 66 | 67 | // find public iface name 68 | iface, err := FindInterfaceName(ifaces, pubAddr) 69 | failIfErr(err) 70 | 71 | // setup droplan-peers-public chain for public interface 72 | err = Setup(ipt, iface, "droplan-peers-public") 73 | failIfErr(err) 74 | 75 | // update droplan-peers-public 76 | err = UpdatePeers(ipt, publicPeers, "droplan-peers-public") 77 | failIfErr(err) 78 | log.Printf("Added %d peers to droplan-peers-public", len(publicPeers)) 79 | } 80 | 81 | privAddr, err := PrivateAddress(mData) 82 | failIfErr(err) 83 | 84 | privatePeers, ok := SortDroplets(drops)[region] 85 | if !ok { 86 | log.Printf("No droplets listed in region [%s]", region) 87 | } 88 | 89 | // find private iface name 90 | iface, err := FindInterfaceName(ifaces, privAddr) 91 | if public != "" && err != nil && err.Error() == "no private interfaces" { 92 | os.Exit(0) 93 | } 94 | failIfErr(err) 95 | 96 | // setup droplan-peers chain for private interface 97 | err = Setup(ipt, iface, "droplan-peers") 98 | failIfErr(err) 99 | 100 | // update droplan-peers 101 | err = UpdatePeers(ipt, privatePeers, "droplan-peers") 102 | failIfErr(err) 103 | log.Printf("Added %d peers to droplan-peers", len(privatePeers)) 104 | } 105 | 106 | func failIfErr(err error) { 107 | if err != nil { 108 | log.Fatal(err) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /peers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/digitalocean/godo" 4 | 5 | // SortDroplets returns a map (keyed by region slug) of droplets with private ip 6 | // interfaces 7 | func SortDroplets(droplets []godo.Droplet) map[string][]string { 8 | netDrops := map[string][]string{} 9 | 10 | for _, droplet := range droplets { 11 | for _, net := range droplet.Networks.V4 { 12 | if net.Type == "private" { 13 | _, ok := netDrops[droplet.Region.Slug] 14 | if ok { 15 | netDrops[droplet.Region.Slug] = append(netDrops[droplet.Region.Slug], net.IPAddress) 16 | } else { 17 | netDrops[droplet.Region.Slug] = []string{net.IPAddress} 18 | } 19 | } 20 | } 21 | } 22 | 23 | return netDrops 24 | } 25 | 26 | // PublicDroplets returns an array of all the public ip interfaces of the provided 27 | // droplets 28 | func PublicDroplets(droplets []godo.Droplet) []string { 29 | netDrops := []string{} 30 | for _, droplet := range droplets { 31 | for _, net := range droplet.Networks.V4 { 32 | if net.Type == "public" { 33 | netDrops = append(netDrops, net.IPAddress) 34 | } 35 | } 36 | } 37 | return netDrops 38 | } 39 | 40 | // DropletList paginates through the digitalocean API to return a list of all 41 | // droplets 42 | func DropletList(ds godo.DropletsService) ([]godo.Droplet, error) { 43 | // create a list to hold our droplets 44 | list := []godo.Droplet{} 45 | 46 | // create options. initially, these will be blank 47 | opt := &godo.ListOptions{} 48 | for { 49 | droplets, resp, err := ds.List(opt) 50 | 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | // append the current page's droplets to our list 56 | for _, d := range droplets { 57 | list = append(list, d) 58 | } 59 | 60 | // if we are at the last page, break out the for loop 61 | if resp.Links == nil || resp.Links.IsLastPage() { 62 | break 63 | } 64 | 65 | page, err := resp.Links.CurrentPage() 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | // set the page we want for the next request 71 | opt.Page = page + 1 72 | } 73 | 74 | return list, nil 75 | } 76 | 77 | // DropletListTags paginates through the digitalocean API to return a list of 78 | // all droplets with the given tag 79 | func DropletListTags(ds godo.DropletsService, tag string) ([]godo.Droplet, error) { 80 | // create a list to hold our droplets 81 | list := []godo.Droplet{} 82 | 83 | // create options. initially, these will be blank 84 | opt := &godo.ListOptions{} 85 | for { 86 | droplets, resp, err := ds.ListByTag(tag, opt) 87 | 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | // append the current page's droplets to our list 93 | for _, d := range droplets { 94 | list = append(list, d) 95 | } 96 | 97 | // if we are at the last page, break out the for loop 98 | if resp.Links == nil || resp.Links.IsLastPage() { 99 | break 100 | } 101 | 102 | page, err := resp.Links.CurrentPage() 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | // set the page we want for the next request 108 | opt.Page = page + 1 109 | } 110 | 111 | return list, nil 112 | } 113 | -------------------------------------------------------------------------------- /peers_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/digitalocean/godo" 9 | ) 10 | 11 | func TestDropletList(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | ds *stubDropletService 15 | expectedDroplets []godo.Droplet 16 | expectedError error 17 | }{ 18 | { 19 | name: "no droplets", 20 | ds: &stubDropletService{ 21 | list: func(b *godo.ListOptions) ([]godo.Droplet, *godo.Response, error) { 22 | resp := &godo.Response{} 23 | resp.Links = nil 24 | return []godo.Droplet{}, resp, nil 25 | }, 26 | }, 27 | expectedDroplets: []godo.Droplet{}, 28 | }, 29 | { 30 | name: "single page of droplets", 31 | ds: &stubDropletService{ 32 | list: func(b *godo.ListOptions) ([]godo.Droplet, *godo.Response, error) { 33 | resp := &godo.Response{} 34 | resp.Links = nil 35 | return []godo.Droplet{{Name: "foobar"}}, resp, nil 36 | }, 37 | }, 38 | expectedDroplets: []godo.Droplet{{Name: "foobar"}}, 39 | }, 40 | { 41 | name: "multiple pages of droplets", 42 | ds: &stubDropletService{ 43 | list: func(b *godo.ListOptions) ([]godo.Droplet, *godo.Response, error) { 44 | resp := &godo.Response{} 45 | drops := []godo.Droplet{} 46 | if b.Page == 0 { 47 | resp.Links = &godo.Links{Pages: &godo.Pages{Next: "http://example.com/droplets?page=2", Last: "http://example.com/droplets?page=2"}} 48 | drops = append(drops, godo.Droplet{Name: "firstPage"}) 49 | } else { 50 | resp.Links = &godo.Links{Pages: &godo.Pages{Prev: "http://example.com/droplets?page=1"}} 51 | drops = append(drops, godo.Droplet{Name: "secondPage"}) 52 | } 53 | return drops, resp, nil 54 | }, 55 | }, 56 | expectedDroplets: []godo.Droplet{{Name: "firstPage"}, {Name: "secondPage"}}, 57 | }, 58 | { 59 | name: "list errors", 60 | ds: &stubDropletService{ 61 | list: func(b *godo.ListOptions) ([]godo.Droplet, *godo.Response, error) { 62 | return []godo.Droplet{}, nil, errors.New("asdf") 63 | }, 64 | }, 65 | expectedError: errors.New("asdf"), 66 | }, 67 | { 68 | name: "current page errors", 69 | ds: &stubDropletService{ 70 | list: func(b *godo.ListOptions) ([]godo.Droplet, *godo.Response, error) { 71 | resp := &godo.Response{} 72 | resp.Links = &godo.Links{Pages: &godo.Pages{Prev: "page=)", Last: "page="}} 73 | return []godo.Droplet{{Name: "foobar"}}, resp, nil 74 | }, 75 | }, 76 | expectedError: errors.New("parse page=): invalid URI for request"), 77 | }, 78 | } 79 | 80 | for _, test := range tests { 81 | out, err := DropletList(test.ds) 82 | if !reflect.DeepEqual(err, test.expectedError) { 83 | if err.Error() != test.expectedError.Error() { 84 | t.Logf("want:%v", test.expectedError) 85 | t.Logf("got:%v", err) 86 | t.Fatalf("test case failed: %s", test.name) 87 | } 88 | } 89 | if !reflect.DeepEqual(out, test.expectedDroplets) { 90 | t.Logf("want:%v", test.expectedDroplets) 91 | t.Logf("got:%v", out) 92 | t.Fatalf("test case failed: %s", test.name) 93 | } 94 | } 95 | } 96 | 97 | func TestDropletListTags(t *testing.T) { 98 | tests := []struct { 99 | name string 100 | ds *stubDropletService 101 | expectedDroplets []godo.Droplet 102 | expectedError error 103 | }{ 104 | { 105 | name: "no droplets", 106 | ds: &stubDropletService{ 107 | listTag: func(a string, b *godo.ListOptions) ([]godo.Droplet, *godo.Response, error) { 108 | resp := &godo.Response{} 109 | resp.Links = nil 110 | return []godo.Droplet{}, resp, nil 111 | }, 112 | }, 113 | expectedDroplets: []godo.Droplet{}, 114 | }, 115 | { 116 | name: "single page of droplets", 117 | ds: &stubDropletService{ 118 | listTag: func(a string, b *godo.ListOptions) ([]godo.Droplet, *godo.Response, error) { 119 | resp := &godo.Response{} 120 | resp.Links = nil 121 | return []godo.Droplet{{Name: "foobar"}}, resp, nil 122 | }, 123 | }, 124 | expectedDroplets: []godo.Droplet{{Name: "foobar"}}, 125 | }, 126 | { 127 | name: "multiple pages of droplets", 128 | ds: &stubDropletService{ 129 | listTag: func(a string, b *godo.ListOptions) ([]godo.Droplet, *godo.Response, error) { 130 | resp := &godo.Response{} 131 | drops := []godo.Droplet{} 132 | if b.Page == 0 { 133 | resp.Links = &godo.Links{Pages: &godo.Pages{Next: "http://example.com/droplets?page=2", Last: "http://example.com/droplets?page=2"}} 134 | drops = append(drops, godo.Droplet{Name: "firstPage"}) 135 | } else { 136 | resp.Links = &godo.Links{Pages: &godo.Pages{Prev: "http://example.com/droplets?page=1"}} 137 | drops = append(drops, godo.Droplet{Name: "secondPage"}) 138 | } 139 | return drops, resp, nil 140 | }, 141 | }, 142 | expectedDroplets: []godo.Droplet{{Name: "firstPage"}, {Name: "secondPage"}}, 143 | }, 144 | { 145 | name: "list errors", 146 | ds: &stubDropletService{ 147 | listTag: func(a string, b *godo.ListOptions) ([]godo.Droplet, *godo.Response, error) { 148 | return []godo.Droplet{}, nil, errors.New("asdf") 149 | }, 150 | }, 151 | expectedError: errors.New("asdf"), 152 | }, 153 | { 154 | name: "current page errors", 155 | ds: &stubDropletService{ 156 | listTag: func(a string, b *godo.ListOptions) ([]godo.Droplet, *godo.Response, error) { 157 | resp := &godo.Response{} 158 | resp.Links = &godo.Links{Pages: &godo.Pages{Prev: "page=)", Last: "page="}} 159 | return []godo.Droplet{{Name: "foobar"}}, resp, nil 160 | }, 161 | }, 162 | expectedError: errors.New("parse page=): invalid URI for request"), 163 | }, 164 | } 165 | 166 | for _, test := range tests { 167 | out, err := DropletListTags(test.ds, "access") 168 | if !reflect.DeepEqual(err, test.expectedError) { 169 | if err.Error() != test.expectedError.Error() { 170 | t.Logf("want:%v", test.expectedError) 171 | t.Logf("got:%v", err) 172 | t.Fatalf("test case failed: %s", test.name) 173 | } 174 | } 175 | if !reflect.DeepEqual(out, test.expectedDroplets) { 176 | t.Logf("want:%v", test.expectedDroplets) 177 | t.Logf("got:%v", out) 178 | t.Fatalf("test case failed: %s", test.name) 179 | } 180 | } 181 | } 182 | 183 | func TestSortDroplets(t *testing.T) { 184 | tests := []struct { 185 | name string 186 | droplet godo.Droplet 187 | exp map[string][]string 188 | }{ 189 | { 190 | name: "no private iface", 191 | droplet: godo.Droplet{ 192 | Region: &godo.Region{ 193 | Slug: "nyc1", 194 | }, 195 | Networks: &godo.Networks{ 196 | V4: []godo.NetworkV4{ 197 | godo.NetworkV4{IPAddress: "192.168.0.0", Type: "public"}, 198 | }, 199 | }, 200 | }, 201 | exp: map[string][]string{}, 202 | }, 203 | { 204 | name: "private iface", 205 | droplet: godo.Droplet{ 206 | Region: &godo.Region{ 207 | Slug: "nyc1", 208 | }, 209 | Networks: &godo.Networks{ 210 | V4: []godo.NetworkV4{ 211 | godo.NetworkV4{IPAddress: "192.168.0.0", Type: "private"}, 212 | }, 213 | }, 214 | }, 215 | exp: map[string][]string{ 216 | "nyc1": []string{"192.168.0.0"}, 217 | }, 218 | }, 219 | } 220 | 221 | for _, test := range tests { 222 | out := SortDroplets([]godo.Droplet{test.droplet}) 223 | if !reflect.DeepEqual(out, test.exp) { 224 | t.Logf("want:%v", test.exp) 225 | t.Logf("got:%v", out) 226 | t.Fatalf("test case failed: %s", test.name) 227 | } 228 | } 229 | } 230 | 231 | func TestPublicDroplets(t *testing.T) { 232 | tests := []struct { 233 | name string 234 | droplet godo.Droplet 235 | exp []string 236 | }{ 237 | { 238 | name: "no public iface", 239 | droplet: godo.Droplet{ 240 | Region: &godo.Region{ 241 | Slug: "nyc1", 242 | }, 243 | Networks: &godo.Networks{ 244 | V4: []godo.NetworkV4{ 245 | godo.NetworkV4{IPAddress: "192.168.0.0", Type: "private"}, 246 | }, 247 | }, 248 | }, 249 | exp: []string{}, 250 | }, 251 | { 252 | name: "public iface", 253 | droplet: godo.Droplet{ 254 | Region: &godo.Region{ 255 | Slug: "nyc1", 256 | }, 257 | Networks: &godo.Networks{ 258 | V4: []godo.NetworkV4{ 259 | godo.NetworkV4{IPAddress: "192.168.0.0", Type: "public"}, 260 | }, 261 | }, 262 | }, 263 | exp: []string{"192.168.0.0"}, 264 | }, 265 | } 266 | 267 | for _, test := range tests { 268 | out := PublicDroplets([]godo.Droplet{test.droplet}) 269 | if !reflect.DeepEqual(out, test.exp) { 270 | t.Logf("want:%v", test.exp) 271 | t.Logf("got:%v", out) 272 | t.Fatalf("test case failed: %s", test.name) 273 | } 274 | } 275 | } 276 | 277 | type stubDropletService struct { 278 | list func(*godo.ListOptions) ([]godo.Droplet, *godo.Response, error) 279 | listTag func(string, *godo.ListOptions) ([]godo.Droplet, *godo.Response, error) 280 | get func(int) (*godo.Droplet, *godo.Response, error) 281 | create func(*godo.DropletCreateRequest) (*godo.Droplet, *godo.Response, error) 282 | createMultiple func(*godo.DropletMultiCreateRequest) ([]godo.Droplet, *godo.Response, error) 283 | delete func(int) (*godo.Response, error) 284 | deleteTag func(string) (*godo.Response, error) 285 | kernels func(int, *godo.ListOptions) ([]godo.Kernel, *godo.Response, error) 286 | snapshots func(int, *godo.ListOptions) ([]godo.Image, *godo.Response, error) 287 | backups func(int, *godo.ListOptions) ([]godo.Image, *godo.Response, error) 288 | actions func(int, *godo.ListOptions) ([]godo.Action, *godo.Response, error) 289 | neighbors func(int) ([]godo.Droplet, *godo.Response, error) 290 | } 291 | 292 | func (sds *stubDropletService) List(a *godo.ListOptions) ([]godo.Droplet, *godo.Response, error) { 293 | return sds.list(a) 294 | } 295 | 296 | func (sds *stubDropletService) ListByTag(a string, b *godo.ListOptions) ([]godo.Droplet, *godo.Response, error) { 297 | return sds.listTag(a, b) 298 | } 299 | 300 | func (sds *stubDropletService) Get(a int) (*godo.Droplet, *godo.Response, error) { 301 | return sds.get(a) 302 | } 303 | 304 | func (sds *stubDropletService) Create(a *godo.DropletCreateRequest) (*godo.Droplet, *godo.Response, error) { 305 | return sds.create(a) 306 | } 307 | 308 | func (sds *stubDropletService) CreateMultiple(a *godo.DropletMultiCreateRequest) ([]godo.Droplet, *godo.Response, error) { 309 | return sds.createMultiple(a) 310 | } 311 | 312 | func (sds *stubDropletService) Delete(a int) (*godo.Response, error) { 313 | return sds.delete(a) 314 | } 315 | 316 | func (sds *stubDropletService) DeleteByTag(a string) (*godo.Response, error) { 317 | return sds.deleteTag(a) 318 | } 319 | 320 | func (sds *stubDropletService) Kernels(a int, b *godo.ListOptions) ([]godo.Kernel, *godo.Response, error) { 321 | return sds.kernels(a, b) 322 | } 323 | 324 | func (sds *stubDropletService) Snapshots(a int, b *godo.ListOptions) ([]godo.Image, *godo.Response, error) { 325 | return sds.snapshots(a, b) 326 | } 327 | 328 | func (sds *stubDropletService) Backups(a int, b *godo.ListOptions) ([]godo.Image, *godo.Response, error) { 329 | return sds.backups(a, b) 330 | } 331 | 332 | func (sds *stubDropletService) Actions(a int, b *godo.ListOptions) ([]godo.Action, *godo.Response, error) { 333 | return sds.actions(a, b) 334 | } 335 | 336 | func (sds *stubDropletService) Neighbors(a int) ([]godo.Droplet, *godo.Response, error) { 337 | return sds.neighbors(a) 338 | } 339 | -------------------------------------------------------------------------------- /tables.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // IPTables interface for interacting with an iptables library. Declare it this 4 | // way so that it is easy to dependency inject a mock. 5 | type IPTables interface { 6 | ClearChain(string, string) error 7 | Append(string, string, ...string) error 8 | AppendUnique(string, string, ...string) error 9 | NewChain(string, string) error 10 | } 11 | 12 | // Setup creates a new iptables chain for holding peers and adds the chain and 13 | // deny rules to the specified interface 14 | func Setup(ipt IPTables, ipFace, chain string) error { 15 | var err error 16 | 17 | err = ipt.NewChain("filter", chain) 18 | if err != nil { 19 | if err.Error() != "exit status 1: iptables: Chain already exists.\n" { 20 | return err 21 | } 22 | } 23 | 24 | err = ipt.AppendUnique("filter", "INPUT", "-i", ipFace, "-j", chain) 25 | if err != nil { 26 | return err 27 | } 28 | // Do not drop connections when the `droplan-peers` chain is being updated 29 | err = ipt.AppendUnique("filter", "INPUT", "-i", ipFace, "-m", "conntrack", "--ctstate", "ESTABLISHED,RELATED", "-j", "ACCEPT") 30 | if err != nil { 31 | return err 32 | } 33 | err = ipt.AppendUnique("filter", "INPUT", "-i", ipFace, "-j", "DROP") 34 | if err != nil { 35 | return err 36 | } 37 | return nil 38 | } 39 | 40 | // UpdatePeers updates the droplan-peers chain in iptables with the specified 41 | // peers 42 | func UpdatePeers(ipt IPTables, peers []string, chain string) error { 43 | err := ipt.ClearChain("filter", chain) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | for _, peer := range peers { 49 | err := ipt.Append("filter", chain, "-s", peer, "-j", "ACCEPT") 50 | if err != nil { 51 | return err 52 | } 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /tables_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestSetup(t *testing.T) { 10 | count := 0 11 | 12 | tests := []struct { 13 | name string 14 | ipt IPTables 15 | exp error 16 | }{ 17 | { 18 | name: "chain exists", 19 | ipt: &stubIPTables{ 20 | newChain: func(a, b string) error { 21 | return errors.New("exit status 1: iptables: Chain already exists.\n") 22 | }, 23 | clearChain: func(string, string) error { return nil }, 24 | appendUnique: func(string, string, ...string) error { return nil }, 25 | append: func(string, string, ...string) error { return nil }, 26 | }, 27 | exp: nil, 28 | }, 29 | { 30 | name: "chain error", 31 | ipt: &stubIPTables{ 32 | newChain: func(a, b string) error { 33 | return errors.New("something bad") 34 | }, 35 | clearChain: func(string, string) error { return nil }, 36 | appendUnique: func(string, string, ...string) error { return nil }, 37 | append: func(string, string, ...string) error { return nil }, 38 | }, 39 | exp: errors.New("something bad"), 40 | }, 41 | { 42 | name: "adds the droplan-peers filter", 43 | ipt: &stubIPTables{ 44 | newChain: func(a, b string) error { 45 | if a == "filter" && b == "droplan-peers" { 46 | return nil 47 | } 48 | return errors.New("bad params") 49 | }, 50 | clearChain: func(string, string) error { return nil }, 51 | appendUnique: func(string, string, ...string) error { return nil }, 52 | append: func(string, string, ...string) error { return nil }, 53 | }, 54 | exp: nil, 55 | }, 56 | { 57 | name: "when adding the chain to the interface errors", 58 | ipt: &stubIPTables{ 59 | newChain: func(string, string) error { return nil }, 60 | clearChain: func(string, string) error { return nil }, 61 | appendUnique: func(a, b string, c ...string) error { 62 | if a == "filter" && b == "INPUT" && len(c) == 4 { 63 | if c[0] == "-i" && c[1] == "eth1" && c[2] == "-j" && c[3] == "droplan-peers" { 64 | return errors.New("bad add chain") 65 | } 66 | } 67 | return nil 68 | }, 69 | append: func(string, string, ...string) error { return nil }, 70 | }, 71 | exp: errors.New("bad add chain"), 72 | }, 73 | { 74 | name: "when adding the established connection rule errors", 75 | ipt: &stubIPTables{ 76 | newChain: func(string, string) error { return nil }, 77 | clearChain: func(string, string) error { return nil }, 78 | appendUnique: func(a, b string, c ...string) error { 79 | if a == "filter" && b == "INPUT" && len(c) == 8 { 80 | return errors.New("bad connect rule") 81 | } 82 | return nil 83 | }, 84 | append: func(string, string, ...string) error { return nil }, 85 | }, 86 | exp: errors.New("bad connect rule"), 87 | }, 88 | { 89 | name: "when adding the deny rule errors", 90 | ipt: &stubIPTables{ 91 | newChain: func(string, string) error { return nil }, 92 | clearChain: func(string, string) error { return nil }, 93 | appendUnique: func(a, b string, c ...string) error { 94 | if a == "filter" && b == "INPUT" && len(c) == 4 { 95 | if c[0] == "-i" && c[1] == "eth1" && c[2] == "-j" && c[3] == "DROP" { 96 | return errors.New("bad deny rule") 97 | } 98 | } 99 | return nil 100 | }, 101 | append: func(string, string, ...string) error { return nil }, 102 | }, 103 | exp: errors.New("bad deny rule"), 104 | }, 105 | { 106 | name: "append rules in order", 107 | ipt: &stubIPTables{ 108 | newChain: func(string, string) error { return nil }, 109 | clearChain: func(string, string) error { return nil }, 110 | appendUnique: func(a, b string, c ...string) error { 111 | if a == "filter" && b == "INPUT" && len(c) == 4 { 112 | if c[0] == "-i" && c[1] == "eth1" && c[2] == "-j" && c[3] == "DROP" { 113 | return errors.New("bad deny rule") 114 | } 115 | } 116 | return nil 117 | }, 118 | append: func(string, string, ...string) error { return nil }, 119 | }, 120 | exp: errors.New("bad deny rule"), 121 | }, 122 | { 123 | name: "adds peer chain and drop interface", 124 | ipt: &stubIPTables{ 125 | newChain: func(string, string) error { return nil }, 126 | clearChain: func(string, string) error { return nil }, 127 | appendUnique: func(a, b string, c ...string) error { 128 | defer func() { count++ }() 129 | switch count { 130 | case 0: 131 | if a == "filter" && b == "INPUT" && reflect.DeepEqual(c, []string{"-i", "eth1", "-j", "droplan-peers"}) { 132 | return nil 133 | } else { 134 | return errors.New("bad case 0") 135 | } 136 | case 1: 137 | if a == "filter" && b == "INPUT" && reflect.DeepEqual(c, []string{"-i", "eth1", "-m", "conntrack", "--ctstate", "ESTABLISHED,RELATED", "-j", "ACCEPT"}) { 138 | return nil 139 | } else { 140 | return errors.New("bad case 1") 141 | } 142 | case 2: 143 | if a == "filter" && b == "INPUT" && reflect.DeepEqual(c, []string{"-i", "eth1", "-j", "DROP"}) { 144 | return nil 145 | } else { 146 | return errors.New("bad case 2") 147 | } 148 | default: 149 | return errors.New("bad input") 150 | } 151 | return nil 152 | }, 153 | append: func(string, string, ...string) error { return nil }, 154 | }, 155 | }, 156 | } 157 | 158 | for _, test := range tests { 159 | out := Setup(test.ipt, "eth1", "droplan-peers") 160 | if !reflect.DeepEqual(out, test.exp) { 161 | t.Logf("want:%v", test.exp) 162 | t.Logf("got:%v", out) 163 | t.Fatalf("test case failed: %s", test.name) 164 | } 165 | } 166 | } 167 | 168 | func TestUpdatePeers(t *testing.T) { 169 | count := 0 170 | 171 | tests := []struct { 172 | name string 173 | ipt IPTables 174 | peers []string 175 | exp error 176 | }{ 177 | { 178 | name: "clears the chain", 179 | ipt: &stubIPTables{ 180 | newChain: func(string, string) error { return nil }, 181 | clearChain: func(a, b string) error { 182 | if a == "filter" && b == "droplan-peers" { 183 | return nil 184 | } 185 | return errors.New("bad clear chain args") 186 | }, 187 | appendUnique: func(string, string, ...string) error { return nil }, 188 | append: func(string, string, ...string) error { return nil }, 189 | }, 190 | }, 191 | { 192 | name: "does not append anythign if peers are empty", 193 | ipt: &stubIPTables{ 194 | newChain: func(string, string) error { return nil }, 195 | clearChain: func(string, string) error { return nil }, 196 | appendUnique: func(string, string, ...string) error { return nil }, 197 | append: func(string, string, ...string) error { 198 | return errors.New("no peers should be appended") 199 | }, 200 | }, 201 | }, 202 | { 203 | name: "when clearing the chain errors", 204 | ipt: &stubIPTables{ 205 | newChain: func(string, string) error { return nil }, 206 | clearChain: func(string, string) error { 207 | return errors.New("clear chain error") 208 | }, 209 | appendUnique: func(string, string, ...string) error { return nil }, 210 | append: func(string, string, ...string) error { return nil }, 211 | }, 212 | exp: errors.New("clear chain error"), 213 | }, 214 | { 215 | name: "when appending to the chain errors", 216 | ipt: &stubIPTables{ 217 | newChain: func(string, string) error { return nil }, 218 | clearChain: func(string, string) error { return nil }, 219 | appendUnique: func(string, string, ...string) error { return nil }, 220 | append: func(string, string, ...string) error { 221 | return errors.New("peer append error") 222 | }, 223 | }, 224 | peers: []string{"peer1"}, 225 | exp: errors.New("peer append error"), 226 | }, 227 | { 228 | name: "adds peer chain and drop interface", 229 | ipt: &stubIPTables{ 230 | newChain: func(string, string) error { return nil }, 231 | clearChain: func(string, string) error { return nil }, 232 | appendUnique: func(string, string, ...string) error { return nil }, 233 | append: func(a, b string, c ...string) error { 234 | defer func() { count++ }() 235 | switch count { 236 | case 0: 237 | if a == "filter" && b == "droplan-peers" && reflect.DeepEqual(c, []string{"-s", "peer1", "-j", "ACCEPT"}) { 238 | return nil 239 | } else { 240 | return errors.New("bad case 0") 241 | } 242 | case 1: 243 | if a == "filter" && b == "droplan-peers" && reflect.DeepEqual(c, []string{"-s", "peer2", "-j", "ACCEPT"}) { 244 | return nil 245 | } else { 246 | return errors.New("bad case 1") 247 | } 248 | case 2: 249 | if a == "filter" && b == "droplan-peers" && reflect.DeepEqual(c, []string{"-s", "peer3", "-j", "ACCEPT"}) { 250 | return nil 251 | } else { 252 | return errors.New("bad case 2") 253 | } 254 | default: 255 | return errors.New("bad input") 256 | } 257 | return nil 258 | }, 259 | }, 260 | peers: []string{"peer1", "peer2", "peer3"}, 261 | }, 262 | } 263 | 264 | for _, test := range tests { 265 | out := UpdatePeers(test.ipt, test.peers, "droplan-peers") 266 | if !reflect.DeepEqual(out, test.exp) { 267 | t.Logf("want:%v", test.exp) 268 | t.Logf("got:%v", out) 269 | t.Fatalf("test case failed: %s", test.name) 270 | } 271 | } 272 | } 273 | 274 | func newStubIPTables() *stubIPTables { 275 | return &stubIPTables{ 276 | newChain: func(string, string) error { return nil }, 277 | clearChain: func(string, string) error { return nil }, 278 | appendUnique: func(string, string, ...string) error { return nil }, 279 | append: func(string, string, ...string) error { return nil }, 280 | } 281 | } 282 | 283 | type stubIPTables struct { 284 | newChain func(string, string) error 285 | clearChain func(string, string) error 286 | appendUnique func(string, string, ...string) error 287 | append func(string, string, ...string) error 288 | } 289 | 290 | func (sipt *stubIPTables) ClearChain(a, b string) error { 291 | return sipt.clearChain(a, b) 292 | } 293 | 294 | func (sipt *stubIPTables) Append(a, b string, c ...string) error { 295 | return sipt.append(a, b, c...) 296 | } 297 | 298 | func (sipt *stubIPTables) AppendUnique(a, b string, c ...string) error { 299 | return sipt.appendUnique(a, b, c...) 300 | } 301 | 302 | func (sipt *stubIPTables) NewChain(a, b string) error { 303 | return sipt.newChain(a, b) 304 | } 305 | -------------------------------------------------------------------------------- /vendor/github.com/coreos/go-iptables/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /vendor/github.com/coreos/go-iptables/iptables/iptables.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 CoreOS, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package iptables 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "io" 21 | "os/exec" 22 | "regexp" 23 | "strconv" 24 | "strings" 25 | "syscall" 26 | ) 27 | 28 | // Adds the output of stderr to exec.ExitError 29 | type Error struct { 30 | exec.ExitError 31 | msg string 32 | } 33 | 34 | func (e *Error) ExitStatus() int { 35 | return e.Sys().(syscall.WaitStatus).ExitStatus() 36 | } 37 | 38 | func (e *Error) Error() string { 39 | return fmt.Sprintf("exit status %v: %v", e.ExitStatus(), e.msg) 40 | } 41 | 42 | // Protocol to differentiate between IPv4 and IPv6 43 | type Protocol byte 44 | 45 | const ( 46 | ProtocolIPv4 Protocol = iota 47 | ProtocolIPv6 48 | ) 49 | 50 | type IPTables struct { 51 | path string 52 | proto Protocol 53 | hasCheck bool 54 | hasWait bool 55 | } 56 | 57 | // New creates a new IPTables. 58 | // For backwards compatibility, this always uses IPv4, i.e. "iptables". 59 | func New() (*IPTables, error) { 60 | return NewWithProtocol(ProtocolIPv4) 61 | } 62 | 63 | // New creates a new IPTables for the given proto. 64 | // The proto will determine which command is used, either "iptables" or "ip6tables". 65 | func NewWithProtocol(proto Protocol) (*IPTables, error) { 66 | path, err := exec.LookPath(getIptablesCommand(proto)) 67 | if err != nil { 68 | return nil, err 69 | } 70 | checkPresent, waitPresent, err := getIptablesCommandSupport(path) 71 | if err != nil { 72 | return nil, fmt.Errorf("error checking iptables version: %v", err) 73 | } 74 | ipt := IPTables{ 75 | path: path, 76 | proto: proto, 77 | hasCheck: checkPresent, 78 | hasWait: waitPresent, 79 | } 80 | return &ipt, nil 81 | } 82 | 83 | // Proto returns the protocol used by this IPTables. 84 | func (ipt *IPTables) Proto() Protocol { 85 | return ipt.proto 86 | } 87 | 88 | // Exists checks if given rulespec in specified table/chain exists 89 | func (ipt *IPTables) Exists(table, chain string, rulespec ...string) (bool, error) { 90 | if !ipt.hasCheck { 91 | return ipt.existsForOldIptables(table, chain, rulespec) 92 | 93 | } 94 | cmd := append([]string{"-t", table, "-C", chain}, rulespec...) 95 | err := ipt.run(cmd...) 96 | eerr, eok := err.(*Error) 97 | switch { 98 | case err == nil: 99 | return true, nil 100 | case eok && eerr.ExitStatus() == 1: 101 | return false, nil 102 | default: 103 | return false, err 104 | } 105 | } 106 | 107 | // Insert inserts rulespec to specified table/chain (in specified pos) 108 | func (ipt *IPTables) Insert(table, chain string, pos int, rulespec ...string) error { 109 | cmd := append([]string{"-t", table, "-I", chain, strconv.Itoa(pos)}, rulespec...) 110 | return ipt.run(cmd...) 111 | } 112 | 113 | // Append appends rulespec to specified table/chain 114 | func (ipt *IPTables) Append(table, chain string, rulespec ...string) error { 115 | cmd := append([]string{"-t", table, "-A", chain}, rulespec...) 116 | return ipt.run(cmd...) 117 | } 118 | 119 | // AppendUnique acts like Append except that it won't add a duplicate 120 | func (ipt *IPTables) AppendUnique(table, chain string, rulespec ...string) error { 121 | exists, err := ipt.Exists(table, chain, rulespec...) 122 | if err != nil { 123 | return err 124 | } 125 | 126 | if !exists { 127 | return ipt.Append(table, chain, rulespec...) 128 | } 129 | 130 | return nil 131 | } 132 | 133 | // Delete removes rulespec in specified table/chain 134 | func (ipt *IPTables) Delete(table, chain string, rulespec ...string) error { 135 | cmd := append([]string{"-t", table, "-D", chain}, rulespec...) 136 | return ipt.run(cmd...) 137 | } 138 | 139 | // List rules in specified table/chain 140 | func (ipt *IPTables) List(table, chain string) ([]string, error) { 141 | args := []string{"-t", table, "-S", chain} 142 | return ipt.executeList(args) 143 | } 144 | 145 | // ListChains returns a slice containing the name of each chain in the specified table. 146 | func (ipt *IPTables) ListChains(table string) ([]string, error) { 147 | args := []string{"-t", table, "-S"} 148 | 149 | result, err := ipt.executeList(args) 150 | if err != nil { 151 | return nil, err 152 | } 153 | 154 | // Iterate over rules to find all default (-P) and user-specified (-N) chains. 155 | // Chains definition always come before rules. 156 | // Format is the following: 157 | // -P OUTPUT ACCEPT 158 | // -N Custom 159 | var chains []string 160 | for _, val := range result { 161 | if strings.HasPrefix(val, "-P") || strings.HasPrefix(val, "-N") { 162 | chains = append(chains, strings.Fields(val)[1]) 163 | } else { 164 | break 165 | } 166 | } 167 | return chains, nil 168 | } 169 | 170 | func (ipt *IPTables) executeList(args []string) ([]string, error) { 171 | var stdout bytes.Buffer 172 | if err := ipt.runWithOutput(args, &stdout); err != nil { 173 | return nil, err 174 | } 175 | 176 | rules := strings.Split(stdout.String(), "\n") 177 | if len(rules) > 0 && rules[len(rules)-1] == "" { 178 | rules = rules[:len(rules)-1] 179 | } 180 | 181 | return rules, nil 182 | } 183 | 184 | // NewChain creates a new chain in the specified table. 185 | // If the chain already exists, it will result in an error. 186 | func (ipt *IPTables) NewChain(table, chain string) error { 187 | return ipt.run("-t", table, "-N", chain) 188 | } 189 | 190 | // ClearChain flushed (deletes all rules) in the specified table/chain. 191 | // If the chain does not exist, a new one will be created 192 | func (ipt *IPTables) ClearChain(table, chain string) error { 193 | err := ipt.NewChain(table, chain) 194 | 195 | eerr, eok := err.(*Error) 196 | switch { 197 | case err == nil: 198 | return nil 199 | case eok && eerr.ExitStatus() == 1: 200 | // chain already exists. Flush (clear) it. 201 | return ipt.run("-t", table, "-F", chain) 202 | default: 203 | return err 204 | } 205 | } 206 | 207 | // RenameChain renames the old chain to the new one. 208 | func (ipt *IPTables) RenameChain(table, oldChain, newChain string) error { 209 | return ipt.run("-t", table, "-E", oldChain, newChain) 210 | } 211 | 212 | // DeleteChain deletes the chain in the specified table. 213 | // The chain must be empty 214 | func (ipt *IPTables) DeleteChain(table, chain string) error { 215 | return ipt.run("-t", table, "-X", chain) 216 | } 217 | 218 | // run runs an iptables command with the given arguments, ignoring 219 | // any stdout output 220 | func (ipt *IPTables) run(args ...string) error { 221 | return ipt.runWithOutput(args, nil) 222 | } 223 | 224 | // runWithOutput runs an iptables command with the given arguments, 225 | // writing any stdout output to the given writer 226 | func (ipt *IPTables) runWithOutput(args []string, stdout io.Writer) error { 227 | args = append([]string{ipt.path}, args...) 228 | if ipt.hasWait { 229 | args = append(args, "--wait") 230 | } else { 231 | fmu, err := newXtablesFileLock() 232 | if err != nil { 233 | return err 234 | } 235 | ul, err := fmu.tryLock() 236 | if err != nil { 237 | return err 238 | } 239 | defer ul.Unlock() 240 | } 241 | 242 | var stderr bytes.Buffer 243 | cmd := exec.Cmd{ 244 | Path: ipt.path, 245 | Args: args, 246 | Stdout: stdout, 247 | Stderr: &stderr, 248 | } 249 | 250 | if err := cmd.Run(); err != nil { 251 | return &Error{*(err.(*exec.ExitError)), stderr.String()} 252 | } 253 | 254 | return nil 255 | } 256 | 257 | // getIptablesCommand returns the correct command for the given protocol, either "iptables" or "ip6tables". 258 | func getIptablesCommand(proto Protocol) string { 259 | if proto == ProtocolIPv6 { 260 | return "ip6tables" 261 | } else { 262 | return "iptables" 263 | } 264 | } 265 | 266 | // Checks if iptables has the "-C" and "--wait" flag 267 | func getIptablesCommandSupport(path string) (bool, bool, error) { 268 | vstring, err := getIptablesVersionString(path) 269 | if err != nil { 270 | return false, false, err 271 | } 272 | 273 | v1, v2, v3, err := extractIptablesVersion(vstring) 274 | if err != nil { 275 | return false, false, err 276 | } 277 | 278 | return iptablesHasCheckCommand(v1, v2, v3), iptablesHasWaitCommand(v1, v2, v3), nil 279 | } 280 | 281 | // getIptablesVersion returns the first three components of the iptables version. 282 | // e.g. "iptables v1.3.66" would return (1, 3, 66, nil) 283 | func extractIptablesVersion(str string) (int, int, int, error) { 284 | versionMatcher := regexp.MustCompile("v([0-9]+)\\.([0-9]+)\\.([0-9]+)") 285 | result := versionMatcher.FindStringSubmatch(str) 286 | if result == nil { 287 | return 0, 0, 0, fmt.Errorf("no iptables version found in string: %s", str) 288 | } 289 | 290 | v1, err := strconv.Atoi(result[1]) 291 | if err != nil { 292 | return 0, 0, 0, err 293 | } 294 | 295 | v2, err := strconv.Atoi(result[2]) 296 | if err != nil { 297 | return 0, 0, 0, err 298 | } 299 | 300 | v3, err := strconv.Atoi(result[3]) 301 | if err != nil { 302 | return 0, 0, 0, err 303 | } 304 | 305 | return v1, v2, v3, nil 306 | } 307 | 308 | // Runs "iptables --version" to get the version string 309 | func getIptablesVersionString(path string) (string, error) { 310 | cmd := exec.Command(path, "--version") 311 | var out bytes.Buffer 312 | cmd.Stdout = &out 313 | err := cmd.Run() 314 | if err != nil { 315 | return "", err 316 | } 317 | return out.String(), nil 318 | } 319 | 320 | // Checks if an iptables version is after 1.4.11, when --check was added 321 | func iptablesHasCheckCommand(v1 int, v2 int, v3 int) bool { 322 | if v1 > 1 { 323 | return true 324 | } 325 | if v1 == 1 && v2 > 4 { 326 | return true 327 | } 328 | if v1 == 1 && v2 == 4 && v3 >= 11 { 329 | return true 330 | } 331 | return false 332 | } 333 | 334 | // Checks if an iptables version is after 1.4.20, when --wait was added 335 | func iptablesHasWaitCommand(v1 int, v2 int, v3 int) bool { 336 | if v1 > 1 { 337 | return true 338 | } 339 | if v1 == 1 && v2 > 4 { 340 | return true 341 | } 342 | if v1 == 1 && v2 == 4 && v3 >= 20 { 343 | return true 344 | } 345 | return false 346 | } 347 | 348 | // Checks if a rule specification exists for a table 349 | func (ipt *IPTables) existsForOldIptables(table, chain string, rulespec []string) (bool, error) { 350 | rs := strings.Join(append([]string{"-A", chain}, rulespec...), " ") 351 | args := []string{"-t", table, "-S"} 352 | var stdout bytes.Buffer 353 | err := ipt.runWithOutput(args, &stdout) 354 | if err != nil { 355 | return false, err 356 | } 357 | return strings.Contains(stdout.String(), rs), nil 358 | } 359 | -------------------------------------------------------------------------------- /vendor/github.com/coreos/go-iptables/iptables/lock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 CoreOS, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package iptables 16 | 17 | import ( 18 | "os" 19 | "sync" 20 | "syscall" 21 | ) 22 | 23 | const ( 24 | // In earlier versions of iptables, the xtables lock was implemented 25 | // via a Unix socket, but now flock is used via this lockfile: 26 | // http://git.netfilter.org/iptables/commit/?id=aa562a660d1555b13cffbac1e744033e91f82707 27 | // Note the LSB-conforming "/run" directory does not exist on old 28 | // distributions, so assume "/var" is symlinked 29 | xtablesLockFilePath = "/var/run/xtables.lock" 30 | 31 | defaultFilePerm = 0600 32 | ) 33 | 34 | type Unlocker interface { 35 | Unlock() error 36 | } 37 | 38 | type nopUnlocker struct{} 39 | 40 | func (_ nopUnlocker) Unlock() error { return nil } 41 | 42 | type fileLock struct { 43 | // mu is used to protect against concurrent invocations from within this process 44 | mu sync.Mutex 45 | fd int 46 | } 47 | 48 | // tryLock takes an exclusive lock on the xtables lock file without blocking. 49 | // This is best-effort only: if the exclusive lock would block (i.e. because 50 | // another process already holds it), no error is returned. Otherwise, any 51 | // error encountered during the locking operation is returned. 52 | // The returned Unlocker should be used to release the lock when the caller is 53 | // done invoking iptables commands. 54 | func (l *fileLock) tryLock() (Unlocker, error) { 55 | l.mu.Lock() 56 | err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB) 57 | switch err { 58 | case syscall.EWOULDBLOCK: 59 | l.mu.Unlock() 60 | return nopUnlocker{}, nil 61 | case nil: 62 | return l, nil 63 | default: 64 | l.mu.Unlock() 65 | return nil, err 66 | } 67 | } 68 | 69 | // Unlock closes the underlying file, which implicitly unlocks it as well. It 70 | // also unlocks the associated mutex. 71 | func (l *fileLock) Unlock() error { 72 | defer l.mu.Unlock() 73 | return syscall.Close(l.fd) 74 | } 75 | 76 | // newXtablesFileLock opens a new lock on the xtables lockfile without 77 | // acquiring the lock 78 | func newXtablesFileLock() (*fileLock, error) { 79 | fd, err := syscall.Open(xtablesLockFilePath, os.O_CREATE, defaultFilePerm) 80 | if err != nil { 81 | return nil, err 82 | } 83 | return &fileLock{fd: fd}, nil 84 | } 85 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/go-metadata/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 DigitalOcean 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/go-metadata/README.md: -------------------------------------------------------------------------------- 1 | # metadata 2 | 3 | A Go client to interact with the DigitalOcean Metadata API. 4 | 5 | # Usage 6 | 7 | ```go 8 | // Create a client 9 | client := metadata.NewClient(opts) 10 | 11 | // Request all the metadata about the current droplet 12 | all, err := client.Metadata() 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | // Lookup what our IPv4 address is on our first public 18 | // network interface. 19 | publicIPv4Addr := all.Interfaces["public"][0].IPv4.IPAddress 20 | 21 | fmt.Println(publicIPv4Addr) 22 | ``` 23 | 24 | # License 25 | 26 | MIT license 27 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/go-metadata/all_json.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | type Metadata struct { 4 | DropletID int `json:"droplet_id,omitempty"` 5 | Hostname string `json:"hostname,omitempty"` 6 | UserData string `json:"user_data,omitempty"` 7 | VendorData string `json:"vendor_data,omitempty"` 8 | PublicKeys []string `json:"public_keys,omitempty"` 9 | Region string `json:"region,omitempty"` 10 | 11 | DNS struct { 12 | Nameservers []string `json:"nameservers,omitempty"` 13 | } `json:"dns,omitempty"` 14 | 15 | Interfaces map[string][]struct { 16 | MACAddress string `json:"mac,omitempty"` 17 | Type string `json:"type,omitempty"` 18 | 19 | IPv4 *struct { 20 | IPAddress string `json:"ip_address,omitempty"` 21 | Netmask string `json:"netmask,omitempty"` 22 | Gateway string `json:"gateway,omitempty"` 23 | } `json:"ipv4,omitempty"` 24 | 25 | IPv6 *struct { 26 | IPAddress string `json:"ip_address,omitempty"` 27 | CIDR int `json:"cidr,omitempty"` 28 | Gateway string `json:"gateway,omitempty"` 29 | } `json:"ipv6,omitempty"` 30 | 31 | AnchorIPv4 *struct { 32 | IPAddress string `json:"ip_address,omitempty"` 33 | Netmask string `json:"netmask,omitempty"` 34 | Gateway string `json:"gateway,omitempty"` 35 | } `json:"anchor_ipv4,omitempty"` 36 | } `json:"interfaces,omitempty"` 37 | 38 | FloatingIP struct { 39 | IPv4 struct { 40 | IPAddress string `json:"ip_address,omitempty"` 41 | Active bool `json:"active,omitempty"` 42 | } `json:"ipv4,omitempty"` 43 | } `json:"floating_ip",omitempty"` 44 | } 45 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/go-metadata/client.go: -------------------------------------------------------------------------------- 1 | // Package metadata implements a client for the DigitalOcean metadata 2 | // API. This API allows a droplet to inspect information about itself, 3 | // like it's region, droplet ID, and so on. 4 | // 5 | // Documentation for the API is available at: 6 | // 7 | // https://developers.digitalocean.com/documentation/metadata/ 8 | package metadata 9 | 10 | import ( 11 | "bufio" 12 | "encoding/json" 13 | "fmt" 14 | "io" 15 | "io/ioutil" 16 | "net/http" 17 | "net/url" 18 | "path" 19 | "time" 20 | ) 21 | 22 | const ( 23 | maxErrMsgLen = 128 // arbitrary max length for error messages 24 | 25 | defaultTimeout = 2 * time.Second 26 | defaultPath = "/metadata/v1/" 27 | ) 28 | 29 | var ( 30 | defaultBaseURL = func() *url.URL { 31 | u, err := url.Parse("http://169.254.169.254") 32 | if err != nil { 33 | panic(err) 34 | } 35 | return u 36 | }() 37 | ) 38 | 39 | // ClientOption modifies the default behavior of a metadata client. This 40 | // is usually not needed. 41 | type ClientOption func(*Client) 42 | 43 | // WithHTTPClient makes the metadata client use the given HTTP client. 44 | func WithHTTPClient(client *http.Client) ClientOption { 45 | return func(metaclient *Client) { metaclient.client = client } 46 | } 47 | 48 | // WithBaseURL makes the metadata client reach the metadata API using the 49 | // given base URL. 50 | func WithBaseURL(base *url.URL) ClientOption { 51 | return func(metaclient *Client) { metaclient.baseURL = base } 52 | } 53 | 54 | // Client to interact with the DigitalOcean metadata API, from inside 55 | // a droplet. 56 | type Client struct { 57 | client *http.Client 58 | baseURL *url.URL 59 | } 60 | 61 | // NewClient creates a client for the metadata API. 62 | func NewClient(opts ...ClientOption) *Client { 63 | client := &Client{ 64 | client: &http.Client{Timeout: defaultTimeout}, 65 | baseURL: defaultBaseURL, 66 | } 67 | for _, opt := range opts { 68 | opt(client) 69 | } 70 | return client 71 | } 72 | 73 | // Metadata contains the entire contents of a Droplet's metadata. 74 | // This method is unique because it returns all of the 75 | // metadata at once, instead of individual metadata items. 76 | func (c *Client) Metadata() (*Metadata, error) { 77 | metadata := new(Metadata) 78 | err := c.doGetURL(c.resolve("/metadata/v1.json"), func(r io.Reader) error { 79 | return json.NewDecoder(r).Decode(metadata) 80 | }) 81 | return metadata, err 82 | } 83 | 84 | // DropletID returns the Droplet's unique identifier. This is 85 | // automatically generated upon Droplet creation. 86 | func (c *Client) DropletID() (int, error) { 87 | dropletID := new(int) 88 | err := c.doGet("id", func(r io.Reader) error { 89 | _, err := fmt.Fscanf(r, "%d", dropletID) 90 | return err 91 | }) 92 | return *dropletID, err 93 | } 94 | 95 | // Hostname returns the Droplet's hostname, as specified by the 96 | // user during Droplet creation. 97 | func (c *Client) Hostname() (string, error) { 98 | var hostname string 99 | err := c.doGet("hostname", func(r io.Reader) error { 100 | hostnameraw, err := ioutil.ReadAll(r) 101 | hostname = string(hostnameraw) 102 | return err 103 | }) 104 | return hostname, err 105 | } 106 | 107 | // UserData returns the user data that was provided by the user 108 | // during Droplet creation. User data can contain arbitrary data 109 | // for miscellaneous use or, with certain Linux distributions, 110 | // an arbitrary shell script or cloud-config file that will be 111 | // consumed by a variation of cloud-init upon boot. At this time, 112 | // cloud-config support is included with CoreOS, Ubuntu 14.04, and 113 | // CentOS 7 images on DigitalOcean. 114 | func (c *Client) UserData() (string, error) { 115 | var userdata string 116 | err := c.doGet("user-data", func(r io.Reader) error { 117 | userdataraw, err := ioutil.ReadAll(r) 118 | userdata = string(userdataraw) 119 | return err 120 | }) 121 | return userdata, err 122 | } 123 | 124 | // VendorData provided data that can be used to configure Droplets 125 | // upon their creation. This is similar to user data, but it is 126 | // provided by DigitalOcean instead of the user. 127 | func (c *Client) VendorData() (string, error) { 128 | var vendordata string 129 | err := c.doGet("vendor-data", func(r io.Reader) error { 130 | vendordataraw, err := ioutil.ReadAll(r) 131 | vendordata = string(vendordataraw) 132 | return err 133 | }) 134 | return vendordata, err 135 | } 136 | 137 | // Region returns the region code of where the Droplet resides. 138 | func (c *Client) Region() (string, error) { 139 | var region string 140 | err := c.doGet("region", func(r io.Reader) error { 141 | regionraw, err := ioutil.ReadAll(r) 142 | region = string(regionraw) 143 | return err 144 | }) 145 | return region, err 146 | } 147 | 148 | // AuthToken returns the authentication token. 149 | func (c *Client) AuthToken() (string, error) { 150 | var authToken string 151 | err := c.doGet("auth-token", func(r io.Reader) error { 152 | authTokenraw, err := ioutil.ReadAll(r) 153 | authToken = string(authTokenraw) 154 | return err 155 | }) 156 | return authToken, err 157 | } 158 | 159 | // PublicKeys returns the public SSH key(s) that were added to 160 | // the Droplet's root user's authorized_keys file during Droplet 161 | // creation. 162 | func (c *Client) PublicKeys() ([]string, error) { 163 | var keys []string 164 | err := c.doGet("public-keys", func(r io.Reader) error { 165 | scan := bufio.NewScanner(r) 166 | for scan.Scan() { 167 | keys = append(keys, scan.Text()) 168 | } 169 | return scan.Err() 170 | }) 171 | return keys, err 172 | } 173 | 174 | // Nameservers returns the nameserver entries that are added 175 | // to a Droplet's /etc/resolv.conf file during creation. 176 | func (c *Client) Nameservers() ([]string, error) { 177 | var ns []string 178 | err := c.doGet("dns/nameservers", func(r io.Reader) error { 179 | scan := bufio.NewScanner(r) 180 | for scan.Scan() { 181 | ns = append(ns, scan.Text()) 182 | } 183 | return scan.Err() 184 | }) 185 | return ns, err 186 | } 187 | 188 | // FloatingIPv4Active returns true if an IPv4 Floating IP 189 | // Address is assigned to the Droplet. 190 | func (c *Client) FloatingIPv4Active() (bool, error) { 191 | var active bool 192 | err := c.doGet("floating_ip/ipv4/active", func(r io.Reader) error { 193 | activeraw, err := ioutil.ReadAll(r) 194 | if string(activeraw) == "true" { 195 | active = true 196 | } 197 | return err 198 | }) 199 | return active, err 200 | } 201 | 202 | func (c *Client) doGet(resource string, decoder func(r io.Reader) error) error { 203 | return c.doGetURL(c.resolve(defaultPath, resource), decoder) 204 | } 205 | 206 | func (c *Client) doGetURL(url string, decoder func(r io.Reader) error) error { 207 | resp, err := c.client.Get(url) 208 | if err != nil { 209 | return err 210 | } 211 | defer resp.Body.Close() 212 | if resp.StatusCode != http.StatusOK { 213 | return c.makeError(resp) 214 | } 215 | return decoder(resp.Body) 216 | } 217 | 218 | func (c *Client) makeError(resp *http.Response) error { 219 | body, _ := ioutil.ReadAll(io.LimitReader(resp.Body, maxErrMsgLen)) 220 | if len(body) >= maxErrMsgLen { 221 | body = append(body[:maxErrMsgLen], []byte("... (elided)")...) 222 | } else if len(body) == 0 { 223 | body = []byte(resp.Status) 224 | } 225 | return fmt.Errorf("unexpected response from metadata API, status %d: %s", 226 | resp.StatusCode, string(body)) 227 | } 228 | 229 | func (c *Client) resolve(basePath string, resource ...string) string { 230 | dupe := *c.baseURL 231 | dupe.Path = path.Join(append([]string{basePath}, resource...)...) 232 | return dupe.String() 233 | } 234 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you submit a pull request, please keep the following guidelines in mind: 4 | 5 | 1. Code should be `go fmt` compliant. 6 | 2. Types, structs and funcs should be documented. 7 | 3. Tests pass. 8 | 9 | ## Getting set up 10 | 11 | Assuming your `$GOPATH` is set up according to your desires, run: 12 | 13 | ```sh 14 | go get github.com/digitalocean/godo 15 | ``` 16 | 17 | ## Running tests 18 | 19 | When working on code in this repository, tests can be run via: 20 | 21 | ```sh 22 | go test . 23 | ``` 24 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2016 The godo AUTHORS. All rights reserved. 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | ====================== 25 | Portions of the client are based on code at: 26 | https://github.com/google/go-github/ 27 | 28 | Copyright (c) 2013 The go-github AUTHORS. All rights reserved. 29 | 30 | Redistribution and use in source and binary forms, with or without 31 | modification, are permitted provided that the following conditions are 32 | met: 33 | 34 | * Redistributions of source code must retain the above copyright 35 | notice, this list of conditions and the following disclaimer. 36 | * Redistributions in binary form must reproduce the above 37 | copyright notice, this list of conditions and the following disclaimer 38 | in the documentation and/or other materials provided with the 39 | distribution. 40 | * Neither the name of Google Inc. nor the names of its 41 | contributors may be used to endorse or promote products derived from 42 | this software without specific prior written permission. 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 45 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 46 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 47 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 48 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 49 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 50 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 51 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 52 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 53 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 54 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 55 | 56 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/digitalocean/godo.svg)](https://travis-ci.org/digitalocean/godo) 2 | 3 | # Godo 4 | 5 | Godo is a Go client library for accessing the DigitalOcean V2 API. 6 | 7 | You can view the client API docs here: [http://godoc.org/github.com/digitalocean/godo](http://godoc.org/github.com/digitalocean/godo) 8 | 9 | You can view DigitalOcean API docs here: [https://developers.digitalocean.com/documentation/v2/](https://developers.digitalocean.com/documentation/v2/) 10 | 11 | 12 | ## Usage 13 | 14 | ```go 15 | import "github.com/digitalocean/godo" 16 | ``` 17 | 18 | Create a new DigitalOcean client, then use the exposed services to 19 | access different parts of the DigitalOcean API. 20 | 21 | ### Authentication 22 | 23 | Currently, Personal Access Token (PAT) is the only method of 24 | authenticating with the API. You can manage your tokens 25 | at the DigitalOcean Control Panel [Applications Page](https://cloud.digitalocean.com/settings/applications). 26 | 27 | You can then use your token to create a new client: 28 | 29 | ```go 30 | import "golang.org/x/oauth2" 31 | 32 | pat := "mytoken" 33 | type TokenSource struct { 34 | AccessToken string 35 | } 36 | 37 | func (t *TokenSource) Token() (*oauth2.Token, error) { 38 | token := &oauth2.Token{ 39 | AccessToken: t.AccessToken, 40 | } 41 | return token, nil 42 | } 43 | 44 | tokenSource := &TokenSource{ 45 | AccessToken: pat, 46 | } 47 | oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource) 48 | client := godo.NewClient(oauthClient) 49 | ``` 50 | 51 | ## Examples 52 | 53 | 54 | To create a new Droplet: 55 | 56 | ```go 57 | dropletName := "super-cool-droplet" 58 | 59 | createRequest := &godo.DropletCreateRequest{ 60 | Name: dropletName, 61 | Region: "nyc3", 62 | Size: "512mb", 63 | Image: godo.DropletCreateImage{ 64 | Slug: "ubuntu-14-04-x64", 65 | }, 66 | } 67 | 68 | newDroplet, _, err := client.Droplets.Create(createRequest) 69 | 70 | if err != nil { 71 | fmt.Printf("Something bad happened: %s\n\n", err) 72 | return err 73 | } 74 | ``` 75 | 76 | ### Pagination 77 | 78 | If a list of items is paginated by the API, you must request pages individually. For example, to fetch all Droplets: 79 | 80 | ```go 81 | func DropletList(client *godo.Client) ([]godo.Droplet, error) { 82 | // create a list to hold our droplets 83 | list := []godo.Droplet{} 84 | 85 | // create options. initially, these will be blank 86 | opt := &godo.ListOptions{} 87 | for { 88 | droplets, resp, err := client.Droplets.List(opt) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | // append the current page's droplets to our list 94 | for _, d := range droplets { 95 | list = append(list, d) 96 | } 97 | 98 | // if we are at the last page, break out the for loop 99 | if resp.Links == nil || resp.Links.IsLastPage() { 100 | break 101 | } 102 | 103 | page, err := resp.Links.CurrentPage() 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | // set the page we want for the next request 109 | opt.Page = page + 1 110 | } 111 | 112 | return list, nil 113 | } 114 | ``` 115 | 116 | ## Versioning 117 | 118 | Each version of the client is tagged and the version is updated accordingly. 119 | 120 | Since Go does not have a built-in versioning, a package management tool is 121 | recommended - a good one that works with git tags is 122 | [gopkg.in](http://labix.org/gopkg.in). 123 | 124 | To see the list of past versions, run `git tag`. 125 | 126 | 127 | ## Documentation 128 | 129 | For a comprehensive list of examples, check out the [API documentation](https://developers.digitalocean.com/documentation/v2/). 130 | 131 | For details on all the functionality in this library, see the [GoDoc](http://godoc.org/github.com/digitalocean/godo) documentation. 132 | 133 | 134 | ## Contributing 135 | 136 | We love pull requests! Please see the [contribution guidelines](CONTRIBUTING.md). 137 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/account.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | // AccountService is an interface for interfacing with the Account 4 | // endpoints of the DigitalOcean API 5 | // See: https://developers.digitalocean.com/documentation/v2/#account 6 | type AccountService interface { 7 | Get() (*Account, *Response, error) 8 | } 9 | 10 | // AccountServiceOp handles communication with the Account related methods of 11 | // the DigitalOcean API. 12 | type AccountServiceOp struct { 13 | client *Client 14 | } 15 | 16 | var _ AccountService = &AccountServiceOp{} 17 | 18 | // Account represents a DigitalOcean Account 19 | type Account struct { 20 | DropletLimit int `json:"droplet_limit,omitempty"` 21 | FloatingIPLimit int `json:"floating_ip_limit,omitempty"` 22 | Email string `json:"email,omitempty"` 23 | UUID string `json:"uuid,omitempty"` 24 | EmailVerified bool `json:"email_verified,omitempty"` 25 | Status string `json:"status,omitempty"` 26 | StatusMessage string `json:"status_message,omitempty"` 27 | } 28 | 29 | type accountRoot struct { 30 | Account *Account `json:"account"` 31 | } 32 | 33 | func (r Account) String() string { 34 | return Stringify(r) 35 | } 36 | 37 | // Get DigitalOcean account info 38 | func (s *AccountServiceOp) Get() (*Account, *Response, error) { 39 | path := "v2/account" 40 | 41 | req, err := s.client.NewRequest("GET", path, nil) 42 | if err != nil { 43 | return nil, nil, err 44 | } 45 | 46 | root := new(accountRoot) 47 | resp, err := s.client.Do(req, root) 48 | if err != nil { 49 | return nil, resp, err 50 | } 51 | 52 | return root.Account, resp, err 53 | } 54 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/action.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import "fmt" 4 | 5 | const ( 6 | actionsBasePath = "v2/actions" 7 | 8 | // ActionInProgress is an in progress action status 9 | ActionInProgress = "in-progress" 10 | 11 | //ActionCompleted is a completed action status 12 | ActionCompleted = "completed" 13 | ) 14 | 15 | // ActionsService handles communction with action related methods of the 16 | // DigitalOcean API: https://developers.digitalocean.com/documentation/v2#actions 17 | type ActionsService interface { 18 | List(*ListOptions) ([]Action, *Response, error) 19 | Get(int) (*Action, *Response, error) 20 | } 21 | 22 | // ActionsServiceOp handles communition with the image action related methods of the 23 | // DigitalOcean API. 24 | type ActionsServiceOp struct { 25 | client *Client 26 | } 27 | 28 | var _ ActionsService = &ActionsServiceOp{} 29 | 30 | type actionsRoot struct { 31 | Actions []Action `json:"actions"` 32 | Links *Links `json:"links"` 33 | } 34 | 35 | type actionRoot struct { 36 | Event Action `json:"action"` 37 | } 38 | 39 | // Action represents a DigitalOcean Action 40 | type Action struct { 41 | ID int `json:"id"` 42 | Status string `json:"status"` 43 | Type string `json:"type"` 44 | StartedAt *Timestamp `json:"started_at"` 45 | CompletedAt *Timestamp `json:"completed_at"` 46 | ResourceID int `json:"resource_id"` 47 | ResourceType string `json:"resource_type"` 48 | Region *Region `json:"region,omitempty"` 49 | RegionSlug string `json:"region_slug,omitempty"` 50 | } 51 | 52 | // List all actions 53 | func (s *ActionsServiceOp) List(opt *ListOptions) ([]Action, *Response, error) { 54 | path := actionsBasePath 55 | path, err := addOptions(path, opt) 56 | if err != nil { 57 | return nil, nil, err 58 | } 59 | 60 | req, err := s.client.NewRequest("GET", path, nil) 61 | if err != nil { 62 | return nil, nil, err 63 | } 64 | 65 | root := new(actionsRoot) 66 | resp, err := s.client.Do(req, root) 67 | if err != nil { 68 | return nil, resp, err 69 | } 70 | if l := root.Links; l != nil { 71 | resp.Links = l 72 | } 73 | 74 | return root.Actions, resp, err 75 | } 76 | 77 | // Get an action by ID. 78 | func (s *ActionsServiceOp) Get(id int) (*Action, *Response, error) { 79 | if id < 1 { 80 | return nil, nil, NewArgError("id", "cannot be less than 1") 81 | } 82 | 83 | path := fmt.Sprintf("%s/%d", actionsBasePath, id) 84 | req, err := s.client.NewRequest("GET", path, nil) 85 | if err != nil { 86 | return nil, nil, err 87 | } 88 | 89 | root := new(actionRoot) 90 | resp, err := s.client.Do(req, root) 91 | if err != nil { 92 | return nil, resp, err 93 | } 94 | 95 | return &root.Event, resp, err 96 | } 97 | 98 | func (a Action) String() string { 99 | return Stringify(a) 100 | } 101 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/doc.go: -------------------------------------------------------------------------------- 1 | // Package godo is the DigtalOcean API v2 client for Go 2 | package godo 3 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/domains.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import "fmt" 4 | 5 | const domainsBasePath = "v2/domains" 6 | 7 | // DomainsService is an interface for managing DNS with the DigitalOcean API. 8 | // See: https://developers.digitalocean.com/documentation/v2#domains and 9 | // https://developers.digitalocean.com/documentation/v2#domain-records 10 | type DomainsService interface { 11 | List(*ListOptions) ([]Domain, *Response, error) 12 | Get(string) (*Domain, *Response, error) 13 | Create(*DomainCreateRequest) (*Domain, *Response, error) 14 | Delete(string) (*Response, error) 15 | 16 | Records(string, *ListOptions) ([]DomainRecord, *Response, error) 17 | Record(string, int) (*DomainRecord, *Response, error) 18 | DeleteRecord(string, int) (*Response, error) 19 | EditRecord(string, int, *DomainRecordEditRequest) (*DomainRecord, *Response, error) 20 | CreateRecord(string, *DomainRecordEditRequest) (*DomainRecord, *Response, error) 21 | } 22 | 23 | // DomainsServiceOp handles communication with the domain related methods of the 24 | // DigitalOcean API. 25 | type DomainsServiceOp struct { 26 | client *Client 27 | } 28 | 29 | var _ DomainsService = &DomainsServiceOp{} 30 | 31 | // Domain represents a DigitalOcean domain 32 | type Domain struct { 33 | Name string `json:"name"` 34 | TTL int `json:"ttl"` 35 | ZoneFile string `json:"zone_file"` 36 | } 37 | 38 | // domainRoot represents a response from the DigitalOcean API 39 | type domainRoot struct { 40 | Domain *Domain `json:"domain"` 41 | } 42 | 43 | type domainsRoot struct { 44 | Domains []Domain `json:"domains"` 45 | Links *Links `json:"links"` 46 | } 47 | 48 | // DomainCreateRequest respresents a request to create a domain. 49 | type DomainCreateRequest struct { 50 | Name string `json:"name"` 51 | IPAddress string `json:"ip_address"` 52 | } 53 | 54 | // DomainRecordRoot is the root of an individual Domain Record response 55 | type domainRecordRoot struct { 56 | DomainRecord *DomainRecord `json:"domain_record"` 57 | } 58 | 59 | // DomainRecordsRoot is the root of a group of Domain Record responses 60 | type domainRecordsRoot struct { 61 | DomainRecords []DomainRecord `json:"domain_records"` 62 | Links *Links `json:"links"` 63 | } 64 | 65 | // DomainRecord represents a DigitalOcean DomainRecord 66 | type DomainRecord struct { 67 | ID int `json:"id,float64,omitempty"` 68 | Type string `json:"type,omitempty"` 69 | Name string `json:"name,omitempty"` 70 | Data string `json:"data,omitempty"` 71 | Priority int `json:"priority,omitempty"` 72 | Port int `json:"port,omitempty"` 73 | Weight int `json:"weight,omitempty"` 74 | } 75 | 76 | // DomainRecordEditRequest represents a request to update a domain record. 77 | type DomainRecordEditRequest struct { 78 | Type string `json:"type,omitempty"` 79 | Name string `json:"name,omitempty"` 80 | Data string `json:"data,omitempty"` 81 | Priority int `json:"priority,omitempty"` 82 | Port int `json:"port,omitempty"` 83 | Weight int `json:"weight,omitempty"` 84 | } 85 | 86 | func (d Domain) String() string { 87 | return Stringify(d) 88 | } 89 | 90 | // List all domains. 91 | func (s DomainsServiceOp) List(opt *ListOptions) ([]Domain, *Response, error) { 92 | path := domainsBasePath 93 | path, err := addOptions(path, opt) 94 | if err != nil { 95 | return nil, nil, err 96 | } 97 | 98 | req, err := s.client.NewRequest("GET", path, nil) 99 | if err != nil { 100 | return nil, nil, err 101 | } 102 | 103 | root := new(domainsRoot) 104 | resp, err := s.client.Do(req, root) 105 | if err != nil { 106 | return nil, resp, err 107 | } 108 | if l := root.Links; l != nil { 109 | resp.Links = l 110 | } 111 | 112 | return root.Domains, resp, err 113 | } 114 | 115 | // Get individual domain. It requires a non-empty domain name. 116 | func (s *DomainsServiceOp) Get(name string) (*Domain, *Response, error) { 117 | if len(name) < 1 { 118 | return nil, nil, NewArgError("name", "cannot be an empty string") 119 | } 120 | 121 | path := fmt.Sprintf("%s/%s", domainsBasePath, name) 122 | 123 | req, err := s.client.NewRequest("GET", path, nil) 124 | if err != nil { 125 | return nil, nil, err 126 | } 127 | 128 | root := new(domainRoot) 129 | resp, err := s.client.Do(req, root) 130 | if err != nil { 131 | return nil, resp, err 132 | } 133 | 134 | return root.Domain, resp, err 135 | } 136 | 137 | // Create a new domain 138 | func (s *DomainsServiceOp) Create(createRequest *DomainCreateRequest) (*Domain, *Response, error) { 139 | if createRequest == nil { 140 | return nil, nil, NewArgError("createRequest", "cannot be nil") 141 | } 142 | 143 | path := domainsBasePath 144 | 145 | req, err := s.client.NewRequest("POST", path, createRequest) 146 | if err != nil { 147 | return nil, nil, err 148 | } 149 | 150 | root := new(domainRoot) 151 | resp, err := s.client.Do(req, root) 152 | if err != nil { 153 | return nil, resp, err 154 | } 155 | return root.Domain, resp, err 156 | } 157 | 158 | // Delete domain 159 | func (s *DomainsServiceOp) Delete(name string) (*Response, error) { 160 | if len(name) < 1 { 161 | return nil, NewArgError("name", "cannot be an empty string") 162 | } 163 | 164 | path := fmt.Sprintf("%s/%s", domainsBasePath, name) 165 | 166 | req, err := s.client.NewRequest("DELETE", path, nil) 167 | if err != nil { 168 | return nil, err 169 | } 170 | 171 | resp, err := s.client.Do(req, nil) 172 | 173 | return resp, err 174 | } 175 | 176 | // Converts a DomainRecord to a string. 177 | func (d DomainRecord) String() string { 178 | return Stringify(d) 179 | } 180 | 181 | // Converts a DomainRecordEditRequest to a string. 182 | func (d DomainRecordEditRequest) String() string { 183 | return Stringify(d) 184 | } 185 | 186 | // Records returns a slice of DomainRecords for a domain 187 | func (s *DomainsServiceOp) Records(domain string, opt *ListOptions) ([]DomainRecord, *Response, error) { 188 | if len(domain) < 1 { 189 | return nil, nil, NewArgError("domain", "cannot be an empty string") 190 | } 191 | 192 | path := fmt.Sprintf("%s/%s/records", domainsBasePath, domain) 193 | path, err := addOptions(path, opt) 194 | if err != nil { 195 | return nil, nil, err 196 | } 197 | 198 | req, err := s.client.NewRequest("GET", path, nil) 199 | if err != nil { 200 | return nil, nil, err 201 | } 202 | 203 | root := new(domainRecordsRoot) 204 | resp, err := s.client.Do(req, root) 205 | if err != nil { 206 | return nil, resp, err 207 | } 208 | if l := root.Links; l != nil { 209 | resp.Links = l 210 | } 211 | 212 | return root.DomainRecords, resp, err 213 | } 214 | 215 | // Record returns the record id from a domain 216 | func (s *DomainsServiceOp) Record(domain string, id int) (*DomainRecord, *Response, error) { 217 | if len(domain) < 1 { 218 | return nil, nil, NewArgError("domain", "cannot be an empty string") 219 | } 220 | 221 | if id < 1 { 222 | return nil, nil, NewArgError("id", "cannot be less than 1") 223 | } 224 | 225 | path := fmt.Sprintf("%s/%s/records/%d", domainsBasePath, domain, id) 226 | 227 | req, err := s.client.NewRequest("GET", path, nil) 228 | if err != nil { 229 | return nil, nil, err 230 | } 231 | 232 | record := new(domainRecordRoot) 233 | resp, err := s.client.Do(req, record) 234 | if err != nil { 235 | return nil, resp, err 236 | } 237 | 238 | return record.DomainRecord, resp, err 239 | } 240 | 241 | // DeleteRecord deletes a record from a domain identified by id 242 | func (s *DomainsServiceOp) DeleteRecord(domain string, id int) (*Response, error) { 243 | if len(domain) < 1 { 244 | return nil, NewArgError("domain", "cannot be an empty string") 245 | } 246 | 247 | if id < 1 { 248 | return nil, NewArgError("id", "cannot be less than 1") 249 | } 250 | 251 | path := fmt.Sprintf("%s/%s/records/%d", domainsBasePath, domain, id) 252 | 253 | req, err := s.client.NewRequest("DELETE", path, nil) 254 | if err != nil { 255 | return nil, err 256 | } 257 | 258 | resp, err := s.client.Do(req, nil) 259 | 260 | return resp, err 261 | } 262 | 263 | // EditRecord edits a record using a DomainRecordEditRequest 264 | func (s *DomainsServiceOp) EditRecord( 265 | domain string, 266 | id int, 267 | editRequest *DomainRecordEditRequest, 268 | ) (*DomainRecord, *Response, error) { 269 | if len(domain) < 1 { 270 | return nil, nil, NewArgError("domain", "cannot be an empty string") 271 | } 272 | 273 | if id < 1 { 274 | return nil, nil, NewArgError("id", "cannot be less than 1") 275 | } 276 | 277 | if editRequest == nil { 278 | return nil, nil, NewArgError("editRequest", "cannot be nil") 279 | } 280 | 281 | path := fmt.Sprintf("%s/%s/records/%d", domainsBasePath, domain, id) 282 | 283 | req, err := s.client.NewRequest("PUT", path, editRequest) 284 | if err != nil { 285 | return nil, nil, err 286 | } 287 | 288 | d := new(DomainRecord) 289 | resp, err := s.client.Do(req, d) 290 | if err != nil { 291 | return nil, resp, err 292 | } 293 | 294 | return d, resp, err 295 | } 296 | 297 | // CreateRecord creates a record using a DomainRecordEditRequest 298 | func (s *DomainsServiceOp) CreateRecord( 299 | domain string, 300 | createRequest *DomainRecordEditRequest) (*DomainRecord, *Response, error) { 301 | if len(domain) < 1 { 302 | return nil, nil, NewArgError("domain", "cannot be empty string") 303 | } 304 | 305 | if createRequest == nil { 306 | return nil, nil, NewArgError("createRequest", "cannot be nil") 307 | } 308 | 309 | path := fmt.Sprintf("%s/%s/records", domainsBasePath, domain) 310 | req, err := s.client.NewRequest("POST", path, createRequest) 311 | 312 | if err != nil { 313 | return nil, nil, err 314 | } 315 | 316 | d := new(domainRecordRoot) 317 | resp, err := s.client.Do(req, d) 318 | if err != nil { 319 | return nil, resp, err 320 | } 321 | 322 | return d.DomainRecord, resp, err 323 | } 324 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/errors.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import "fmt" 4 | 5 | // ArgError is an error that represents an error with an input to godo. It 6 | // identifies the argument and the cause (if possible). 7 | type ArgError struct { 8 | arg string 9 | reason string 10 | } 11 | 12 | var _ error = &ArgError{} 13 | 14 | // NewArgError creates an InputError. 15 | func NewArgError(arg, reason string) *ArgError { 16 | return &ArgError{ 17 | arg: arg, 18 | reason: reason, 19 | } 20 | } 21 | 22 | func (e *ArgError) Error() string { 23 | return fmt.Sprintf("%s is invalid because %s", e.arg, e.reason) 24 | } 25 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/floating_ips.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import "fmt" 4 | 5 | const floatingBasePath = "v2/floating_ips" 6 | 7 | // FloatingIPsService is an interface for interfacing with the floating IPs 8 | // endpoints of the Digital Ocean API. 9 | // See: https://developers.digitalocean.com/documentation/v2#floating-ips 10 | type FloatingIPsService interface { 11 | List(*ListOptions) ([]FloatingIP, *Response, error) 12 | Get(string) (*FloatingIP, *Response, error) 13 | Create(*FloatingIPCreateRequest) (*FloatingIP, *Response, error) 14 | Delete(string) (*Response, error) 15 | } 16 | 17 | // FloatingIPsServiceOp handles communication with the floating IPs related methods of the 18 | // DigitalOcean API. 19 | type FloatingIPsServiceOp struct { 20 | client *Client 21 | } 22 | 23 | var _ FloatingIPsService = &FloatingIPsServiceOp{} 24 | 25 | // FloatingIP represents a Digital Ocean floating IP. 26 | type FloatingIP struct { 27 | Region *Region `json:"region"` 28 | Droplet *Droplet `json:"droplet"` 29 | IP string `json:"ip"` 30 | } 31 | 32 | func (f FloatingIP) String() string { 33 | return Stringify(f) 34 | } 35 | 36 | type floatingIPsRoot struct { 37 | FloatingIPs []FloatingIP `json:"floating_ips"` 38 | Links *Links `json:"links"` 39 | } 40 | 41 | type floatingIPRoot struct { 42 | FloatingIP *FloatingIP `json:"floating_ip"` 43 | Links *Links `json:"links,omitempty"` 44 | } 45 | 46 | // FloatingIPCreateRequest represents a request to create a floating IP. 47 | // If DropletID is not empty, the floating IP will be assigned to the 48 | // droplet. 49 | type FloatingIPCreateRequest struct { 50 | Region string `json:"region"` 51 | DropletID int `json:"droplet_id,omitempty"` 52 | } 53 | 54 | // List all floating IPs. 55 | func (f *FloatingIPsServiceOp) List(opt *ListOptions) ([]FloatingIP, *Response, error) { 56 | path := floatingBasePath 57 | path, err := addOptions(path, opt) 58 | if err != nil { 59 | return nil, nil, err 60 | } 61 | 62 | req, err := f.client.NewRequest("GET", path, nil) 63 | if err != nil { 64 | return nil, nil, err 65 | } 66 | 67 | root := new(floatingIPsRoot) 68 | resp, err := f.client.Do(req, root) 69 | if err != nil { 70 | return nil, resp, err 71 | } 72 | if l := root.Links; l != nil { 73 | resp.Links = l 74 | } 75 | 76 | return root.FloatingIPs, resp, err 77 | } 78 | 79 | // Get an individual floating IP. 80 | func (f *FloatingIPsServiceOp) Get(ip string) (*FloatingIP, *Response, error) { 81 | path := fmt.Sprintf("%s/%s", floatingBasePath, ip) 82 | 83 | req, err := f.client.NewRequest("GET", path, nil) 84 | if err != nil { 85 | return nil, nil, err 86 | } 87 | 88 | root := new(floatingIPRoot) 89 | resp, err := f.client.Do(req, root) 90 | if err != nil { 91 | return nil, resp, err 92 | } 93 | 94 | return root.FloatingIP, resp, err 95 | } 96 | 97 | // Create a floating IP. If the DropletID field of the request is not empty, 98 | // the floating IP will also be assigned to the droplet. 99 | func (f *FloatingIPsServiceOp) Create(createRequest *FloatingIPCreateRequest) (*FloatingIP, *Response, error) { 100 | path := floatingBasePath 101 | 102 | req, err := f.client.NewRequest("POST", path, createRequest) 103 | if err != nil { 104 | return nil, nil, err 105 | } 106 | 107 | root := new(floatingIPRoot) 108 | resp, err := f.client.Do(req, root) 109 | if err != nil { 110 | return nil, resp, err 111 | } 112 | if l := root.Links; l != nil { 113 | resp.Links = l 114 | } 115 | 116 | return root.FloatingIP, resp, err 117 | } 118 | 119 | // Delete a floating IP. 120 | func (f *FloatingIPsServiceOp) Delete(ip string) (*Response, error) { 121 | path := fmt.Sprintf("%s/%s", floatingBasePath, ip) 122 | 123 | req, err := f.client.NewRequest("DELETE", path, nil) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | resp, err := f.client.Do(req, nil) 129 | 130 | return resp, err 131 | } 132 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/floating_ips_actions.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import "fmt" 4 | 5 | // FloatingIPActionsService is an interface for interfacing with the 6 | // floating IPs actions endpoints of the Digital Ocean API. 7 | // See: https://developers.digitalocean.com/documentation/v2#floating-ips-action 8 | type FloatingIPActionsService interface { 9 | Assign(ip string, dropletID int) (*Action, *Response, error) 10 | Unassign(ip string) (*Action, *Response, error) 11 | Get(ip string, actionID int) (*Action, *Response, error) 12 | List(ip string, opt *ListOptions) ([]Action, *Response, error) 13 | } 14 | 15 | // FloatingIPActionsServiceOp handles communication with the floating IPs 16 | // action related methods of the DigitalOcean API. 17 | type FloatingIPActionsServiceOp struct { 18 | client *Client 19 | } 20 | 21 | // Assign a floating IP to a droplet. 22 | func (s *FloatingIPActionsServiceOp) Assign(ip string, dropletID int) (*Action, *Response, error) { 23 | request := &ActionRequest{ 24 | "type": "assign", 25 | "droplet_id": dropletID, 26 | } 27 | return s.doAction(ip, request) 28 | } 29 | 30 | // Unassign a floating IP from the droplet it is currently assigned to. 31 | func (s *FloatingIPActionsServiceOp) Unassign(ip string) (*Action, *Response, error) { 32 | request := &ActionRequest{"type": "unassign"} 33 | return s.doAction(ip, request) 34 | } 35 | 36 | // Get an action for a particular floating IP by id. 37 | func (s *FloatingIPActionsServiceOp) Get(ip string, actionID int) (*Action, *Response, error) { 38 | path := fmt.Sprintf("%s/%d", floatingIPActionPath(ip), actionID) 39 | return s.get(path) 40 | } 41 | 42 | // List the actions for a particular floating IP. 43 | func (s *FloatingIPActionsServiceOp) List(ip string, opt *ListOptions) ([]Action, *Response, error) { 44 | path := floatingIPActionPath(ip) 45 | path, err := addOptions(path, opt) 46 | if err != nil { 47 | return nil, nil, err 48 | } 49 | 50 | return s.list(path) 51 | } 52 | 53 | func (s *FloatingIPActionsServiceOp) doAction(ip string, request *ActionRequest) (*Action, *Response, error) { 54 | path := floatingIPActionPath(ip) 55 | 56 | req, err := s.client.NewRequest("POST", path, request) 57 | if err != nil { 58 | return nil, nil, err 59 | } 60 | 61 | root := new(actionRoot) 62 | resp, err := s.client.Do(req, root) 63 | if err != nil { 64 | return nil, resp, err 65 | } 66 | 67 | return &root.Event, resp, err 68 | } 69 | 70 | func (s *FloatingIPActionsServiceOp) get(path string) (*Action, *Response, error) { 71 | req, err := s.client.NewRequest("GET", path, nil) 72 | if err != nil { 73 | return nil, nil, err 74 | } 75 | 76 | root := new(actionRoot) 77 | resp, err := s.client.Do(req, root) 78 | if err != nil { 79 | return nil, resp, err 80 | } 81 | 82 | return &root.Event, resp, err 83 | } 84 | 85 | func (s *FloatingIPActionsServiceOp) list(path string) ([]Action, *Response, error) { 86 | req, err := s.client.NewRequest("GET", path, nil) 87 | if err != nil { 88 | return nil, nil, err 89 | } 90 | 91 | root := new(actionsRoot) 92 | resp, err := s.client.Do(req, root) 93 | if err != nil { 94 | return nil, resp, err 95 | } 96 | if l := root.Links; l != nil { 97 | resp.Links = l 98 | } 99 | 100 | return root.Actions, resp, err 101 | } 102 | 103 | func floatingIPActionPath(ip string) string { 104 | return fmt.Sprintf("%s/%s/actions", floatingBasePath, ip) 105 | } 106 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/image_actions.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import "fmt" 4 | 5 | // ImageActionsService is an interface for interfacing with the image actions 6 | // endpoints of the DigitalOcean API 7 | // See: https://developers.digitalocean.com/documentation/v2#image-actions 8 | type ImageActionsService interface { 9 | Get(int, int) (*Action, *Response, error) 10 | Transfer(int, *ActionRequest) (*Action, *Response, error) 11 | } 12 | 13 | // ImageActionsServiceOp handles communition with the image action related methods of the 14 | // DigitalOcean API. 15 | type ImageActionsServiceOp struct { 16 | client *Client 17 | } 18 | 19 | var _ ImageActionsService = &ImageActionsServiceOp{} 20 | 21 | // Transfer an image 22 | func (i *ImageActionsServiceOp) Transfer(imageID int, transferRequest *ActionRequest) (*Action, *Response, error) { 23 | if imageID < 1 { 24 | return nil, nil, NewArgError("imageID", "cannot be less than 1") 25 | } 26 | 27 | if transferRequest == nil { 28 | return nil, nil, NewArgError("transferRequest", "cannot be nil") 29 | } 30 | 31 | path := fmt.Sprintf("v2/images/%d/actions", imageID) 32 | 33 | req, err := i.client.NewRequest("POST", path, transferRequest) 34 | if err != nil { 35 | return nil, nil, err 36 | } 37 | 38 | root := new(actionRoot) 39 | resp, err := i.client.Do(req, root) 40 | if err != nil { 41 | return nil, resp, err 42 | } 43 | 44 | return &root.Event, resp, err 45 | } 46 | 47 | // Get an action for a particular image by id. 48 | func (i *ImageActionsServiceOp) Get(imageID, actionID int) (*Action, *Response, error) { 49 | if imageID < 1 { 50 | return nil, nil, NewArgError("imageID", "cannot be less than 1") 51 | } 52 | 53 | if actionID < 1 { 54 | return nil, nil, NewArgError("actionID", "cannot be less than 1") 55 | } 56 | 57 | path := fmt.Sprintf("v2/images/%d/actions/%d", imageID, actionID) 58 | 59 | req, err := i.client.NewRequest("GET", path, nil) 60 | if err != nil { 61 | return nil, nil, err 62 | } 63 | 64 | root := new(actionRoot) 65 | resp, err := i.client.Do(req, root) 66 | if err != nil { 67 | return nil, resp, err 68 | } 69 | 70 | return &root.Event, resp, err 71 | } 72 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/images.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import "fmt" 4 | 5 | const imageBasePath = "v2/images" 6 | 7 | // ImagesService is an interface for interfacing with the images 8 | // endpoints of the DigitalOcean API 9 | // See: https://developers.digitalocean.com/documentation/v2#images 10 | type ImagesService interface { 11 | List(*ListOptions) ([]Image, *Response, error) 12 | ListDistribution(opt *ListOptions) ([]Image, *Response, error) 13 | ListApplication(opt *ListOptions) ([]Image, *Response, error) 14 | ListUser(opt *ListOptions) ([]Image, *Response, error) 15 | GetByID(int) (*Image, *Response, error) 16 | GetBySlug(string) (*Image, *Response, error) 17 | Update(int, *ImageUpdateRequest) (*Image, *Response, error) 18 | Delete(int) (*Response, error) 19 | } 20 | 21 | // ImagesServiceOp handles communication with the image related methods of the 22 | // DigitalOcean API. 23 | type ImagesServiceOp struct { 24 | client *Client 25 | } 26 | 27 | var _ ImagesService = &ImagesServiceOp{} 28 | 29 | // Image represents a DigitalOcean Image 30 | type Image struct { 31 | ID int `json:"id,float64,omitempty"` 32 | Name string `json:"name,omitempty"` 33 | Type string `json:"type,omitempty"` 34 | Distribution string `json:"distribution,omitempty"` 35 | Slug string `json:"slug,omitempty"` 36 | Public bool `json:"public,omitempty"` 37 | Regions []string `json:"regions,omitempty"` 38 | MinDiskSize int `json:"min_disk_size,omitempty"` 39 | Created string `json:"created_at,omitempty"` 40 | } 41 | 42 | // ImageUpdateRequest represents a request to update an image. 43 | type ImageUpdateRequest struct { 44 | Name string `json:"name"` 45 | } 46 | 47 | type imageRoot struct { 48 | Image Image 49 | } 50 | 51 | type imagesRoot struct { 52 | Images []Image 53 | Links *Links `json:"links"` 54 | } 55 | 56 | type listImageOptions struct { 57 | Private bool `url:"private,omitempty"` 58 | Type string `url:"type,omitempty"` 59 | } 60 | 61 | func (i Image) String() string { 62 | return Stringify(i) 63 | } 64 | 65 | // List lists all the images available. 66 | func (s *ImagesServiceOp) List(opt *ListOptions) ([]Image, *Response, error) { 67 | return s.list(opt, nil) 68 | } 69 | 70 | // ListDistribution lists all the distribution images. 71 | func (s *ImagesServiceOp) ListDistribution(opt *ListOptions) ([]Image, *Response, error) { 72 | listOpt := listImageOptions{Type: "distribution"} 73 | return s.list(opt, &listOpt) 74 | } 75 | 76 | // ListApplication lists all the application images. 77 | func (s *ImagesServiceOp) ListApplication(opt *ListOptions) ([]Image, *Response, error) { 78 | listOpt := listImageOptions{Type: "application"} 79 | return s.list(opt, &listOpt) 80 | } 81 | 82 | // ListUser lists all the user images. 83 | func (s *ImagesServiceOp) ListUser(opt *ListOptions) ([]Image, *Response, error) { 84 | listOpt := listImageOptions{Private: true} 85 | return s.list(opt, &listOpt) 86 | } 87 | 88 | // GetByID retrieves an image by id. 89 | func (s *ImagesServiceOp) GetByID(imageID int) (*Image, *Response, error) { 90 | if imageID < 1 { 91 | return nil, nil, NewArgError("imageID", "cannot be less than 1") 92 | } 93 | 94 | return s.get(interface{}(imageID)) 95 | } 96 | 97 | // GetBySlug retrieves an image by slug. 98 | func (s *ImagesServiceOp) GetBySlug(slug string) (*Image, *Response, error) { 99 | if len(slug) < 1 { 100 | return nil, nil, NewArgError("slug", "cannot be blank") 101 | } 102 | 103 | return s.get(interface{}(slug)) 104 | } 105 | 106 | // Update an image name. 107 | func (s *ImagesServiceOp) Update(imageID int, updateRequest *ImageUpdateRequest) (*Image, *Response, error) { 108 | if imageID < 1 { 109 | return nil, nil, NewArgError("imageID", "cannot be less than 1") 110 | } 111 | 112 | if updateRequest == nil { 113 | return nil, nil, NewArgError("updateRequest", "cannot be nil") 114 | } 115 | 116 | path := fmt.Sprintf("%s/%d", imageBasePath, imageID) 117 | req, err := s.client.NewRequest("PUT", path, updateRequest) 118 | if err != nil { 119 | return nil, nil, err 120 | } 121 | 122 | root := new(imageRoot) 123 | resp, err := s.client.Do(req, root) 124 | if err != nil { 125 | return nil, resp, err 126 | } 127 | 128 | return &root.Image, resp, err 129 | } 130 | 131 | // Delete an image. 132 | func (s *ImagesServiceOp) Delete(imageID int) (*Response, error) { 133 | if imageID < 1 { 134 | return nil, NewArgError("imageID", "cannot be less than 1") 135 | } 136 | 137 | path := fmt.Sprintf("%s/%d", imageBasePath, imageID) 138 | 139 | req, err := s.client.NewRequest("DELETE", path, nil) 140 | if err != nil { 141 | return nil, err 142 | } 143 | 144 | resp, err := s.client.Do(req, nil) 145 | 146 | return resp, err 147 | } 148 | 149 | // Helper method for getting an individual image 150 | func (s *ImagesServiceOp) get(ID interface{}) (*Image, *Response, error) { 151 | path := fmt.Sprintf("%s/%v", imageBasePath, ID) 152 | 153 | req, err := s.client.NewRequest("GET", path, nil) 154 | if err != nil { 155 | return nil, nil, err 156 | } 157 | 158 | root := new(imageRoot) 159 | resp, err := s.client.Do(req, root) 160 | if err != nil { 161 | return nil, resp, err 162 | } 163 | 164 | return &root.Image, resp, err 165 | } 166 | 167 | // Helper method for listing images 168 | func (s *ImagesServiceOp) list(opt *ListOptions, listOpt *listImageOptions) ([]Image, *Response, error) { 169 | path := imageBasePath 170 | path, err := addOptions(path, opt) 171 | if err != nil { 172 | return nil, nil, err 173 | } 174 | path, err = addOptions(path, listOpt) 175 | if err != nil { 176 | return nil, nil, err 177 | } 178 | 179 | req, err := s.client.NewRequest("GET", path, nil) 180 | if err != nil { 181 | return nil, nil, err 182 | } 183 | 184 | root := new(imagesRoot) 185 | resp, err := s.client.Do(req, root) 186 | if err != nil { 187 | return nil, resp, err 188 | } 189 | if l := root.Links; l != nil { 190 | resp.Links = l 191 | } 192 | 193 | return root.Images, resp, err 194 | } 195 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/keys.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import "fmt" 4 | 5 | const keysBasePath = "v2/account/keys" 6 | 7 | // KeysService is an interface for interfacing with the keys 8 | // endpoints of the DigitalOcean API 9 | // See: https://developers.digitalocean.com/documentation/v2#keys 10 | type KeysService interface { 11 | List(*ListOptions) ([]Key, *Response, error) 12 | GetByID(int) (*Key, *Response, error) 13 | GetByFingerprint(string) (*Key, *Response, error) 14 | Create(*KeyCreateRequest) (*Key, *Response, error) 15 | UpdateByID(int, *KeyUpdateRequest) (*Key, *Response, error) 16 | UpdateByFingerprint(string, *KeyUpdateRequest) (*Key, *Response, error) 17 | DeleteByID(int) (*Response, error) 18 | DeleteByFingerprint(string) (*Response, error) 19 | } 20 | 21 | // KeysServiceOp handles communication with key related method of the 22 | // DigitalOcean API. 23 | type KeysServiceOp struct { 24 | client *Client 25 | } 26 | 27 | var _ KeysService = &KeysServiceOp{} 28 | 29 | // Key represents a DigitalOcean Key. 30 | type Key struct { 31 | ID int `json:"id,float64,omitempty"` 32 | Name string `json:"name,omitempty"` 33 | Fingerprint string `json:"fingerprint,omitempty"` 34 | PublicKey string `json:"public_key,omitempty"` 35 | } 36 | 37 | // KeyUpdateRequest represents a request to update a DigitalOcean key. 38 | type KeyUpdateRequest struct { 39 | Name string `json:"name"` 40 | } 41 | 42 | type keysRoot struct { 43 | SSHKeys []Key `json:"ssh_keys"` 44 | Links *Links `json:"links"` 45 | } 46 | 47 | type keyRoot struct { 48 | SSHKey Key `json:"ssh_key"` 49 | } 50 | 51 | func (s Key) String() string { 52 | return Stringify(s) 53 | } 54 | 55 | // KeyCreateRequest represents a request to create a new key. 56 | type KeyCreateRequest struct { 57 | Name string `json:"name"` 58 | PublicKey string `json:"public_key"` 59 | } 60 | 61 | // List all keys 62 | func (s *KeysServiceOp) List(opt *ListOptions) ([]Key, *Response, error) { 63 | path := keysBasePath 64 | path, err := addOptions(path, opt) 65 | if err != nil { 66 | return nil, nil, err 67 | } 68 | 69 | req, err := s.client.NewRequest("GET", path, nil) 70 | if err != nil { 71 | return nil, nil, err 72 | } 73 | 74 | root := new(keysRoot) 75 | resp, err := s.client.Do(req, root) 76 | if err != nil { 77 | return nil, resp, err 78 | } 79 | if l := root.Links; l != nil { 80 | resp.Links = l 81 | } 82 | 83 | return root.SSHKeys, resp, err 84 | } 85 | 86 | // Performs a get given a path 87 | func (s *KeysServiceOp) get(path string) (*Key, *Response, error) { 88 | req, err := s.client.NewRequest("GET", path, nil) 89 | if err != nil { 90 | return nil, nil, err 91 | } 92 | 93 | root := new(keyRoot) 94 | resp, err := s.client.Do(req, root) 95 | if err != nil { 96 | return nil, resp, err 97 | } 98 | 99 | return &root.SSHKey, resp, err 100 | } 101 | 102 | // GetByID gets a Key by id 103 | func (s *KeysServiceOp) GetByID(keyID int) (*Key, *Response, error) { 104 | if keyID < 1 { 105 | return nil, nil, NewArgError("keyID", "cannot be less than 1") 106 | } 107 | 108 | path := fmt.Sprintf("%s/%d", keysBasePath, keyID) 109 | return s.get(path) 110 | } 111 | 112 | // GetByFingerprint gets a Key by by fingerprint 113 | func (s *KeysServiceOp) GetByFingerprint(fingerprint string) (*Key, *Response, error) { 114 | if len(fingerprint) < 1 { 115 | return nil, nil, NewArgError("fingerprint", "cannot not be empty") 116 | } 117 | 118 | path := fmt.Sprintf("%s/%s", keysBasePath, fingerprint) 119 | return s.get(path) 120 | } 121 | 122 | // Create a key using a KeyCreateRequest 123 | func (s *KeysServiceOp) Create(createRequest *KeyCreateRequest) (*Key, *Response, error) { 124 | if createRequest == nil { 125 | return nil, nil, NewArgError("createRequest", "cannot be nil") 126 | } 127 | 128 | req, err := s.client.NewRequest("POST", keysBasePath, createRequest) 129 | if err != nil { 130 | return nil, nil, err 131 | } 132 | 133 | root := new(keyRoot) 134 | resp, err := s.client.Do(req, root) 135 | if err != nil { 136 | return nil, resp, err 137 | } 138 | 139 | return &root.SSHKey, resp, err 140 | } 141 | 142 | // UpdateByID updates a key name by ID. 143 | func (s *KeysServiceOp) UpdateByID(keyID int, updateRequest *KeyUpdateRequest) (*Key, *Response, error) { 144 | if keyID < 1 { 145 | return nil, nil, NewArgError("keyID", "cannot be less than 1") 146 | } 147 | 148 | if updateRequest == nil { 149 | return nil, nil, NewArgError("updateRequest", "cannot be nil") 150 | } 151 | 152 | path := fmt.Sprintf("%s/%d", keysBasePath, keyID) 153 | req, err := s.client.NewRequest("PUT", path, updateRequest) 154 | if err != nil { 155 | return nil, nil, err 156 | } 157 | 158 | root := new(keyRoot) 159 | resp, err := s.client.Do(req, root) 160 | if err != nil { 161 | return nil, resp, err 162 | } 163 | 164 | return &root.SSHKey, resp, err 165 | } 166 | 167 | // UpdateByFingerprint updates a key name by fingerprint. 168 | func (s *KeysServiceOp) UpdateByFingerprint(fingerprint string, updateRequest *KeyUpdateRequest) (*Key, *Response, error) { 169 | if len(fingerprint) < 1 { 170 | return nil, nil, NewArgError("fingerprint", "cannot be empty") 171 | } 172 | 173 | if updateRequest == nil { 174 | return nil, nil, NewArgError("updateRequest", "cannot be nil") 175 | } 176 | 177 | path := fmt.Sprintf("%s/%s", keysBasePath, fingerprint) 178 | req, err := s.client.NewRequest("PUT", path, updateRequest) 179 | if err != nil { 180 | return nil, nil, err 181 | } 182 | 183 | root := new(keyRoot) 184 | resp, err := s.client.Do(req, root) 185 | if err != nil { 186 | return nil, resp, err 187 | } 188 | 189 | return &root.SSHKey, resp, err 190 | } 191 | 192 | // Delete key using a path 193 | func (s *KeysServiceOp) delete(path string) (*Response, error) { 194 | req, err := s.client.NewRequest("DELETE", path, nil) 195 | if err != nil { 196 | return nil, err 197 | } 198 | 199 | resp, err := s.client.Do(req, nil) 200 | 201 | return resp, err 202 | } 203 | 204 | // DeleteByID deletes a key by its id 205 | func (s *KeysServiceOp) DeleteByID(keyID int) (*Response, error) { 206 | if keyID < 1 { 207 | return nil, NewArgError("keyID", "cannot be less than 1") 208 | } 209 | 210 | path := fmt.Sprintf("%s/%d", keysBasePath, keyID) 211 | return s.delete(path) 212 | } 213 | 214 | // DeleteByFingerprint deletes a key by its fingerprint 215 | func (s *KeysServiceOp) DeleteByFingerprint(fingerprint string) (*Response, error) { 216 | if len(fingerprint) < 1 { 217 | return nil, NewArgError("fingerprint", "cannot be empty") 218 | } 219 | 220 | path := fmt.Sprintf("%s/%s", keysBasePath, fingerprint) 221 | return s.delete(path) 222 | } 223 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/links.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | ) 7 | 8 | // Links manages links that are returned along with a List 9 | type Links struct { 10 | Pages *Pages `json:"pages,omitempty"` 11 | Actions []LinkAction `json:"actions,omitempty"` 12 | } 13 | 14 | // Pages are pages specified in Links 15 | type Pages struct { 16 | First string `json:"first,omitempty"` 17 | Prev string `json:"prev,omitempty"` 18 | Last string `json:"last,omitempty"` 19 | Next string `json:"next,omitempty"` 20 | } 21 | 22 | // LinkAction is a pointer to an action 23 | type LinkAction struct { 24 | ID int `json:"id,omitempty"` 25 | Rel string `json:"rel,omitempty"` 26 | HREF string `json:"href,omitempty"` 27 | } 28 | 29 | // CurrentPage is current page of the list 30 | func (l *Links) CurrentPage() (int, error) { 31 | return l.Pages.current() 32 | } 33 | 34 | func (p *Pages) current() (int, error) { 35 | switch { 36 | case p == nil: 37 | return 1, nil 38 | case p.Prev == "" && p.Next != "": 39 | return 1, nil 40 | case p.Prev != "": 41 | prevPage, err := pageForURL(p.Prev) 42 | if err != nil { 43 | return 0, err 44 | } 45 | 46 | return prevPage + 1, nil 47 | } 48 | 49 | return 0, nil 50 | } 51 | 52 | // IsLastPage returns true if the current page is the last 53 | func (l *Links) IsLastPage() bool { 54 | if l.Pages == nil { 55 | return true 56 | } 57 | return l.Pages.isLast() 58 | } 59 | 60 | func (p *Pages) isLast() bool { 61 | if p.Last == "" { 62 | return true 63 | } 64 | 65 | return false 66 | } 67 | 68 | func pageForURL(urlText string) (int, error) { 69 | u, err := url.ParseRequestURI(urlText) 70 | if err != nil { 71 | return 0, err 72 | } 73 | 74 | pageStr := u.Query().Get("page") 75 | page, err := strconv.Atoi(pageStr) 76 | if err != nil { 77 | return 0, err 78 | } 79 | 80 | return page, nil 81 | } 82 | 83 | // Get a link action by id. 84 | func (la *LinkAction) Get(client *Client) (*Action, *Response, error) { 85 | return client.Actions.Get(la.ID) 86 | } 87 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/regions.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | // RegionsService is an interface for interfacing with the regions 4 | // endpoints of the DigitalOcean API 5 | // See: https://developers.digitalocean.com/documentation/v2#regions 6 | type RegionsService interface { 7 | List(*ListOptions) ([]Region, *Response, error) 8 | } 9 | 10 | // RegionsServiceOp handles communication with the region related methods of the 11 | // DigitalOcean API. 12 | type RegionsServiceOp struct { 13 | client *Client 14 | } 15 | 16 | var _ RegionsService = &RegionsServiceOp{} 17 | 18 | // Region represents a DigitalOcean Region 19 | type Region struct { 20 | Slug string `json:"slug,omitempty"` 21 | Name string `json:"name,omitempty"` 22 | Sizes []string `json:"sizes,omitempty"` 23 | Available bool `json:"available,omitempty"` 24 | Features []string `json:"features,omitempty"` 25 | } 26 | 27 | type regionsRoot struct { 28 | Regions []Region 29 | Links *Links `json:"links"` 30 | } 31 | 32 | type regionRoot struct { 33 | Region Region 34 | } 35 | 36 | func (r Region) String() string { 37 | return Stringify(r) 38 | } 39 | 40 | // List all regions 41 | func (s *RegionsServiceOp) List(opt *ListOptions) ([]Region, *Response, error) { 42 | path := "v2/regions" 43 | path, err := addOptions(path, opt) 44 | if err != nil { 45 | return nil, nil, err 46 | } 47 | 48 | req, err := s.client.NewRequest("GET", path, nil) 49 | if err != nil { 50 | return nil, nil, err 51 | } 52 | 53 | root := new(regionsRoot) 54 | resp, err := s.client.Do(req, root) 55 | if err != nil { 56 | return nil, resp, err 57 | } 58 | if l := root.Links; l != nil { 59 | resp.Links = l 60 | } 61 | 62 | return root.Regions, resp, err 63 | } 64 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/sizes.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | // SizesService is an interface for interfacing with the size 4 | // endpoints of the DigitalOcean API 5 | // See: https://developers.digitalocean.com/documentation/v2#sizes 6 | type SizesService interface { 7 | List(*ListOptions) ([]Size, *Response, error) 8 | } 9 | 10 | // SizesServiceOp handles communication with the size related methods of the 11 | // DigitalOcean API. 12 | type SizesServiceOp struct { 13 | client *Client 14 | } 15 | 16 | var _ SizesService = &SizesServiceOp{} 17 | 18 | // Size represents a DigitalOcean Size 19 | type Size struct { 20 | Slug string `json:"slug,omitempty"` 21 | Memory int `json:"memory,omitempty"` 22 | Vcpus int `json:"vcpus,omitempty"` 23 | Disk int `json:"disk,omitempty"` 24 | PriceMonthly float64 `json:"price_monthly,omitempty"` 25 | PriceHourly float64 `json:"price_hourly,omitempty"` 26 | Regions []string `json:"regions,omitempty"` 27 | Available bool `json:"available,omitempty"` 28 | Transfer float64 `json:"transfer,omitempty"` 29 | } 30 | 31 | func (s Size) String() string { 32 | return Stringify(s) 33 | } 34 | 35 | type sizesRoot struct { 36 | Sizes []Size 37 | Links *Links `json:"links"` 38 | } 39 | 40 | // List all images 41 | func (s *SizesServiceOp) List(opt *ListOptions) ([]Size, *Response, error) { 42 | path := "v2/sizes" 43 | path, err := addOptions(path, opt) 44 | if err != nil { 45 | return nil, nil, err 46 | } 47 | 48 | req, err := s.client.NewRequest("GET", path, nil) 49 | if err != nil { 50 | return nil, nil, err 51 | } 52 | 53 | root := new(sizesRoot) 54 | resp, err := s.client.Do(req, root) 55 | if err != nil { 56 | return nil, resp, err 57 | } 58 | if l := root.Links; l != nil { 59 | resp.Links = l 60 | } 61 | 62 | return root.Sizes, resp, err 63 | } 64 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/storage.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | const ( 9 | storageBasePath = "v2" 10 | storageAllocPath = storageBasePath + "/volumes" 11 | storageSnapPath = storageBasePath + "/snapshots" 12 | ) 13 | 14 | // StorageService is an interface for interfacing with the storage 15 | // endpoints of the Digital Ocean API. 16 | // See: https://developers.digitalocean.com/documentation/v2#storage 17 | type StorageService interface { 18 | ListVolumes(*ListOptions) ([]Volume, *Response, error) 19 | GetVolume(string) (*Volume, *Response, error) 20 | CreateVolume(*VolumeCreateRequest) (*Volume, *Response, error) 21 | DeleteVolume(string) (*Response, error) 22 | } 23 | 24 | // BetaStorageService is an interface for the storage services that are 25 | // not yet stable. The interface is not exposed in the godo.Client and 26 | // requires type-asserting the `StorageService` to make it available. 27 | // 28 | // Note that Beta features will change and compiling against those 29 | // symbols (using type-assertion) is prone to breaking your build 30 | // if you use our master. 31 | type BetaStorageService interface { 32 | StorageService 33 | 34 | ListSnapshots(volumeID string, opts *ListOptions) ([]Snapshot, *Response, error) 35 | GetSnapshot(string) (*Snapshot, *Response, error) 36 | CreateSnapshot(*SnapshotCreateRequest) (*Snapshot, *Response, error) 37 | DeleteSnapshot(string) (*Response, error) 38 | } 39 | 40 | // StorageServiceOp handles communication with the storage volumes related methods of the 41 | // DigitalOcean API. 42 | type StorageServiceOp struct { 43 | client *Client 44 | } 45 | 46 | var _ StorageService = &StorageServiceOp{} 47 | 48 | // Volume represents a Digital Ocean block store volume. 49 | type Volume struct { 50 | ID string `json:"id"` 51 | Region *Region `json:"region"` 52 | Name string `json:"name"` 53 | SizeGigaBytes int64 `json:"size_gigabytes"` 54 | Description string `json:"description"` 55 | DropletIDs []int `json:"droplet_ids"` 56 | CreatedAt time.Time `json:"created_at"` 57 | } 58 | 59 | func (f Volume) String() string { 60 | return Stringify(f) 61 | } 62 | 63 | type storageVolumesRoot struct { 64 | Volumes []Volume `json:"volumes"` 65 | Links *Links `json:"links"` 66 | } 67 | 68 | type storageVolumeRoot struct { 69 | Volume *Volume `json:"volume"` 70 | Links *Links `json:"links,omitempty"` 71 | } 72 | 73 | // VolumeCreateRequest represents a request to create a block store 74 | // volume. 75 | type VolumeCreateRequest struct { 76 | Region string `json:"region"` 77 | Name string `json:"name"` 78 | Description string `json:"description"` 79 | SizeGigaBytes int64 `json:"size_gigabytes"` 80 | } 81 | 82 | // ListVolumes lists all storage volumes. 83 | func (svc *StorageServiceOp) ListVolumes(opt *ListOptions) ([]Volume, *Response, error) { 84 | path, err := addOptions(storageAllocPath, opt) 85 | if err != nil { 86 | return nil, nil, err 87 | } 88 | 89 | req, err := svc.client.NewRequest("GET", path, nil) 90 | if err != nil { 91 | return nil, nil, err 92 | } 93 | 94 | root := new(storageVolumesRoot) 95 | resp, err := svc.client.Do(req, root) 96 | if err != nil { 97 | return nil, resp, err 98 | } 99 | 100 | if l := root.Links; l != nil { 101 | resp.Links = l 102 | } 103 | 104 | return root.Volumes, resp, nil 105 | } 106 | 107 | // CreateVolume creates a storage volume. The name must be unique. 108 | func (svc *StorageServiceOp) CreateVolume(createRequest *VolumeCreateRequest) (*Volume, *Response, error) { 109 | path := storageAllocPath 110 | 111 | req, err := svc.client.NewRequest("POST", path, createRequest) 112 | if err != nil { 113 | return nil, nil, err 114 | } 115 | 116 | root := new(storageVolumeRoot) 117 | resp, err := svc.client.Do(req, root) 118 | if err != nil { 119 | return nil, resp, err 120 | } 121 | return root.Volume, resp, nil 122 | } 123 | 124 | // GetVolume retrieves an individual storage volume. 125 | func (svc *StorageServiceOp) GetVolume(id string) (*Volume, *Response, error) { 126 | path := fmt.Sprintf("%s/%s", storageAllocPath, id) 127 | 128 | req, err := svc.client.NewRequest("GET", path, nil) 129 | if err != nil { 130 | return nil, nil, err 131 | } 132 | 133 | root := new(storageVolumeRoot) 134 | resp, err := svc.client.Do(req, root) 135 | if err != nil { 136 | return nil, resp, err 137 | } 138 | 139 | return root.Volume, resp, nil 140 | } 141 | 142 | // DeleteVolume deletes a storage volume. 143 | func (svc *StorageServiceOp) DeleteVolume(id string) (*Response, error) { 144 | path := fmt.Sprintf("%s/%s", storageAllocPath, id) 145 | 146 | req, err := svc.client.NewRequest("DELETE", path, nil) 147 | if err != nil { 148 | return nil, err 149 | } 150 | return svc.client.Do(req, nil) 151 | } 152 | 153 | // Snapshot represents a Digital Ocean block store snapshot. 154 | type Snapshot struct { 155 | ID string `json:"id"` 156 | VolumeID string `json:"volume_id"` 157 | Region *Region `json:"region"` 158 | Name string `json:"name"` 159 | SizeGigaBytes int64 `json:"size_gigabytes"` 160 | Description string `json:"description"` 161 | CreatedAt time.Time `json:"created_at"` 162 | } 163 | 164 | type storageSnapsRoot struct { 165 | Snapshots []Snapshot `json:"snapshots"` 166 | Links *Links `json:"links"` 167 | } 168 | 169 | type storageSnapRoot struct { 170 | Snapshot *Snapshot `json:"snapshot"` 171 | Links *Links `json:"links,omitempty"` 172 | } 173 | 174 | // SnapshotCreateRequest represents a request to create a block store 175 | // volume. 176 | type SnapshotCreateRequest struct { 177 | VolumeID string `json:"volume_id"` 178 | Name string `json:"name"` 179 | Description string `json:"description"` 180 | } 181 | 182 | // ListSnapshots lists all snapshots related to a storage volume. 183 | func (svc *StorageServiceOp) ListSnapshots(volumeID string, opt *ListOptions) ([]Snapshot, *Response, error) { 184 | path := fmt.Sprintf("%s/%s/snapshots", storageAllocPath, volumeID) 185 | path, err := addOptions(path, opt) 186 | if err != nil { 187 | return nil, nil, err 188 | } 189 | 190 | req, err := svc.client.NewRequest("GET", path, nil) 191 | if err != nil { 192 | return nil, nil, err 193 | } 194 | 195 | root := new(storageSnapsRoot) 196 | resp, err := svc.client.Do(req, root) 197 | if err != nil { 198 | return nil, resp, err 199 | } 200 | 201 | if l := root.Links; l != nil { 202 | resp.Links = l 203 | } 204 | 205 | return root.Snapshots, resp, nil 206 | } 207 | 208 | // CreateSnapshot creates a snapshot of a storage volume. 209 | func (svc *StorageServiceOp) CreateSnapshot(createRequest *SnapshotCreateRequest) (*Snapshot, *Response, error) { 210 | path := fmt.Sprintf("%s/%s/snapshots", storageAllocPath, createRequest.VolumeID) 211 | 212 | req, err := svc.client.NewRequest("POST", path, createRequest) 213 | if err != nil { 214 | return nil, nil, err 215 | } 216 | 217 | root := new(storageSnapRoot) 218 | resp, err := svc.client.Do(req, root) 219 | if err != nil { 220 | return nil, resp, err 221 | } 222 | return root.Snapshot, resp, nil 223 | } 224 | 225 | // GetSnapshot retrieves an individual snapshot. 226 | func (svc *StorageServiceOp) GetSnapshot(id string) (*Snapshot, *Response, error) { 227 | path := fmt.Sprintf("%s/%s", storageSnapPath, id) 228 | 229 | req, err := svc.client.NewRequest("GET", path, nil) 230 | if err != nil { 231 | return nil, nil, err 232 | } 233 | 234 | root := new(storageSnapRoot) 235 | resp, err := svc.client.Do(req, root) 236 | if err != nil { 237 | return nil, resp, err 238 | } 239 | 240 | return root.Snapshot, resp, nil 241 | } 242 | 243 | // DeleteSnapshot deletes a snapshot. 244 | func (svc *StorageServiceOp) DeleteSnapshot(id string) (*Response, error) { 245 | path := fmt.Sprintf("%s/%s", storageSnapPath, id) 246 | 247 | req, err := svc.client.NewRequest("DELETE", path, nil) 248 | if err != nil { 249 | return nil, err 250 | } 251 | return svc.client.Do(req, nil) 252 | } 253 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/storage_actions.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import "fmt" 4 | 5 | // StorageActionsService is an interface for interfacing with the 6 | // storage actions endpoints of the Digital Ocean API. 7 | // See: https://developers.digitalocean.com/documentation/v2#storage-actions 8 | type StorageActionsService interface { 9 | Attach(volumeID string, dropletID int) (*Action, *Response, error) 10 | Detach(volumeID string) (*Action, *Response, error) 11 | Get(volumeID string, actionID int) (*Action, *Response, error) 12 | List(volumeID string, opt *ListOptions) ([]Action, *Response, error) 13 | Resize(volumeID string, sizeGigabytes int, regionSlug string) (*Action, *Response, error) 14 | } 15 | 16 | // StorageActionsServiceOp handles communication with the storage volumes 17 | // action related methods of the DigitalOcean API. 18 | type StorageActionsServiceOp struct { 19 | client *Client 20 | } 21 | 22 | // StorageAttachment represents the attachement of a block storage 23 | // volume to a specific droplet under the device name. 24 | type StorageAttachment struct { 25 | DropletID int `json:"droplet_id"` 26 | } 27 | 28 | // Attach a storage volume to a droplet. 29 | func (s *StorageActionsServiceOp) Attach(volumeID string, dropletID int) (*Action, *Response, error) { 30 | request := &ActionRequest{ 31 | "type": "attach", 32 | "droplet_id": dropletID, 33 | } 34 | return s.doAction(volumeID, request) 35 | } 36 | 37 | // Detach a storage volume from a droplet. 38 | func (s *StorageActionsServiceOp) Detach(volumeID string) (*Action, *Response, error) { 39 | request := &ActionRequest{ 40 | "type": "detach", 41 | } 42 | return s.doAction(volumeID, request) 43 | } 44 | 45 | // Get an action for a particular storage volume by id. 46 | func (s *StorageActionsServiceOp) Get(volumeID string, actionID int) (*Action, *Response, error) { 47 | path := fmt.Sprintf("%s/%d", storageAllocationActionPath(volumeID), actionID) 48 | return s.get(path) 49 | } 50 | 51 | // List the actions for a particular storage volume. 52 | func (s *StorageActionsServiceOp) List(volumeID string, opt *ListOptions) ([]Action, *Response, error) { 53 | path := storageAllocationActionPath(volumeID) 54 | path, err := addOptions(path, opt) 55 | if err != nil { 56 | return nil, nil, err 57 | } 58 | 59 | return s.list(path) 60 | } 61 | 62 | // Resize a storage volume. 63 | func (s *StorageActionsServiceOp) Resize(volumeID string, sizeGigabytes int, regionSlug string) (*Action, *Response, error) { 64 | request := &ActionRequest{ 65 | "type": "resize", 66 | "size_gigabytes": sizeGigabytes, 67 | "region": regionSlug, 68 | } 69 | return s.doAction(volumeID, request) 70 | } 71 | 72 | func (s *StorageActionsServiceOp) doAction(volumeID string, request *ActionRequest) (*Action, *Response, error) { 73 | path := storageAllocationActionPath(volumeID) 74 | 75 | req, err := s.client.NewRequest("POST", path, request) 76 | if err != nil { 77 | return nil, nil, err 78 | } 79 | 80 | root := new(actionRoot) 81 | resp, err := s.client.Do(req, root) 82 | if err != nil { 83 | return nil, resp, err 84 | } 85 | 86 | return &root.Event, resp, err 87 | } 88 | 89 | func (s *StorageActionsServiceOp) get(path string) (*Action, *Response, error) { 90 | req, err := s.client.NewRequest("GET", path, nil) 91 | if err != nil { 92 | return nil, nil, err 93 | } 94 | 95 | root := new(actionRoot) 96 | resp, err := s.client.Do(req, root) 97 | if err != nil { 98 | return nil, resp, err 99 | } 100 | 101 | return &root.Event, resp, err 102 | } 103 | 104 | func (s *StorageActionsServiceOp) list(path string) ([]Action, *Response, error) { 105 | req, err := s.client.NewRequest("GET", path, nil) 106 | if err != nil { 107 | return nil, nil, err 108 | } 109 | 110 | root := new(actionsRoot) 111 | resp, err := s.client.Do(req, root) 112 | if err != nil { 113 | return nil, resp, err 114 | } 115 | if l := root.Links; l != nil { 116 | resp.Links = l 117 | } 118 | 119 | return root.Actions, resp, err 120 | } 121 | 122 | func storageAllocationActionPath(volumeID string) string { 123 | return fmt.Sprintf("%s/%s/actions", storageAllocPath, volumeID) 124 | } 125 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/strings.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | var timestampType = reflect.TypeOf(Timestamp{}) 10 | 11 | // Stringify attempts to create a string representation of DigitalOcean types 12 | func Stringify(message interface{}) string { 13 | var buf bytes.Buffer 14 | v := reflect.ValueOf(message) 15 | stringifyValue(&buf, v) 16 | return buf.String() 17 | } 18 | 19 | // stringifyValue was graciously cargoculted from the goprotubuf library 20 | func stringifyValue(w *bytes.Buffer, val reflect.Value) { 21 | if val.Kind() == reflect.Ptr && val.IsNil() { 22 | _, _ = w.Write([]byte("")) 23 | return 24 | } 25 | 26 | v := reflect.Indirect(val) 27 | 28 | switch v.Kind() { 29 | case reflect.String: 30 | fmt.Fprintf(w, `"%s"`, v) 31 | case reflect.Slice: 32 | _, _ = w.Write([]byte{'['}) 33 | for i := 0; i < v.Len(); i++ { 34 | if i > 0 { 35 | _, _ = w.Write([]byte{' '}) 36 | } 37 | 38 | stringifyValue(w, v.Index(i)) 39 | } 40 | 41 | _, _ = w.Write([]byte{']'}) 42 | return 43 | case reflect.Struct: 44 | if v.Type().Name() != "" { 45 | _, _ = w.Write([]byte(v.Type().String())) 46 | } 47 | 48 | // special handling of Timestamp values 49 | if v.Type() == timestampType { 50 | fmt.Fprintf(w, "{%s}", v.Interface()) 51 | return 52 | } 53 | 54 | _, _ = w.Write([]byte{'{'}) 55 | 56 | var sep bool 57 | for i := 0; i < v.NumField(); i++ { 58 | fv := v.Field(i) 59 | if fv.Kind() == reflect.Ptr && fv.IsNil() { 60 | continue 61 | } 62 | if fv.Kind() == reflect.Slice && fv.IsNil() { 63 | continue 64 | } 65 | 66 | if sep { 67 | _, _ = w.Write([]byte(", ")) 68 | } else { 69 | sep = true 70 | } 71 | 72 | _, _ = w.Write([]byte(v.Type().Field(i).Name)) 73 | _, _ = w.Write([]byte{':'}) 74 | stringifyValue(w, fv) 75 | } 76 | 77 | _, _ = w.Write([]byte{'}'}) 78 | default: 79 | if v.CanInterface() { 80 | fmt.Fprint(w, v.Interface()) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/tags.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import "fmt" 4 | 5 | const tagsBasePath = "v2/tags" 6 | 7 | // TagsService is an interface for interfacing with the tags 8 | // endpoints of the DigitalOcean API 9 | // See: https://developers.digitalocean.com/documentation/v2#tags 10 | type TagsService interface { 11 | List(*ListOptions) ([]Tag, *Response, error) 12 | Get(string) (*Tag, *Response, error) 13 | Create(*TagCreateRequest) (*Tag, *Response, error) 14 | Update(string, *TagUpdateRequest) (*Response, error) 15 | Delete(string) (*Response, error) 16 | 17 | TagResources(string, *TagResourcesRequest) (*Response, error) 18 | UntagResources(string, *UntagResourcesRequest) (*Response, error) 19 | } 20 | 21 | // TagsServiceOp handles communication with tag related method of the 22 | // DigitalOcean API. 23 | type TagsServiceOp struct { 24 | client *Client 25 | } 26 | 27 | var _ TagsService = &TagsServiceOp{} 28 | 29 | // ResourceType represents a class of resource, currently only droplet are supported 30 | type ResourceType string 31 | 32 | const ( 33 | DropletResourceType ResourceType = "droplet" 34 | ) 35 | 36 | // Resource represent a single resource for associating/disassociating with tags 37 | type Resource struct { 38 | ID string `json:"resource_id,omit_empty"` 39 | Type ResourceType `json:"resource_type,omit_empty"` 40 | } 41 | 42 | // TaggedResources represent the set of resources a tag is attached to 43 | type TaggedResources struct { 44 | Droplets *TaggedDropletsResources `json:"droplets,omitempty"` 45 | } 46 | 47 | // TaggedDropletsResources represent the droplet resources a tag is attached to 48 | type TaggedDropletsResources struct { 49 | Count int `json:"count,float64,omitempty"` 50 | LastTagged *Droplet `json:"last_tagged,omitempty"` 51 | } 52 | 53 | // Tag represent DigitalOcean tag 54 | type Tag struct { 55 | Name string `json:"name,omitempty"` 56 | Resources *TaggedResources `json:"resources,omitempty"` 57 | } 58 | 59 | type TagCreateRequest struct { 60 | Name string `json:"name"` 61 | } 62 | 63 | type TagUpdateRequest struct { 64 | Name string `json:"name"` 65 | } 66 | 67 | type TagResourcesRequest struct { 68 | Resources []Resource `json:"resources"` 69 | } 70 | 71 | type UntagResourcesRequest struct { 72 | Resources []Resource `json:"resources"` 73 | } 74 | 75 | type tagsRoot struct { 76 | Tags []Tag `json:"tags"` 77 | Links *Links `json:"links"` 78 | } 79 | 80 | type tagRoot struct { 81 | Tag *Tag `json:"tag"` 82 | } 83 | 84 | // List all tags 85 | func (s *TagsServiceOp) List(opt *ListOptions) ([]Tag, *Response, error) { 86 | path := tagsBasePath 87 | path, err := addOptions(path, opt) 88 | 89 | if err != nil { 90 | return nil, nil, err 91 | } 92 | 93 | req, err := s.client.NewRequest("GET", path, nil) 94 | if err != nil { 95 | return nil, nil, err 96 | } 97 | 98 | root := new(tagsRoot) 99 | resp, err := s.client.Do(req, root) 100 | if err != nil { 101 | return nil, resp, err 102 | } 103 | if l := root.Links; l != nil { 104 | resp.Links = l 105 | } 106 | 107 | return root.Tags, resp, err 108 | } 109 | 110 | // Get a single tag 111 | func (s *TagsServiceOp) Get(name string) (*Tag, *Response, error) { 112 | path := fmt.Sprintf("%s/%s", tagsBasePath, name) 113 | 114 | req, err := s.client.NewRequest("GET", path, nil) 115 | if err != nil { 116 | return nil, nil, err 117 | } 118 | 119 | root := new(tagRoot) 120 | resp, err := s.client.Do(req, root) 121 | if err != nil { 122 | return nil, resp, err 123 | } 124 | 125 | return root.Tag, resp, err 126 | } 127 | 128 | // Create a new tag 129 | func (s *TagsServiceOp) Create(createRequest *TagCreateRequest) (*Tag, *Response, error) { 130 | if createRequest == nil { 131 | return nil, nil, NewArgError("createRequest", "cannot be nil") 132 | } 133 | 134 | req, err := s.client.NewRequest("POST", tagsBasePath, createRequest) 135 | if err != nil { 136 | return nil, nil, err 137 | } 138 | 139 | root := new(tagRoot) 140 | resp, err := s.client.Do(req, root) 141 | if err != nil { 142 | return nil, resp, err 143 | } 144 | 145 | return root.Tag, resp, err 146 | } 147 | 148 | // Update an exsting tag 149 | func (s *TagsServiceOp) Update(name string, updateRequest *TagUpdateRequest) (*Response, error) { 150 | if name == "" { 151 | return nil, NewArgError("name", "cannot be empty") 152 | } 153 | 154 | if updateRequest == nil { 155 | return nil, NewArgError("updateRequest", "cannot be nil") 156 | } 157 | 158 | path := fmt.Sprintf("%s/%s", tagsBasePath, name) 159 | req, err := s.client.NewRequest("PUT", path, updateRequest) 160 | if err != nil { 161 | return nil, err 162 | } 163 | 164 | resp, err := s.client.Do(req, nil) 165 | 166 | return resp, err 167 | } 168 | 169 | // Delete an existing tag 170 | func (s *TagsServiceOp) Delete(name string) (*Response, error) { 171 | if name == "" { 172 | return nil, NewArgError("name", "cannot be empty") 173 | } 174 | 175 | path := fmt.Sprintf("%s/%s", tagsBasePath, name) 176 | req, err := s.client.NewRequest("DELETE", path, nil) 177 | if err != nil { 178 | return nil, err 179 | } 180 | 181 | resp, err := s.client.Do(req, nil) 182 | 183 | return resp, err 184 | } 185 | 186 | // Associate resources with a tag 187 | func (s *TagsServiceOp) TagResources(name string, tagRequest *TagResourcesRequest) (*Response, error) { 188 | if name == "" { 189 | return nil, NewArgError("name", "cannot be empty") 190 | } 191 | 192 | if tagRequest == nil { 193 | return nil, NewArgError("tagRequest", "cannot be nil") 194 | } 195 | 196 | path := fmt.Sprintf("%s/%s/resources", tagsBasePath, name) 197 | req, err := s.client.NewRequest("POST", path, tagRequest) 198 | if err != nil { 199 | return nil, err 200 | } 201 | 202 | resp, err := s.client.Do(req, nil) 203 | 204 | return resp, err 205 | } 206 | 207 | // Dissociate resources with a tag 208 | func (s *TagsServiceOp) UntagResources(name string, untagRequest *UntagResourcesRequest) (*Response, error) { 209 | if name == "" { 210 | return nil, NewArgError("name", "cannot be empty") 211 | } 212 | 213 | if untagRequest == nil { 214 | return nil, NewArgError("tagRequest", "cannot be nil") 215 | } 216 | 217 | path := fmt.Sprintf("%s/%s/resources", tagsBasePath, name) 218 | req, err := s.client.NewRequest("DELETE", path, untagRequest) 219 | if err != nil { 220 | return nil, err 221 | } 222 | 223 | resp, err := s.client.Do(req, nil) 224 | 225 | return resp, err 226 | } 227 | -------------------------------------------------------------------------------- /vendor/github.com/digitalocean/godo/timestamp.go: -------------------------------------------------------------------------------- 1 | package godo 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | ) 7 | 8 | // Timestamp represents a time that can be unmarshalled from a JSON string 9 | // formatted as either an RFC3339 or Unix timestamp. All 10 | // exported methods of time.Time can be called on Timestamp. 11 | type Timestamp struct { 12 | time.Time 13 | } 14 | 15 | func (t Timestamp) String() string { 16 | return t.Time.String() 17 | } 18 | 19 | // UnmarshalJSON implements the json.Unmarshaler interface. 20 | // Time is expected in RFC3339 or Unix format. 21 | func (t *Timestamp) UnmarshalJSON(data []byte) error { 22 | str := string(data) 23 | i, err := strconv.ParseInt(str, 10, 64) 24 | if err == nil { 25 | t.Time = time.Unix(i, 0) 26 | } else { 27 | t.Time, err = time.Parse(`"`+time.RFC3339+`"`, str) 28 | } 29 | return err 30 | } 31 | 32 | // Equal reports whether t and u are equal based on time.Equal 33 | func (t Timestamp) Equal(u Timestamp) bool { 34 | return t.Time.Equal(u.Time) 35 | } 36 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-querystring/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Google. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-querystring/query/encode.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package query implements encoding of structs into URL query parameters. 6 | // 7 | // As a simple example: 8 | // 9 | // type Options struct { 10 | // Query string `url:"q"` 11 | // ShowAll bool `url:"all"` 12 | // Page int `url:"page"` 13 | // } 14 | // 15 | // opt := Options{ "foo", true, 2 } 16 | // v, _ := query.Values(opt) 17 | // fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2" 18 | // 19 | // The exact mapping between Go values and url.Values is described in the 20 | // documentation for the Values() function. 21 | package query 22 | 23 | import ( 24 | "bytes" 25 | "fmt" 26 | "net/url" 27 | "reflect" 28 | "strconv" 29 | "strings" 30 | "time" 31 | ) 32 | 33 | var timeType = reflect.TypeOf(time.Time{}) 34 | 35 | var encoderType = reflect.TypeOf(new(Encoder)).Elem() 36 | 37 | // Encoder is an interface implemented by any type that wishes to encode 38 | // itself into URL values in a non-standard way. 39 | type Encoder interface { 40 | EncodeValues(key string, v *url.Values) error 41 | } 42 | 43 | // Values returns the url.Values encoding of v. 44 | // 45 | // Values expects to be passed a struct, and traverses it recursively using the 46 | // following encoding rules. 47 | // 48 | // Each exported struct field is encoded as a URL parameter unless 49 | // 50 | // - the field's tag is "-", or 51 | // - the field is empty and its tag specifies the "omitempty" option 52 | // 53 | // The empty values are false, 0, any nil pointer or interface value, any array 54 | // slice, map, or string of length zero, and any time.Time that returns true 55 | // for IsZero(). 56 | // 57 | // The URL parameter name defaults to the struct field name but can be 58 | // specified in the struct field's tag value. The "url" key in the struct 59 | // field's tag value is the key name, followed by an optional comma and 60 | // options. For example: 61 | // 62 | // // Field is ignored by this package. 63 | // Field int `url:"-"` 64 | // 65 | // // Field appears as URL parameter "myName". 66 | // Field int `url:"myName"` 67 | // 68 | // // Field appears as URL parameter "myName" and the field is omitted if 69 | // // its value is empty 70 | // Field int `url:"myName,omitempty"` 71 | // 72 | // // Field appears as URL parameter "Field" (the default), but the field 73 | // // is skipped if empty. Note the leading comma. 74 | // Field int `url:",omitempty"` 75 | // 76 | // For encoding individual field values, the following type-dependent rules 77 | // apply: 78 | // 79 | // Boolean values default to encoding as the strings "true" or "false". 80 | // Including the "int" option signals that the field should be encoded as the 81 | // strings "1" or "0". 82 | // 83 | // time.Time values default to encoding as RFC3339 timestamps. Including the 84 | // "unix" option signals that the field should be encoded as a Unix time (see 85 | // time.Unix()) 86 | // 87 | // Slice and Array values default to encoding as multiple URL values of the 88 | // same name. Including the "comma" option signals that the field should be 89 | // encoded as a single comma-delimited value. Including the "space" option 90 | // similarly encodes the value as a single space-delimited string. Including 91 | // the "semicolon" option will encode the value as a semicolon-delimited string. 92 | // Including the "brackets" option signals that the multiple URL values should 93 | // have "[]" appended to the value name. "numbered" will append a number to 94 | // the end of each incidence of the value name, example: 95 | // name0=value0&name1=value1, etc. 96 | // 97 | // Anonymous struct fields are usually encoded as if their inner exported 98 | // fields were fields in the outer struct, subject to the standard Go 99 | // visibility rules. An anonymous struct field with a name given in its URL 100 | // tag is treated as having that name, rather than being anonymous. 101 | // 102 | // Non-nil pointer values are encoded as the value pointed to. 103 | // 104 | // Nested structs are encoded including parent fields in value names for 105 | // scoping. e.g: 106 | // 107 | // "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO" 108 | // 109 | // All other values are encoded using their default string representation. 110 | // 111 | // Multiple fields that encode to the same URL parameter name will be included 112 | // as multiple URL values of the same name. 113 | func Values(v interface{}) (url.Values, error) { 114 | values := make(url.Values) 115 | val := reflect.ValueOf(v) 116 | for val.Kind() == reflect.Ptr { 117 | if val.IsNil() { 118 | return values, nil 119 | } 120 | val = val.Elem() 121 | } 122 | 123 | if v == nil { 124 | return values, nil 125 | } 126 | 127 | if val.Kind() != reflect.Struct { 128 | return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) 129 | } 130 | 131 | err := reflectValue(values, val, "") 132 | return values, err 133 | } 134 | 135 | // reflectValue populates the values parameter from the struct fields in val. 136 | // Embedded structs are followed recursively (using the rules defined in the 137 | // Values function documentation) breadth-first. 138 | func reflectValue(values url.Values, val reflect.Value, scope string) error { 139 | var embedded []reflect.Value 140 | 141 | typ := val.Type() 142 | for i := 0; i < typ.NumField(); i++ { 143 | sf := typ.Field(i) 144 | if sf.PkgPath != "" && !sf.Anonymous { // unexported 145 | continue 146 | } 147 | 148 | sv := val.Field(i) 149 | tag := sf.Tag.Get("url") 150 | if tag == "-" { 151 | continue 152 | } 153 | name, opts := parseTag(tag) 154 | if name == "" { 155 | if sf.Anonymous && sv.Kind() == reflect.Struct { 156 | // save embedded struct for later processing 157 | embedded = append(embedded, sv) 158 | continue 159 | } 160 | 161 | name = sf.Name 162 | } 163 | 164 | if scope != "" { 165 | name = scope + "[" + name + "]" 166 | } 167 | 168 | if opts.Contains("omitempty") && isEmptyValue(sv) { 169 | continue 170 | } 171 | 172 | if sv.Type().Implements(encoderType) { 173 | if !reflect.Indirect(sv).IsValid() { 174 | sv = reflect.New(sv.Type().Elem()) 175 | } 176 | 177 | m := sv.Interface().(Encoder) 178 | if err := m.EncodeValues(name, &values); err != nil { 179 | return err 180 | } 181 | continue 182 | } 183 | 184 | if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { 185 | var del byte 186 | if opts.Contains("comma") { 187 | del = ',' 188 | } else if opts.Contains("space") { 189 | del = ' ' 190 | } else if opts.Contains("semicolon") { 191 | del = ';' 192 | } else if opts.Contains("brackets") { 193 | name = name + "[]" 194 | } 195 | 196 | if del != 0 { 197 | s := new(bytes.Buffer) 198 | first := true 199 | for i := 0; i < sv.Len(); i++ { 200 | if first { 201 | first = false 202 | } else { 203 | s.WriteByte(del) 204 | } 205 | s.WriteString(valueString(sv.Index(i), opts)) 206 | } 207 | values.Add(name, s.String()) 208 | } else { 209 | for i := 0; i < sv.Len(); i++ { 210 | k := name 211 | if opts.Contains("numbered") { 212 | k = fmt.Sprintf("%s%d", name, i) 213 | } 214 | values.Add(k, valueString(sv.Index(i), opts)) 215 | } 216 | } 217 | continue 218 | } 219 | 220 | if sv.Type() == timeType { 221 | values.Add(name, valueString(sv, opts)) 222 | continue 223 | } 224 | 225 | for sv.Kind() == reflect.Ptr { 226 | if sv.IsNil() { 227 | break 228 | } 229 | sv = sv.Elem() 230 | } 231 | 232 | if sv.Kind() == reflect.Struct { 233 | reflectValue(values, sv, name) 234 | continue 235 | } 236 | 237 | values.Add(name, valueString(sv, opts)) 238 | } 239 | 240 | for _, f := range embedded { 241 | if err := reflectValue(values, f, scope); err != nil { 242 | return err 243 | } 244 | } 245 | 246 | return nil 247 | } 248 | 249 | // valueString returns the string representation of a value. 250 | func valueString(v reflect.Value, opts tagOptions) string { 251 | for v.Kind() == reflect.Ptr { 252 | if v.IsNil() { 253 | return "" 254 | } 255 | v = v.Elem() 256 | } 257 | 258 | if v.Kind() == reflect.Bool && opts.Contains("int") { 259 | if v.Bool() { 260 | return "1" 261 | } 262 | return "0" 263 | } 264 | 265 | if v.Type() == timeType { 266 | t := v.Interface().(time.Time) 267 | if opts.Contains("unix") { 268 | return strconv.FormatInt(t.Unix(), 10) 269 | } 270 | return t.Format(time.RFC3339) 271 | } 272 | 273 | return fmt.Sprint(v.Interface()) 274 | } 275 | 276 | // isEmptyValue checks if a value should be considered empty for the purposes 277 | // of omitting fields with the "omitempty" option. 278 | func isEmptyValue(v reflect.Value) bool { 279 | switch v.Kind() { 280 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 281 | return v.Len() == 0 282 | case reflect.Bool: 283 | return !v.Bool() 284 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 285 | return v.Int() == 0 286 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 287 | return v.Uint() == 0 288 | case reflect.Float32, reflect.Float64: 289 | return v.Float() == 0 290 | case reflect.Interface, reflect.Ptr: 291 | return v.IsNil() 292 | } 293 | 294 | if v.Type() == timeType { 295 | return v.Interface().(time.Time).IsZero() 296 | } 297 | 298 | return false 299 | } 300 | 301 | // tagOptions is the string following a comma in a struct field's "url" tag, or 302 | // the empty string. It does not include the leading comma. 303 | type tagOptions []string 304 | 305 | // parseTag splits a struct field's url tag into its name and comma-separated 306 | // options. 307 | func parseTag(tag string) (string, tagOptions) { 308 | s := strings.Split(tag, ",") 309 | return s[0], s[1:] 310 | } 311 | 312 | // Contains checks whether the tagOptions contains the specified option. 313 | func (o tagOptions) Contains(option string) bool { 314 | for _, s := range o { 315 | if s == option { 316 | return true 317 | } 318 | } 319 | return false 320 | } 321 | -------------------------------------------------------------------------------- /vendor/github.com/tent/http-link-go/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Tent.is, LLC. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Tent.is, LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/tent/http-link-go/README.md: -------------------------------------------------------------------------------- 1 | # http-link-go [![Build Status](https://travis-ci.org/tent/http-link-go.png?branch=master)](https://travis-ci.org/tent/http-link-go) 2 | 3 | http-link-go implements parsing and serialization of Link header values as 4 | defined in [RFC 5988](https://tools.ietf.org/html/rfc5988). 5 | 6 | [**Documentation**](http://godoc.org/github.com/tent/http-link-go) 7 | 8 | ## Installation 9 | 10 | ```text 11 | go get github.com/tent/http-link-go 12 | ``` 13 | -------------------------------------------------------------------------------- /vendor/github.com/tent/http-link-go/link.go: -------------------------------------------------------------------------------- 1 | // Package link implements parsing and serialization of Link header values as 2 | // defined in RFC 5988. 3 | package link 4 | 5 | import ( 6 | "bytes" 7 | "errors" 8 | "sort" 9 | "unicode" 10 | ) 11 | 12 | type Link struct { 13 | URI string 14 | Rel string 15 | Params map[string]string 16 | } 17 | 18 | // Format serializes a slice of Links into a header value. It does not currently 19 | // implement RFC 2231 handling of non-ASCII character encoding and language 20 | // information. 21 | func Format(links []Link) string { 22 | buf := &bytes.Buffer{} 23 | for i, link := range links { 24 | if i > 0 { 25 | buf.Write([]byte(", ")) 26 | } 27 | buf.WriteByte('<') 28 | buf.WriteString(link.URI) 29 | buf.WriteByte('>') 30 | 31 | writeParam(buf, "rel", link.Rel) 32 | 33 | keys := make([]string, 0, len(link.Params)) 34 | for k := range link.Params { 35 | keys = append(keys, k) 36 | } 37 | sort.Strings(keys) 38 | 39 | for _, k := range keys { 40 | writeParam(buf, k, link.Params[k]) 41 | } 42 | } 43 | 44 | return buf.String() 45 | } 46 | 47 | func writeParam(buf *bytes.Buffer, key, value string) { 48 | buf.Write([]byte("; ")) 49 | buf.WriteString(key) 50 | buf.Write([]byte(`="`)) 51 | buf.WriteString(value) 52 | buf.WriteByte('"') 53 | } 54 | 55 | // Parse parses a Link header value into a slice of Links. It does not currently 56 | // implement RFC 2231 handling of non-ASCII character encoding and language 57 | // information. 58 | func Parse(l string) ([]Link, error) { 59 | v := []byte(l) 60 | v = bytes.TrimSpace(v) 61 | if len(v) == 0 { 62 | return nil, nil 63 | } 64 | 65 | links := make([]Link, 0, 1) 66 | for len(v) > 0 { 67 | if v[0] != '<' { 68 | return nil, errors.New("link: does not start with <") 69 | } 70 | lend := bytes.IndexByte(v, '>') 71 | if lend == -1 { 72 | return nil, errors.New("link: does not contain ending >") 73 | } 74 | 75 | params := make(map[string]string) 76 | link := Link{URI: string(v[1:lend]), Params: params} 77 | links = append(links, link) 78 | 79 | // trim off parsed url 80 | v = v[lend+1:] 81 | if len(v) == 0 { 82 | break 83 | } 84 | v = bytes.TrimLeftFunc(v, unicode.IsSpace) 85 | 86 | for len(v) > 0 { 87 | if v[0] != ';' && v[0] != ',' { 88 | return nil, errors.New(`link: expected ";" or "'", got "` + string(v[0:1]) + `"`) 89 | } 90 | var next bool 91 | if v[0] == ',' { 92 | next = true 93 | } 94 | v = bytes.TrimLeftFunc(v[1:], unicode.IsSpace) 95 | if next || len(v) == 0 { 96 | break 97 | } 98 | var key, value []byte 99 | key, value, v = consumeParam(v) 100 | if key == nil || value == nil { 101 | return nil, errors.New("link: malformed param") 102 | } 103 | if k := string(key); k == "rel" { 104 | if links[len(links)-1].Rel == "" { 105 | links[len(links)-1].Rel = string(value) 106 | } 107 | } else { 108 | params[k] = string(value) 109 | } 110 | v = bytes.TrimLeftFunc(v, unicode.IsSpace) 111 | } 112 | } 113 | 114 | return links, nil 115 | } 116 | 117 | func isTokenChar(r rune) bool { 118 | return r > 0x20 && r < 0x7f && r != '"' && r != ',' && r != '=' && r != ';' 119 | } 120 | 121 | func isNotTokenChar(r rune) bool { return !isTokenChar(r) } 122 | 123 | func consumeToken(v []byte) (token, rest []byte) { 124 | notPos := bytes.IndexFunc(v, isNotTokenChar) 125 | if notPos == -1 { 126 | return v, nil 127 | } 128 | if notPos == 0 { 129 | return nil, v 130 | } 131 | return v[0:notPos], v[notPos:] 132 | } 133 | 134 | func consumeValue(v []byte) (value, rest []byte) { 135 | if v[0] != '"' { 136 | return nil, v 137 | } 138 | 139 | rest = v[1:] 140 | buffer := &bytes.Buffer{} 141 | var nextIsLiteral bool 142 | for idx, r := range string(rest) { 143 | switch { 144 | case nextIsLiteral: 145 | buffer.WriteRune(r) 146 | nextIsLiteral = false 147 | case r == '"': 148 | return buffer.Bytes(), rest[idx+1:] 149 | case r == '\\': 150 | nextIsLiteral = true 151 | case r != '\r' && r != '\n': 152 | buffer.WriteRune(r) 153 | default: 154 | return nil, v 155 | } 156 | } 157 | return nil, v 158 | } 159 | 160 | func consumeParam(v []byte) (param, value, rest []byte) { 161 | param, rest = consumeToken(v) 162 | param = bytes.ToLower(param) 163 | if param == nil { 164 | return nil, nil, v 165 | } 166 | 167 | rest = bytes.TrimLeftFunc(rest, unicode.IsSpace) 168 | if len(rest) == 0 || rest[0] != '=' { 169 | return nil, nil, v 170 | } 171 | rest = rest[1:] // consume equals sign 172 | rest = bytes.TrimLeftFunc(rest, unicode.IsSpace) 173 | if len(rest) == 0 { 174 | return nil, nil, v 175 | } 176 | if rest[0] != '"' { 177 | value, rest = consumeToken(rest) 178 | } else { 179 | value, rest = consumeValue(rest) 180 | } 181 | if value == nil { 182 | return nil, nil, v 183 | } 184 | return param, value, rest 185 | } 186 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the Go project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of Go, where such license applies only to those patent 11 | claims, both currently owned or controlled by Google and acquired in 12 | the future, licensable by Google that are necessarily infringed by this 13 | implementation of Go. This grant does not include claims that would be 14 | infringed only as a consequence of further modification of this 15 | implementation. If you or your agent or exclusive licensee institute or 16 | order or agree to the institution of patent litigation against any 17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging 18 | that this implementation of Go or any code incorporated within this 19 | implementation of Go constitutes direct or contributory patent 20 | infringement, or inducement of patent infringement, then any patent 21 | rights granted to you under this License for this implementation of Go 22 | shall terminate as of the date such litigation is filed. 23 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/context/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package context defines the Context type, which carries deadlines, 6 | // cancelation signals, and other request-scoped values across API boundaries 7 | // and between processes. 8 | // 9 | // Incoming requests to a server should create a Context, and outgoing calls to 10 | // servers should accept a Context. The chain of function calls between must 11 | // propagate the Context, optionally replacing it with a modified copy created 12 | // using WithDeadline, WithTimeout, WithCancel, or WithValue. 13 | // 14 | // Programs that use Contexts should follow these rules to keep interfaces 15 | // consistent across packages and enable static analysis tools to check context 16 | // propagation: 17 | // 18 | // Do not store Contexts inside a struct type; instead, pass a Context 19 | // explicitly to each function that needs it. The Context should be the first 20 | // parameter, typically named ctx: 21 | // 22 | // func DoSomething(ctx context.Context, arg Arg) error { 23 | // // ... use ctx ... 24 | // } 25 | // 26 | // Do not pass a nil Context, even if a function permits it. Pass context.TODO 27 | // if you are unsure about which Context to use. 28 | // 29 | // Use context Values only for request-scoped data that transits processes and 30 | // APIs, not for passing optional parameters to functions. 31 | // 32 | // The same Context may be passed to functions running in different goroutines; 33 | // Contexts are safe for simultaneous use by multiple goroutines. 34 | // 35 | // See http://blog.golang.org/context for example code for a server that uses 36 | // Contexts. 37 | package context // import "golang.org/x/net/context" 38 | 39 | import "time" 40 | 41 | // A Context carries a deadline, a cancelation signal, and other values across 42 | // API boundaries. 43 | // 44 | // Context's methods may be called by multiple goroutines simultaneously. 45 | type Context interface { 46 | // Deadline returns the time when work done on behalf of this context 47 | // should be canceled. Deadline returns ok==false when no deadline is 48 | // set. Successive calls to Deadline return the same results. 49 | Deadline() (deadline time.Time, ok bool) 50 | 51 | // Done returns a channel that's closed when work done on behalf of this 52 | // context should be canceled. Done may return nil if this context can 53 | // never be canceled. Successive calls to Done return the same value. 54 | // 55 | // WithCancel arranges for Done to be closed when cancel is called; 56 | // WithDeadline arranges for Done to be closed when the deadline 57 | // expires; WithTimeout arranges for Done to be closed when the timeout 58 | // elapses. 59 | // 60 | // Done is provided for use in select statements: 61 | // 62 | // // Stream generates values with DoSomething and sends them to out 63 | // // until DoSomething returns an error or ctx.Done is closed. 64 | // func Stream(ctx context.Context, out chan<- Value) error { 65 | // for { 66 | // v, err := DoSomething(ctx) 67 | // if err != nil { 68 | // return err 69 | // } 70 | // select { 71 | // case <-ctx.Done(): 72 | // return ctx.Err() 73 | // case out <- v: 74 | // } 75 | // } 76 | // } 77 | // 78 | // See http://blog.golang.org/pipelines for more examples of how to use 79 | // a Done channel for cancelation. 80 | Done() <-chan struct{} 81 | 82 | // Err returns a non-nil error value after Done is closed. Err returns 83 | // Canceled if the context was canceled or DeadlineExceeded if the 84 | // context's deadline passed. No other values for Err are defined. 85 | // After Done is closed, successive calls to Err return the same value. 86 | Err() error 87 | 88 | // Value returns the value associated with this context for key, or nil 89 | // if no value is associated with key. Successive calls to Value with 90 | // the same key returns the same result. 91 | // 92 | // Use context values only for request-scoped data that transits 93 | // processes and API boundaries, not for passing optional parameters to 94 | // functions. 95 | // 96 | // A key identifies a specific value in a Context. Functions that wish 97 | // to store values in Context typically allocate a key in a global 98 | // variable then use that key as the argument to context.WithValue and 99 | // Context.Value. A key can be any type that supports equality; 100 | // packages should define keys as an unexported type to avoid 101 | // collisions. 102 | // 103 | // Packages that define a Context key should provide type-safe accessors 104 | // for the values stores using that key: 105 | // 106 | // // Package user defines a User type that's stored in Contexts. 107 | // package user 108 | // 109 | // import "golang.org/x/net/context" 110 | // 111 | // // User is the type of value stored in the Contexts. 112 | // type User struct {...} 113 | // 114 | // // key is an unexported type for keys defined in this package. 115 | // // This prevents collisions with keys defined in other packages. 116 | // type key int 117 | // 118 | // // userKey is the key for user.User values in Contexts. It is 119 | // // unexported; clients use user.NewContext and user.FromContext 120 | // // instead of using this key directly. 121 | // var userKey key = 0 122 | // 123 | // // NewContext returns a new Context that carries value u. 124 | // func NewContext(ctx context.Context, u *User) context.Context { 125 | // return context.WithValue(ctx, userKey, u) 126 | // } 127 | // 128 | // // FromContext returns the User value stored in ctx, if any. 129 | // func FromContext(ctx context.Context) (*User, bool) { 130 | // u, ok := ctx.Value(userKey).(*User) 131 | // return u, ok 132 | // } 133 | Value(key interface{}) interface{} 134 | } 135 | 136 | // Background returns a non-nil, empty Context. It is never canceled, has no 137 | // values, and has no deadline. It is typically used by the main function, 138 | // initialization, and tests, and as the top-level Context for incoming 139 | // requests. 140 | func Background() Context { 141 | return background 142 | } 143 | 144 | // TODO returns a non-nil, empty Context. Code should use context.TODO when 145 | // it's unclear which Context to use or it is not yet available (because the 146 | // surrounding function has not yet been extended to accept a Context 147 | // parameter). TODO is recognized by static analysis tools that determine 148 | // whether Contexts are propagated correctly in a program. 149 | func TODO() Context { 150 | return todo 151 | } 152 | 153 | // A CancelFunc tells an operation to abandon its work. 154 | // A CancelFunc does not wait for the work to stop. 155 | // After the first call, subsequent calls to a CancelFunc do nothing. 156 | type CancelFunc func() 157 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/context/go17.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build go1.7 6 | 7 | package context 8 | 9 | import ( 10 | "context" // standard library's context, as of Go 1.7 11 | "time" 12 | ) 13 | 14 | var ( 15 | todo = context.TODO() 16 | background = context.Background() 17 | ) 18 | 19 | // Canceled is the error returned by Context.Err when the context is canceled. 20 | var Canceled = context.Canceled 21 | 22 | // DeadlineExceeded is the error returned by Context.Err when the context's 23 | // deadline passes. 24 | var DeadlineExceeded = context.DeadlineExceeded 25 | 26 | // WithCancel returns a copy of parent with a new Done channel. The returned 27 | // context's Done channel is closed when the returned cancel function is called 28 | // or when the parent context's Done channel is closed, whichever happens first. 29 | // 30 | // Canceling this context releases resources associated with it, so code should 31 | // call cancel as soon as the operations running in this Context complete. 32 | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { 33 | ctx, f := context.WithCancel(parent) 34 | return ctx, CancelFunc(f) 35 | } 36 | 37 | // WithDeadline returns a copy of the parent context with the deadline adjusted 38 | // to be no later than d. If the parent's deadline is already earlier than d, 39 | // WithDeadline(parent, d) is semantically equivalent to parent. The returned 40 | // context's Done channel is closed when the deadline expires, when the returned 41 | // cancel function is called, or when the parent context's Done channel is 42 | // closed, whichever happens first. 43 | // 44 | // Canceling this context releases resources associated with it, so code should 45 | // call cancel as soon as the operations running in this Context complete. 46 | func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { 47 | ctx, f := context.WithDeadline(parent, deadline) 48 | return ctx, CancelFunc(f) 49 | } 50 | 51 | // WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). 52 | // 53 | // Canceling this context releases resources associated with it, so code should 54 | // call cancel as soon as the operations running in this Context complete: 55 | // 56 | // func slowOperationWithTimeout(ctx context.Context) (Result, error) { 57 | // ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) 58 | // defer cancel() // releases resources if slowOperation completes before timeout elapses 59 | // return slowOperation(ctx) 60 | // } 61 | func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { 62 | return WithDeadline(parent, time.Now().Add(timeout)) 63 | } 64 | 65 | // WithValue returns a copy of parent in which the value associated with key is 66 | // val. 67 | // 68 | // Use context Values only for request-scoped data that transits processes and 69 | // APIs, not for passing optional parameters to functions. 70 | func WithValue(parent Context, key interface{}, val interface{}) Context { 71 | return context.WithValue(parent, key, val) 72 | } 73 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/context/pre_go17.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !go1.7 6 | 7 | package context 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | // An emptyCtx is never canceled, has no values, and has no deadline. It is not 17 | // struct{}, since vars of this type must have distinct addresses. 18 | type emptyCtx int 19 | 20 | func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { 21 | return 22 | } 23 | 24 | func (*emptyCtx) Done() <-chan struct{} { 25 | return nil 26 | } 27 | 28 | func (*emptyCtx) Err() error { 29 | return nil 30 | } 31 | 32 | func (*emptyCtx) Value(key interface{}) interface{} { 33 | return nil 34 | } 35 | 36 | func (e *emptyCtx) String() string { 37 | switch e { 38 | case background: 39 | return "context.Background" 40 | case todo: 41 | return "context.TODO" 42 | } 43 | return "unknown empty Context" 44 | } 45 | 46 | var ( 47 | background = new(emptyCtx) 48 | todo = new(emptyCtx) 49 | ) 50 | 51 | // Canceled is the error returned by Context.Err when the context is canceled. 52 | var Canceled = errors.New("context canceled") 53 | 54 | // DeadlineExceeded is the error returned by Context.Err when the context's 55 | // deadline passes. 56 | var DeadlineExceeded = errors.New("context deadline exceeded") 57 | 58 | // WithCancel returns a copy of parent with a new Done channel. The returned 59 | // context's Done channel is closed when the returned cancel function is called 60 | // or when the parent context's Done channel is closed, whichever happens first. 61 | // 62 | // Canceling this context releases resources associated with it, so code should 63 | // call cancel as soon as the operations running in this Context complete. 64 | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { 65 | c := newCancelCtx(parent) 66 | propagateCancel(parent, c) 67 | return c, func() { c.cancel(true, Canceled) } 68 | } 69 | 70 | // newCancelCtx returns an initialized cancelCtx. 71 | func newCancelCtx(parent Context) *cancelCtx { 72 | return &cancelCtx{ 73 | Context: parent, 74 | done: make(chan struct{}), 75 | } 76 | } 77 | 78 | // propagateCancel arranges for child to be canceled when parent is. 79 | func propagateCancel(parent Context, child canceler) { 80 | if parent.Done() == nil { 81 | return // parent is never canceled 82 | } 83 | if p, ok := parentCancelCtx(parent); ok { 84 | p.mu.Lock() 85 | if p.err != nil { 86 | // parent has already been canceled 87 | child.cancel(false, p.err) 88 | } else { 89 | if p.children == nil { 90 | p.children = make(map[canceler]bool) 91 | } 92 | p.children[child] = true 93 | } 94 | p.mu.Unlock() 95 | } else { 96 | go func() { 97 | select { 98 | case <-parent.Done(): 99 | child.cancel(false, parent.Err()) 100 | case <-child.Done(): 101 | } 102 | }() 103 | } 104 | } 105 | 106 | // parentCancelCtx follows a chain of parent references until it finds a 107 | // *cancelCtx. This function understands how each of the concrete types in this 108 | // package represents its parent. 109 | func parentCancelCtx(parent Context) (*cancelCtx, bool) { 110 | for { 111 | switch c := parent.(type) { 112 | case *cancelCtx: 113 | return c, true 114 | case *timerCtx: 115 | return c.cancelCtx, true 116 | case *valueCtx: 117 | parent = c.Context 118 | default: 119 | return nil, false 120 | } 121 | } 122 | } 123 | 124 | // removeChild removes a context from its parent. 125 | func removeChild(parent Context, child canceler) { 126 | p, ok := parentCancelCtx(parent) 127 | if !ok { 128 | return 129 | } 130 | p.mu.Lock() 131 | if p.children != nil { 132 | delete(p.children, child) 133 | } 134 | p.mu.Unlock() 135 | } 136 | 137 | // A canceler is a context type that can be canceled directly. The 138 | // implementations are *cancelCtx and *timerCtx. 139 | type canceler interface { 140 | cancel(removeFromParent bool, err error) 141 | Done() <-chan struct{} 142 | } 143 | 144 | // A cancelCtx can be canceled. When canceled, it also cancels any children 145 | // that implement canceler. 146 | type cancelCtx struct { 147 | Context 148 | 149 | done chan struct{} // closed by the first cancel call. 150 | 151 | mu sync.Mutex 152 | children map[canceler]bool // set to nil by the first cancel call 153 | err error // set to non-nil by the first cancel call 154 | } 155 | 156 | func (c *cancelCtx) Done() <-chan struct{} { 157 | return c.done 158 | } 159 | 160 | func (c *cancelCtx) Err() error { 161 | c.mu.Lock() 162 | defer c.mu.Unlock() 163 | return c.err 164 | } 165 | 166 | func (c *cancelCtx) String() string { 167 | return fmt.Sprintf("%v.WithCancel", c.Context) 168 | } 169 | 170 | // cancel closes c.done, cancels each of c's children, and, if 171 | // removeFromParent is true, removes c from its parent's children. 172 | func (c *cancelCtx) cancel(removeFromParent bool, err error) { 173 | if err == nil { 174 | panic("context: internal error: missing cancel error") 175 | } 176 | c.mu.Lock() 177 | if c.err != nil { 178 | c.mu.Unlock() 179 | return // already canceled 180 | } 181 | c.err = err 182 | close(c.done) 183 | for child := range c.children { 184 | // NOTE: acquiring the child's lock while holding parent's lock. 185 | child.cancel(false, err) 186 | } 187 | c.children = nil 188 | c.mu.Unlock() 189 | 190 | if removeFromParent { 191 | removeChild(c.Context, c) 192 | } 193 | } 194 | 195 | // WithDeadline returns a copy of the parent context with the deadline adjusted 196 | // to be no later than d. If the parent's deadline is already earlier than d, 197 | // WithDeadline(parent, d) is semantically equivalent to parent. The returned 198 | // context's Done channel is closed when the deadline expires, when the returned 199 | // cancel function is called, or when the parent context's Done channel is 200 | // closed, whichever happens first. 201 | // 202 | // Canceling this context releases resources associated with it, so code should 203 | // call cancel as soon as the operations running in this Context complete. 204 | func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { 205 | if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { 206 | // The current deadline is already sooner than the new one. 207 | return WithCancel(parent) 208 | } 209 | c := &timerCtx{ 210 | cancelCtx: newCancelCtx(parent), 211 | deadline: deadline, 212 | } 213 | propagateCancel(parent, c) 214 | d := deadline.Sub(time.Now()) 215 | if d <= 0 { 216 | c.cancel(true, DeadlineExceeded) // deadline has already passed 217 | return c, func() { c.cancel(true, Canceled) } 218 | } 219 | c.mu.Lock() 220 | defer c.mu.Unlock() 221 | if c.err == nil { 222 | c.timer = time.AfterFunc(d, func() { 223 | c.cancel(true, DeadlineExceeded) 224 | }) 225 | } 226 | return c, func() { c.cancel(true, Canceled) } 227 | } 228 | 229 | // A timerCtx carries a timer and a deadline. It embeds a cancelCtx to 230 | // implement Done and Err. It implements cancel by stopping its timer then 231 | // delegating to cancelCtx.cancel. 232 | type timerCtx struct { 233 | *cancelCtx 234 | timer *time.Timer // Under cancelCtx.mu. 235 | 236 | deadline time.Time 237 | } 238 | 239 | func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { 240 | return c.deadline, true 241 | } 242 | 243 | func (c *timerCtx) String() string { 244 | return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now())) 245 | } 246 | 247 | func (c *timerCtx) cancel(removeFromParent bool, err error) { 248 | c.cancelCtx.cancel(false, err) 249 | if removeFromParent { 250 | // Remove this timerCtx from its parent cancelCtx's children. 251 | removeChild(c.cancelCtx.Context, c) 252 | } 253 | c.mu.Lock() 254 | if c.timer != nil { 255 | c.timer.Stop() 256 | c.timer = nil 257 | } 258 | c.mu.Unlock() 259 | } 260 | 261 | // WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). 262 | // 263 | // Canceling this context releases resources associated with it, so code should 264 | // call cancel as soon as the operations running in this Context complete: 265 | // 266 | // func slowOperationWithTimeout(ctx context.Context) (Result, error) { 267 | // ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) 268 | // defer cancel() // releases resources if slowOperation completes before timeout elapses 269 | // return slowOperation(ctx) 270 | // } 271 | func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { 272 | return WithDeadline(parent, time.Now().Add(timeout)) 273 | } 274 | 275 | // WithValue returns a copy of parent in which the value associated with key is 276 | // val. 277 | // 278 | // Use context Values only for request-scoped data that transits processes and 279 | // APIs, not for passing optional parameters to functions. 280 | func WithValue(parent Context, key interface{}, val interface{}) Context { 281 | return &valueCtx{parent, key, val} 282 | } 283 | 284 | // A valueCtx carries a key-value pair. It implements Value for that key and 285 | // delegates all other calls to the embedded Context. 286 | type valueCtx struct { 287 | Context 288 | key, val interface{} 289 | } 290 | 291 | func (c *valueCtx) String() string { 292 | return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) 293 | } 294 | 295 | func (c *valueCtx) Value(key interface{}) interface{} { 296 | if c.key == key { 297 | return c.val 298 | } 299 | return c.Context.Value(key) 300 | } 301 | -------------------------------------------------------------------------------- /vendor/golang.org/x/oauth2/AUTHORS: -------------------------------------------------------------------------------- 1 | # This source code refers to The Go Authors for copyright purposes. 2 | # The master list of authors is in the main Go distribution, 3 | # visible at http://tip.golang.org/AUTHORS. 4 | -------------------------------------------------------------------------------- /vendor/golang.org/x/oauth2/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Go 2 | 3 | Go is an open source project. 4 | 5 | It is the work of hundreds of contributors. We appreciate your help! 6 | 7 | 8 | ## Filing issues 9 | 10 | When [filing an issue](https://github.com/golang/oauth2/issues), make sure to answer these five questions: 11 | 12 | 1. What version of Go are you using (`go version`)? 13 | 2. What operating system and processor architecture are you using? 14 | 3. What did you do? 15 | 4. What did you expect to see? 16 | 5. What did you see instead? 17 | 18 | General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker. 19 | The gophers there will answer or ask you to file an issue if you've tripped over a bug. 20 | 21 | ## Contributing code 22 | 23 | Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html) 24 | before sending patches. 25 | 26 | **We do not accept GitHub pull requests** 27 | (we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review). 28 | 29 | Unless otherwise noted, the Go source files are distributed under 30 | the BSD-style license found in the LICENSE file. 31 | 32 | -------------------------------------------------------------------------------- /vendor/golang.org/x/oauth2/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This source code was written by the Go contributors. 2 | # The master list of contributors is in the main Go distribution, 3 | # visible at http://tip.golang.org/CONTRIBUTORS. 4 | -------------------------------------------------------------------------------- /vendor/golang.org/x/oauth2/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The oauth2 Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/golang.org/x/oauth2/README.md: -------------------------------------------------------------------------------- 1 | # OAuth2 for Go 2 | 3 | [![Build Status](https://travis-ci.org/golang/oauth2.svg?branch=master)](https://travis-ci.org/golang/oauth2) 4 | [![GoDoc](https://godoc.org/golang.org/x/oauth2?status.svg)](https://godoc.org/golang.org/x/oauth2) 5 | 6 | oauth2 package contains a client implementation for OAuth 2.0 spec. 7 | 8 | ## Installation 9 | 10 | ~~~~ 11 | go get golang.org/x/oauth2 12 | ~~~~ 13 | 14 | See godoc for further documentation and examples. 15 | 16 | * [godoc.org/golang.org/x/oauth2](http://godoc.org/golang.org/x/oauth2) 17 | * [godoc.org/golang.org/x/oauth2/google](http://godoc.org/golang.org/x/oauth2/google) 18 | 19 | 20 | ## App Engine 21 | 22 | In change 96e89be (March 2015) we removed the `oauth2.Context2` type in favor 23 | of the [`context.Context`](https://golang.org/x/net/context#Context) type from 24 | the `golang.org/x/net/context` package 25 | 26 | This means its no longer possible to use the "Classic App Engine" 27 | `appengine.Context` type with the `oauth2` package. (You're using 28 | Classic App Engine if you import the package `"appengine"`.) 29 | 30 | To work around this, you may use the new `"google.golang.org/appengine"` 31 | package. This package has almost the same API as the `"appengine"` package, 32 | but it can be fetched with `go get` and used on "Managed VMs" and well as 33 | Classic App Engine. 34 | 35 | See the [new `appengine` package's readme](https://github.com/golang/appengine#updating-a-go-app-engine-app) 36 | for information on updating your app. 37 | 38 | If you don't want to update your entire app to use the new App Engine packages, 39 | you may use both sets of packages in parallel, using only the new packages 40 | with the `oauth2` package. 41 | 42 | import ( 43 | "golang.org/x/net/context" 44 | "golang.org/x/oauth2" 45 | "golang.org/x/oauth2/google" 46 | newappengine "google.golang.org/appengine" 47 | newurlfetch "google.golang.org/appengine/urlfetch" 48 | 49 | "appengine" 50 | ) 51 | 52 | func handler(w http.ResponseWriter, r *http.Request) { 53 | var c appengine.Context = appengine.NewContext(r) 54 | c.Infof("Logging a message with the old package") 55 | 56 | var ctx context.Context = newappengine.NewContext(r) 57 | client := &http.Client{ 58 | Transport: &oauth2.Transport{ 59 | Source: google.AppEngineTokenSource(ctx, "scope"), 60 | Base: &newurlfetch.Transport{Context: ctx}, 61 | }, 62 | } 63 | client.Get("...") 64 | } 65 | 66 | -------------------------------------------------------------------------------- /vendor/golang.org/x/oauth2/client_appengine.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build appengine 6 | 7 | // App Engine hooks. 8 | 9 | package oauth2 10 | 11 | import ( 12 | "net/http" 13 | 14 | "golang.org/x/net/context" 15 | "golang.org/x/oauth2/internal" 16 | "google.golang.org/appengine/urlfetch" 17 | ) 18 | 19 | func init() { 20 | internal.RegisterContextClientFunc(contextClientAppEngine) 21 | } 22 | 23 | func contextClientAppEngine(ctx context.Context) (*http.Client, error) { 24 | return urlfetch.Client(ctx), nil 25 | } 26 | -------------------------------------------------------------------------------- /vendor/golang.org/x/oauth2/internal/oauth2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package internal contains support packages for oauth2 package. 6 | package internal 7 | 8 | import ( 9 | "bufio" 10 | "crypto/rsa" 11 | "crypto/x509" 12 | "encoding/pem" 13 | "errors" 14 | "fmt" 15 | "io" 16 | "strings" 17 | ) 18 | 19 | // ParseKey converts the binary contents of a private key file 20 | // to an *rsa.PrivateKey. It detects whether the private key is in a 21 | // PEM container or not. If so, it extracts the the private key 22 | // from PEM container before conversion. It only supports PEM 23 | // containers with no passphrase. 24 | func ParseKey(key []byte) (*rsa.PrivateKey, error) { 25 | block, _ := pem.Decode(key) 26 | if block != nil { 27 | key = block.Bytes 28 | } 29 | parsedKey, err := x509.ParsePKCS8PrivateKey(key) 30 | if err != nil { 31 | parsedKey, err = x509.ParsePKCS1PrivateKey(key) 32 | if err != nil { 33 | return nil, fmt.Errorf("private key should be a PEM or plain PKSC1 or PKCS8; parse error: %v", err) 34 | } 35 | } 36 | parsed, ok := parsedKey.(*rsa.PrivateKey) 37 | if !ok { 38 | return nil, errors.New("private key is invalid") 39 | } 40 | return parsed, nil 41 | } 42 | 43 | func ParseINI(ini io.Reader) (map[string]map[string]string, error) { 44 | result := map[string]map[string]string{ 45 | "": map[string]string{}, // root section 46 | } 47 | scanner := bufio.NewScanner(ini) 48 | currentSection := "" 49 | for scanner.Scan() { 50 | line := strings.TrimSpace(scanner.Text()) 51 | if strings.HasPrefix(line, ";") { 52 | // comment. 53 | continue 54 | } 55 | if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { 56 | currentSection = strings.TrimSpace(line[1 : len(line)-1]) 57 | result[currentSection] = map[string]string{} 58 | continue 59 | } 60 | parts := strings.SplitN(line, "=", 2) 61 | if len(parts) == 2 && parts[0] != "" { 62 | result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) 63 | } 64 | } 65 | if err := scanner.Err(); err != nil { 66 | return nil, fmt.Errorf("error scanning ini: %v", err) 67 | } 68 | return result, nil 69 | } 70 | 71 | func CondVal(v string) []string { 72 | if v == "" { 73 | return nil 74 | } 75 | return []string{v} 76 | } 77 | -------------------------------------------------------------------------------- /vendor/golang.org/x/oauth2/internal/token.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package internal contains support packages for oauth2 package. 6 | package internal 7 | 8 | import ( 9 | "encoding/json" 10 | "fmt" 11 | "io" 12 | "io/ioutil" 13 | "mime" 14 | "net/http" 15 | "net/url" 16 | "strconv" 17 | "strings" 18 | "time" 19 | 20 | "golang.org/x/net/context" 21 | ) 22 | 23 | // Token represents the crendentials used to authorize 24 | // the requests to access protected resources on the OAuth 2.0 25 | // provider's backend. 26 | // 27 | // This type is a mirror of oauth2.Token and exists to break 28 | // an otherwise-circular dependency. Other internal packages 29 | // should convert this Token into an oauth2.Token before use. 30 | type Token struct { 31 | // AccessToken is the token that authorizes and authenticates 32 | // the requests. 33 | AccessToken string 34 | 35 | // TokenType is the type of token. 36 | // The Type method returns either this or "Bearer", the default. 37 | TokenType string 38 | 39 | // RefreshToken is a token that's used by the application 40 | // (as opposed to the user) to refresh the access token 41 | // if it expires. 42 | RefreshToken string 43 | 44 | // Expiry is the optional expiration time of the access token. 45 | // 46 | // If zero, TokenSource implementations will reuse the same 47 | // token forever and RefreshToken or equivalent 48 | // mechanisms for that TokenSource will not be used. 49 | Expiry time.Time 50 | 51 | // Raw optionally contains extra metadata from the server 52 | // when updating a token. 53 | Raw interface{} 54 | } 55 | 56 | // tokenJSON is the struct representing the HTTP response from OAuth2 57 | // providers returning a token in JSON form. 58 | type tokenJSON struct { 59 | AccessToken string `json:"access_token"` 60 | TokenType string `json:"token_type"` 61 | RefreshToken string `json:"refresh_token"` 62 | ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number 63 | Expires expirationTime `json:"expires"` // broken Facebook spelling of expires_in 64 | } 65 | 66 | func (e *tokenJSON) expiry() (t time.Time) { 67 | if v := e.ExpiresIn; v != 0 { 68 | return time.Now().Add(time.Duration(v) * time.Second) 69 | } 70 | if v := e.Expires; v != 0 { 71 | return time.Now().Add(time.Duration(v) * time.Second) 72 | } 73 | return 74 | } 75 | 76 | type expirationTime int32 77 | 78 | func (e *expirationTime) UnmarshalJSON(b []byte) error { 79 | var n json.Number 80 | err := json.Unmarshal(b, &n) 81 | if err != nil { 82 | return err 83 | } 84 | i, err := n.Int64() 85 | if err != nil { 86 | return err 87 | } 88 | *e = expirationTime(i) 89 | return nil 90 | } 91 | 92 | var brokenAuthHeaderProviders = []string{ 93 | "https://accounts.google.com/", 94 | "https://api.dropbox.com/", 95 | "https://api.dropboxapi.com/", 96 | "https://api.instagram.com/", 97 | "https://api.netatmo.net/", 98 | "https://api.odnoklassniki.ru/", 99 | "https://api.pushbullet.com/", 100 | "https://api.soundcloud.com/", 101 | "https://api.twitch.tv/", 102 | "https://app.box.com/", 103 | "https://connect.stripe.com/", 104 | "https://login.microsoftonline.com/", 105 | "https://login.salesforce.com/", 106 | "https://oauth.sandbox.trainingpeaks.com/", 107 | "https://oauth.trainingpeaks.com/", 108 | "https://oauth.vk.com/", 109 | "https://openapi.baidu.com/", 110 | "https://slack.com/", 111 | "https://test-sandbox.auth.corp.google.com", 112 | "https://test.salesforce.com/", 113 | "https://user.gini.net/", 114 | "https://www.douban.com/", 115 | "https://www.googleapis.com/", 116 | "https://www.linkedin.com/", 117 | "https://www.strava.com/oauth/", 118 | "https://www.wunderlist.com/oauth/", 119 | "https://api.patreon.com/", 120 | } 121 | 122 | func RegisterBrokenAuthHeaderProvider(tokenURL string) { 123 | brokenAuthHeaderProviders = append(brokenAuthHeaderProviders, tokenURL) 124 | } 125 | 126 | // providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL 127 | // implements the OAuth2 spec correctly 128 | // See https://code.google.com/p/goauth2/issues/detail?id=31 for background. 129 | // In summary: 130 | // - Reddit only accepts client secret in the Authorization header 131 | // - Dropbox accepts either it in URL param or Auth header, but not both. 132 | // - Google only accepts URL param (not spec compliant?), not Auth header 133 | // - Stripe only accepts client secret in Auth header with Bearer method, not Basic 134 | func providerAuthHeaderWorks(tokenURL string) bool { 135 | for _, s := range brokenAuthHeaderProviders { 136 | if strings.HasPrefix(tokenURL, s) { 137 | // Some sites fail to implement the OAuth2 spec fully. 138 | return false 139 | } 140 | } 141 | 142 | // Assume the provider implements the spec properly 143 | // otherwise. We can add more exceptions as they're 144 | // discovered. We will _not_ be adding configurable hooks 145 | // to this package to let users select server bugs. 146 | return true 147 | } 148 | 149 | func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values) (*Token, error) { 150 | hc, err := ContextClient(ctx) 151 | if err != nil { 152 | return nil, err 153 | } 154 | v.Set("client_id", clientID) 155 | bustedAuth := !providerAuthHeaderWorks(tokenURL) 156 | if bustedAuth && clientSecret != "" { 157 | v.Set("client_secret", clientSecret) 158 | } 159 | req, err := http.NewRequest("POST", tokenURL, strings.NewReader(v.Encode())) 160 | if err != nil { 161 | return nil, err 162 | } 163 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 164 | if !bustedAuth { 165 | req.SetBasicAuth(clientID, clientSecret) 166 | } 167 | r, err := hc.Do(req) 168 | if err != nil { 169 | return nil, err 170 | } 171 | defer r.Body.Close() 172 | body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20)) 173 | if err != nil { 174 | return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) 175 | } 176 | if code := r.StatusCode; code < 200 || code > 299 { 177 | return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body) 178 | } 179 | 180 | var token *Token 181 | content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type")) 182 | switch content { 183 | case "application/x-www-form-urlencoded", "text/plain": 184 | vals, err := url.ParseQuery(string(body)) 185 | if err != nil { 186 | return nil, err 187 | } 188 | token = &Token{ 189 | AccessToken: vals.Get("access_token"), 190 | TokenType: vals.Get("token_type"), 191 | RefreshToken: vals.Get("refresh_token"), 192 | Raw: vals, 193 | } 194 | e := vals.Get("expires_in") 195 | if e == "" { 196 | // TODO(jbd): Facebook's OAuth2 implementation is broken and 197 | // returns expires_in field in expires. Remove the fallback to expires, 198 | // when Facebook fixes their implementation. 199 | e = vals.Get("expires") 200 | } 201 | expires, _ := strconv.Atoi(e) 202 | if expires != 0 { 203 | token.Expiry = time.Now().Add(time.Duration(expires) * time.Second) 204 | } 205 | default: 206 | var tj tokenJSON 207 | if err = json.Unmarshal(body, &tj); err != nil { 208 | return nil, err 209 | } 210 | token = &Token{ 211 | AccessToken: tj.AccessToken, 212 | TokenType: tj.TokenType, 213 | RefreshToken: tj.RefreshToken, 214 | Expiry: tj.expiry(), 215 | Raw: make(map[string]interface{}), 216 | } 217 | json.Unmarshal(body, &token.Raw) // no error checks for optional fields 218 | } 219 | // Don't overwrite `RefreshToken` with an empty value 220 | // if this was a token refreshing request. 221 | if token.RefreshToken == "" { 222 | token.RefreshToken = v.Get("refresh_token") 223 | } 224 | return token, nil 225 | } 226 | -------------------------------------------------------------------------------- /vendor/golang.org/x/oauth2/internal/transport.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package internal contains support packages for oauth2 package. 6 | package internal 7 | 8 | import ( 9 | "net/http" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | // HTTPClient is the context key to use with golang.org/x/net/context's 15 | // WithValue function to associate an *http.Client value with a context. 16 | var HTTPClient ContextKey 17 | 18 | // ContextKey is just an empty struct. It exists so HTTPClient can be 19 | // an immutable public variable with a unique type. It's immutable 20 | // because nobody else can create a ContextKey, being unexported. 21 | type ContextKey struct{} 22 | 23 | // ContextClientFunc is a func which tries to return an *http.Client 24 | // given a Context value. If it returns an error, the search stops 25 | // with that error. If it returns (nil, nil), the search continues 26 | // down the list of registered funcs. 27 | type ContextClientFunc func(context.Context) (*http.Client, error) 28 | 29 | var contextClientFuncs []ContextClientFunc 30 | 31 | func RegisterContextClientFunc(fn ContextClientFunc) { 32 | contextClientFuncs = append(contextClientFuncs, fn) 33 | } 34 | 35 | func ContextClient(ctx context.Context) (*http.Client, error) { 36 | if ctx != nil { 37 | if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok { 38 | return hc, nil 39 | } 40 | } 41 | for _, fn := range contextClientFuncs { 42 | c, err := fn(ctx) 43 | if err != nil { 44 | return nil, err 45 | } 46 | if c != nil { 47 | return c, nil 48 | } 49 | } 50 | return http.DefaultClient, nil 51 | } 52 | 53 | func ContextTransport(ctx context.Context) http.RoundTripper { 54 | hc, err := ContextClient(ctx) 55 | // This is a rare error case (somebody using nil on App Engine). 56 | if err != nil { 57 | return ErrorTransport{err} 58 | } 59 | return hc.Transport 60 | } 61 | 62 | // ErrorTransport returns the specified error on RoundTrip. 63 | // This RoundTripper should be used in rare error cases where 64 | // error handling can be postponed to response handling time. 65 | type ErrorTransport struct{ Err error } 66 | 67 | func (t ErrorTransport) RoundTrip(*http.Request) (*http.Response, error) { 68 | return nil, t.Err 69 | } 70 | -------------------------------------------------------------------------------- /vendor/golang.org/x/oauth2/token.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package oauth2 6 | 7 | import ( 8 | "net/http" 9 | "net/url" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "golang.org/x/net/context" 15 | "golang.org/x/oauth2/internal" 16 | ) 17 | 18 | // expiryDelta determines how earlier a token should be considered 19 | // expired than its actual expiration time. It is used to avoid late 20 | // expirations due to client-server time mismatches. 21 | const expiryDelta = 10 * time.Second 22 | 23 | // Token represents the crendentials used to authorize 24 | // the requests to access protected resources on the OAuth 2.0 25 | // provider's backend. 26 | // 27 | // Most users of this package should not access fields of Token 28 | // directly. They're exported mostly for use by related packages 29 | // implementing derivative OAuth2 flows. 30 | type Token struct { 31 | // AccessToken is the token that authorizes and authenticates 32 | // the requests. 33 | AccessToken string `json:"access_token"` 34 | 35 | // TokenType is the type of token. 36 | // The Type method returns either this or "Bearer", the default. 37 | TokenType string `json:"token_type,omitempty"` 38 | 39 | // RefreshToken is a token that's used by the application 40 | // (as opposed to the user) to refresh the access token 41 | // if it expires. 42 | RefreshToken string `json:"refresh_token,omitempty"` 43 | 44 | // Expiry is the optional expiration time of the access token. 45 | // 46 | // If zero, TokenSource implementations will reuse the same 47 | // token forever and RefreshToken or equivalent 48 | // mechanisms for that TokenSource will not be used. 49 | Expiry time.Time `json:"expiry,omitempty"` 50 | 51 | // raw optionally contains extra metadata from the server 52 | // when updating a token. 53 | raw interface{} 54 | } 55 | 56 | // Type returns t.TokenType if non-empty, else "Bearer". 57 | func (t *Token) Type() string { 58 | if strings.EqualFold(t.TokenType, "bearer") { 59 | return "Bearer" 60 | } 61 | if strings.EqualFold(t.TokenType, "mac") { 62 | return "MAC" 63 | } 64 | if strings.EqualFold(t.TokenType, "basic") { 65 | return "Basic" 66 | } 67 | if t.TokenType != "" { 68 | return t.TokenType 69 | } 70 | return "Bearer" 71 | } 72 | 73 | // SetAuthHeader sets the Authorization header to r using the access 74 | // token in t. 75 | // 76 | // This method is unnecessary when using Transport or an HTTP Client 77 | // returned by this package. 78 | func (t *Token) SetAuthHeader(r *http.Request) { 79 | r.Header.Set("Authorization", t.Type()+" "+t.AccessToken) 80 | } 81 | 82 | // WithExtra returns a new Token that's a clone of t, but using the 83 | // provided raw extra map. This is only intended for use by packages 84 | // implementing derivative OAuth2 flows. 85 | func (t *Token) WithExtra(extra interface{}) *Token { 86 | t2 := new(Token) 87 | *t2 = *t 88 | t2.raw = extra 89 | return t2 90 | } 91 | 92 | // Extra returns an extra field. 93 | // Extra fields are key-value pairs returned by the server as a 94 | // part of the token retrieval response. 95 | func (t *Token) Extra(key string) interface{} { 96 | if raw, ok := t.raw.(map[string]interface{}); ok { 97 | return raw[key] 98 | } 99 | 100 | vals, ok := t.raw.(url.Values) 101 | if !ok { 102 | return nil 103 | } 104 | 105 | v := vals.Get(key) 106 | switch s := strings.TrimSpace(v); strings.Count(s, ".") { 107 | case 0: // Contains no "."; try to parse as int 108 | if i, err := strconv.ParseInt(s, 10, 64); err == nil { 109 | return i 110 | } 111 | case 1: // Contains a single "."; try to parse as float 112 | if f, err := strconv.ParseFloat(s, 64); err == nil { 113 | return f 114 | } 115 | } 116 | 117 | return v 118 | } 119 | 120 | // expired reports whether the token is expired. 121 | // t must be non-nil. 122 | func (t *Token) expired() bool { 123 | if t.Expiry.IsZero() { 124 | return false 125 | } 126 | return t.Expiry.Add(-expiryDelta).Before(time.Now()) 127 | } 128 | 129 | // Valid reports whether t is non-nil, has an AccessToken, and is not expired. 130 | func (t *Token) Valid() bool { 131 | return t != nil && t.AccessToken != "" && !t.expired() 132 | } 133 | 134 | // tokenFromInternal maps an *internal.Token struct into 135 | // a *Token struct. 136 | func tokenFromInternal(t *internal.Token) *Token { 137 | if t == nil { 138 | return nil 139 | } 140 | return &Token{ 141 | AccessToken: t.AccessToken, 142 | TokenType: t.TokenType, 143 | RefreshToken: t.RefreshToken, 144 | Expiry: t.Expiry, 145 | raw: t.Raw, 146 | } 147 | } 148 | 149 | // retrieveToken takes a *Config and uses that to retrieve an *internal.Token. 150 | // This token is then mapped from *internal.Token into an *oauth2.Token which is returned along 151 | // with an error.. 152 | func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) { 153 | tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v) 154 | if err != nil { 155 | return nil, err 156 | } 157 | return tokenFromInternal(tk), nil 158 | } 159 | -------------------------------------------------------------------------------- /vendor/golang.org/x/oauth2/transport.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package oauth2 6 | 7 | import ( 8 | "errors" 9 | "io" 10 | "net/http" 11 | "sync" 12 | ) 13 | 14 | // Transport is an http.RoundTripper that makes OAuth 2.0 HTTP requests, 15 | // wrapping a base RoundTripper and adding an Authorization header 16 | // with a token from the supplied Sources. 17 | // 18 | // Transport is a low-level mechanism. Most code will use the 19 | // higher-level Config.Client method instead. 20 | type Transport struct { 21 | // Source supplies the token to add to outgoing requests' 22 | // Authorization headers. 23 | Source TokenSource 24 | 25 | // Base is the base RoundTripper used to make HTTP requests. 26 | // If nil, http.DefaultTransport is used. 27 | Base http.RoundTripper 28 | 29 | mu sync.Mutex // guards modReq 30 | modReq map[*http.Request]*http.Request // original -> modified 31 | } 32 | 33 | // RoundTrip authorizes and authenticates the request with an 34 | // access token. If no token exists or token is expired, 35 | // tries to refresh/fetch a new token. 36 | func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { 37 | if t.Source == nil { 38 | return nil, errors.New("oauth2: Transport's Source is nil") 39 | } 40 | token, err := t.Source.Token() 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | req2 := cloneRequest(req) // per RoundTripper contract 46 | token.SetAuthHeader(req2) 47 | t.setModReq(req, req2) 48 | res, err := t.base().RoundTrip(req2) 49 | if err != nil { 50 | t.setModReq(req, nil) 51 | return nil, err 52 | } 53 | res.Body = &onEOFReader{ 54 | rc: res.Body, 55 | fn: func() { t.setModReq(req, nil) }, 56 | } 57 | return res, nil 58 | } 59 | 60 | // CancelRequest cancels an in-flight request by closing its connection. 61 | func (t *Transport) CancelRequest(req *http.Request) { 62 | type canceler interface { 63 | CancelRequest(*http.Request) 64 | } 65 | if cr, ok := t.base().(canceler); ok { 66 | t.mu.Lock() 67 | modReq := t.modReq[req] 68 | delete(t.modReq, req) 69 | t.mu.Unlock() 70 | cr.CancelRequest(modReq) 71 | } 72 | } 73 | 74 | func (t *Transport) base() http.RoundTripper { 75 | if t.Base != nil { 76 | return t.Base 77 | } 78 | return http.DefaultTransport 79 | } 80 | 81 | func (t *Transport) setModReq(orig, mod *http.Request) { 82 | t.mu.Lock() 83 | defer t.mu.Unlock() 84 | if t.modReq == nil { 85 | t.modReq = make(map[*http.Request]*http.Request) 86 | } 87 | if mod == nil { 88 | delete(t.modReq, orig) 89 | } else { 90 | t.modReq[orig] = mod 91 | } 92 | } 93 | 94 | // cloneRequest returns a clone of the provided *http.Request. 95 | // The clone is a shallow copy of the struct and its Header map. 96 | func cloneRequest(r *http.Request) *http.Request { 97 | // shallow copy of the struct 98 | r2 := new(http.Request) 99 | *r2 = *r 100 | // deep copy of the Header 101 | r2.Header = make(http.Header, len(r.Header)) 102 | for k, s := range r.Header { 103 | r2.Header[k] = append([]string(nil), s...) 104 | } 105 | return r2 106 | } 107 | 108 | type onEOFReader struct { 109 | rc io.ReadCloser 110 | fn func() 111 | } 112 | 113 | func (r *onEOFReader) Read(p []byte) (n int, err error) { 114 | n, err = r.rc.Read(p) 115 | if err == io.EOF { 116 | r.runFunc() 117 | } 118 | return 119 | } 120 | 121 | func (r *onEOFReader) Close() error { 122 | err := r.rc.Close() 123 | r.runFunc() 124 | return err 125 | } 126 | 127 | func (r *onEOFReader) runFunc() { 128 | if fn := r.fn; fn != nil { 129 | fn() 130 | r.fn = nil 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "test", 4 | "package": [ 5 | { 6 | "checksumSHA1": "8s6lHDs2cnGicy0zrJW4ybIYtgI=", 7 | "path": "github.com/coreos/go-iptables/iptables", 8 | "revision": "5463fbac3bcc6b990663941c2e12660d19f6b36d", 9 | "revisionTime": "2016-09-07T22:01:51Z" 10 | }, 11 | { 12 | "checksumSHA1": "kWBL1Ax42U34toAV6zmv5jTHDgM=", 13 | "path": "github.com/digitalocean/go-metadata", 14 | "revision": "a6cf11fb1bf59df1976e32f3c5facf33f74fbfe9", 15 | "revisionTime": "2016-09-22T02:22:14Z" 16 | }, 17 | { 18 | "checksumSHA1": "E9pLUoXaHqo2CFk1m2WVcb0RnKU=", 19 | "path": "github.com/digitalocean/godo", 20 | "revision": "2ff8a02a86cd6918b384a5000ceebe886844fbce", 21 | "revisionTime": "2016-09-09T17:14:55Z" 22 | }, 23 | { 24 | "checksumSHA1": "yyAzHoiVLu+xywYI2BDyRq6sOqE=", 25 | "path": "github.com/google/go-querystring/query", 26 | "revision": "9235644dd9e52eeae6fa48efd539fdc351a0af53", 27 | "revisionTime": "2016-03-11T01:20:12Z" 28 | }, 29 | { 30 | "checksumSHA1": "GQ9bu6PuydK3Yor1JgtVKUfEJm8=", 31 | "path": "github.com/tent/http-link-go", 32 | "revision": "ac974c61c2f990f4115b119354b5e0b47550e888", 33 | "revisionTime": "2013-07-02T22:55:49Z" 34 | }, 35 | { 36 | "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", 37 | "path": "golang.org/x/net/context", 38 | "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", 39 | "revisionTime": "2016-10-13T03:31:11Z" 40 | }, 41 | { 42 | "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", 43 | "path": "golang.org/x/oauth2", 44 | "revision": "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5", 45 | "revisionTime": "2016-10-06T21:47:20Z" 46 | }, 47 | { 48 | "checksumSHA1": "D3v/aqfB9swlaZcSksCoF+lbOqo=", 49 | "path": "golang.org/x/oauth2/internal", 50 | "revision": "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5", 51 | "revisionTime": "2016-10-06T21:47:20Z" 52 | } 53 | ], 54 | "rootPath": "github.com/tam7t/droplan" 55 | } 56 | --------------------------------------------------------------------------------