mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
* Added Vultr provider * Fixed tests * Fixed CI build validation * Add unsupported features * Added #rtype_variations tags according to stackexchange.github.io/dnscontrol/adding-new-rtypes * Add title (for compatibility with #223) * Removed extra rtype_variations
This commit is contained in:
committed by
Tom Limoncelli
parent
002426ce4d
commit
23427516c0
@ -24,6 +24,7 @@ Currently supported DNS providers:
|
|||||||
- Name.com
|
- Name.com
|
||||||
- Route 53
|
- Route 53
|
||||||
- SoftLayer
|
- SoftLayer
|
||||||
|
- Vultr
|
||||||
|
|
||||||
At Stack Overflow, we use this system to manage hundreds of domains
|
At Stack Overflow, we use this system to manage hundreds of domains
|
||||||
and subdomains across multiple registrars and DNS providers.
|
and subdomains across multiple registrars and DNS providers.
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
<th class="rotate"><div><span>NS1</span></div></th>
|
<th class="rotate"><div><span>NS1</span></div></th>
|
||||||
<th class="rotate"><div><span>ROUTE53</span></div></th>
|
<th class="rotate"><div><span>ROUTE53</span></div></th>
|
||||||
<th class="rotate"><div><span>SOFTLAYER</span></div></th>
|
<th class="rotate"><div><span>SOFTLAYER</span></div></th>
|
||||||
|
<th class="rotate"><div><span>VULTR</span></div></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -59,6 +60,9 @@
|
|||||||
<td class="danger">
|
<td class="danger">
|
||||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="danger">
|
||||||
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="The provider has registrar capabilities to set nameservers for zones">Registrar</th>
|
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="The provider has registrar capabilities to set nameservers for zones">Registrar</th>
|
||||||
@ -98,6 +102,9 @@
|
|||||||
<td class="danger">
|
<td class="danger">
|
||||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="danger">
|
||||||
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Can manage and serve DNS zones">DNS Provider</th>
|
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Can manage and serve DNS zones">DNS Provider</th>
|
||||||
@ -137,6 +144,9 @@
|
|||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="success">
|
||||||
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports some kind of ALIAS, ANAME or flattened CNAME record type">ALIAS</th>
|
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports some kind of ALIAS, ANAME or flattened CNAME record type">ALIAS</th>
|
||||||
@ -162,6 +172,9 @@
|
|||||||
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
|
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td class="danger">
|
||||||
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Driver has explicitly implemented SRV record management">SRV</th>
|
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Driver has explicitly implemented SRV record management">SRV</th>
|
||||||
@ -197,6 +210,9 @@
|
|||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="success">
|
||||||
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports adding PTR records for reverse lookup zones">PTR</th>
|
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports adding PTR records for reverse lookup zones">PTR</th>
|
||||||
@ -228,6 +244,9 @@
|
|||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td class="danger">
|
||||||
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage CAA records">CAA</th>
|
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage CAA records">CAA</th>
|
||||||
@ -253,6 +272,9 @@
|
|||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td class="success">
|
||||||
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage TLSA records">TLSA</th>
|
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage TLSA records">TLSA</th>
|
||||||
@ -272,6 +294,9 @@
|
|||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td class="danger">
|
||||||
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This provider is recommended for use in 'dual hosting' scenarios. Usually this means the provider allows full control over the apex NS records">dual host</th>
|
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This provider is recommended for use in 'dual hosting' scenarios. Usually this means the provider allows full control over the apex NS records">dual host</th>
|
||||||
@ -305,6 +330,7 @@
|
|||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This means the provider can automatically create domains that do not currently exist on your account. The 'dnscontrol create-domains' command will initialize any missing domains">create-domains</th>
|
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This means the provider can automatically create domains that do not currently exist on your account. The 'dnscontrol create-domains' command will initialize any missing domains">create-domains</th>
|
||||||
@ -344,6 +370,9 @@
|
|||||||
<td class="danger">
|
<td class="danger">
|
||||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="success">
|
||||||
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="indicates you can use NO_PURGE macro to prevent deleting records not managed by dnscontrol. A few providers that generate the entire zone from scratch have a problem implementing this.">no_purge</th>
|
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="indicates you can use NO_PURGE macro to prevent deleting records not managed by dnscontrol. A few providers that generate the entire zone from scratch have a problem implementing this.">no_purge</th>
|
||||||
@ -383,6 +412,9 @@
|
|||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="success">
|
||||||
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
39
docs/_providers/vultr.md
Normal file
39
docs/_providers/vultr.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
name: Vultr
|
||||||
|
title: Vultr Provider
|
||||||
|
layout: default
|
||||||
|
jsId: VULTR
|
||||||
|
---
|
||||||
|
# Vultr Provider
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
In your providers config json file you must include a Vultr personal access token:
|
||||||
|
|
||||||
|
{% highlight json %}
|
||||||
|
{
|
||||||
|
"vultr":{
|
||||||
|
"token": "your-vultr-personal-access-token"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
This provider does not recognize any special metadata fields unique to Vultr.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Example javascript:
|
||||||
|
|
||||||
|
{% highlight js %}
|
||||||
|
var VULTR = NewDnsProvider("vultr", "VULTR");
|
||||||
|
|
||||||
|
D("example.tld", REG_DNSIMPLE, DnsProvider(VULTR),
|
||||||
|
A("test","1.2.3.4")
|
||||||
|
);
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
## Activation
|
||||||
|
|
||||||
|
Vultr depends on a Vultr personal access token.
|
@ -66,6 +66,7 @@ Maintainers of contributed providers:
|
|||||||
* namecheap @captncraig
|
* namecheap @captncraig
|
||||||
* ns1 @captncraig
|
* ns1 @captncraig
|
||||||
* OVH @Oprax
|
* OVH @Oprax
|
||||||
|
* Vultr @geek1011
|
||||||
|
|
||||||
### Requested providers
|
### Requested providers
|
||||||
|
|
||||||
|
@ -62,5 +62,9 @@
|
|||||||
"domain": "$SL_DOMAIN",
|
"domain": "$SL_DOMAIN",
|
||||||
"username": "$SL_USERNAME",
|
"username": "$SL_USERNAME",
|
||||||
"api_key": "$SL_API_KEY"
|
"api_key": "$SL_API_KEY"
|
||||||
|
},
|
||||||
|
"VULTR": {
|
||||||
|
"token": "$VULTR_TOKEN",
|
||||||
|
"domain": "$VULTR_DOMAIN"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,4 +15,5 @@ import (
|
|||||||
_ "github.com/StackExchange/dnscontrol/providers/ns1"
|
_ "github.com/StackExchange/dnscontrol/providers/ns1"
|
||||||
_ "github.com/StackExchange/dnscontrol/providers/route53"
|
_ "github.com/StackExchange/dnscontrol/providers/route53"
|
||||||
_ "github.com/StackExchange/dnscontrol/providers/softlayer"
|
_ "github.com/StackExchange/dnscontrol/providers/softlayer"
|
||||||
|
_ "github.com/StackExchange/dnscontrol/providers/vultr"
|
||||||
)
|
)
|
||||||
|
73
providers/vultr/convert_test.go
Normal file
73
providers/vultr/convert_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package vultr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
vultr "github.com/JamesClonk/vultr/lib"
|
||||||
|
"github.com/StackExchange/dnscontrol/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConversion(t *testing.T) {
|
||||||
|
dc := &models.DomainConfig{
|
||||||
|
Name: "example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
records := []*vultr.DNSRecord{
|
||||||
|
{
|
||||||
|
Type: "A",
|
||||||
|
Name: "",
|
||||||
|
Data: "127.0.0.1",
|
||||||
|
TTL: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "CNAME",
|
||||||
|
Name: "*",
|
||||||
|
Data: "example.com",
|
||||||
|
TTL: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "SRV",
|
||||||
|
Name: "_ssh_.tcp",
|
||||||
|
Data: "5 22 ssh.example.com",
|
||||||
|
Priority: 5,
|
||||||
|
TTL: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "MX",
|
||||||
|
Name: "",
|
||||||
|
Data: "mail.example.com",
|
||||||
|
TTL: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "NS",
|
||||||
|
Name: "",
|
||||||
|
Data: "ns1.example.net",
|
||||||
|
TTL: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "TXT",
|
||||||
|
Name: "test",
|
||||||
|
Data: "\"testasd asda sdas dasd\"",
|
||||||
|
TTL: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "CAA",
|
||||||
|
Name: "testasd",
|
||||||
|
Data: "0 issue \"test.example.net\"",
|
||||||
|
TTL: 300,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, record := range records {
|
||||||
|
rc, err := toRecordConfig(dc, record)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error converting Vultr record", record)
|
||||||
|
}
|
||||||
|
|
||||||
|
converted := toVultrRecord(dc, rc)
|
||||||
|
|
||||||
|
if converted.Type != record.Type || converted.Name != record.Name || converted.Data != record.Data || converted.Priority != record.Priority || converted.TTL != record.TTL {
|
||||||
|
t.Error("Vultr record conversion mismatch", record, rc, converted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
333
providers/vultr/vultr.go
Normal file
333
providers/vultr/vultr.go
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
package vultr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/StackExchange/dnscontrol/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/providers"
|
||||||
|
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||||
|
"github.com/miekg/dns/dnsutil"
|
||||||
|
|
||||||
|
vultr "github.com/JamesClonk/vultr/lib"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Vultr API DNS provider:
|
||||||
|
|
||||||
|
Info required in `creds.json`:
|
||||||
|
- token
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
var docNotes = providers.DocumentationNotes{
|
||||||
|
providers.DocCreateDomains: providers.Can(),
|
||||||
|
providers.DocOfficiallySupported: providers.Cannot(),
|
||||||
|
providers.CanUseAlias: providers.Cannot(),
|
||||||
|
providers.CanUseTLSA: providers.Cannot(),
|
||||||
|
providers.CanUsePTR: providers.Cannot(),
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
providers.RegisterDomainServiceProviderType("VULTR", NewVultr, providers.CanUseSRV, providers.CanUseCAA, docNotes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VultrApi represents the Vultr DNSServiceProvider
|
||||||
|
type VultrApi struct {
|
||||||
|
client *vultr.Client
|
||||||
|
token string
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultNS are the default nameservers for Vultr
|
||||||
|
var defaultNS = []string{
|
||||||
|
"ns1.vultr.com",
|
||||||
|
"ns2.vultr.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVultr initializes a Vultr DNSServiceProvider
|
||||||
|
func NewVultr(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||||
|
api := &VultrApi{
|
||||||
|
token: m["token"],
|
||||||
|
}
|
||||||
|
|
||||||
|
if api.token == "" {
|
||||||
|
return nil, fmt.Errorf("Vultr API token is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
api.client = vultr.NewClient(api.token, nil)
|
||||||
|
|
||||||
|
// Validate token
|
||||||
|
_, err := api.client.GetAccountInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return api, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomainCorrections gets the corrections for a DomainConfig
|
||||||
|
func (api *VultrApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||||
|
dc.Punycode()
|
||||||
|
|
||||||
|
ok, err := api.isDomainInAccount(dc.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a domain in the Vultr account", dc.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
records, err := api.client.GetDNSRecords(dc.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
curRecords := make([]*models.RecordConfig, len(records))
|
||||||
|
for i := range records {
|
||||||
|
r, err := toRecordConfig(dc, &records[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
curRecords[i] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
differ := diff.New(dc)
|
||||||
|
_, create, delete, modify := differ.IncrementalDiff(curRecords)
|
||||||
|
|
||||||
|
corrections := []*models.Correction{}
|
||||||
|
|
||||||
|
for _, mod := range delete {
|
||||||
|
id := mod.Existing.Original.(*vultr.DNSRecord).RecordID
|
||||||
|
corrections = append(corrections, &models.Correction{
|
||||||
|
Msg: fmt.Sprintf("%s; Vultr RecordID: %v", mod.String(), id),
|
||||||
|
F: func() error {
|
||||||
|
return api.client.DeleteDNSRecord(dc.Name, id)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mod := range create {
|
||||||
|
r := toVultrRecord(dc, mod.Desired)
|
||||||
|
corrections = append(corrections, &models.Correction{
|
||||||
|
Msg: mod.String(),
|
||||||
|
F: func() error {
|
||||||
|
return api.client.CreateDNSRecord(dc.Name, r.Name, r.Type, r.Data, r.Priority, r.TTL)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mod := range modify {
|
||||||
|
id := mod.Existing.Original.(*vultr.DNSRecord).RecordID
|
||||||
|
r := toVultrRecord(dc, mod.Desired)
|
||||||
|
r.RecordID = id
|
||||||
|
corrections = append(corrections, &models.Correction{
|
||||||
|
Msg: fmt.Sprintf("%s; Vultr RecordID: %v", mod.String(), id),
|
||||||
|
F: func() error {
|
||||||
|
return api.client.UpdateDNSRecord(dc.Name, *r)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return corrections, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNameservers gets the Vultr nameservers for a domain
|
||||||
|
func (api *VultrApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||||
|
return models.StringsToNameservers(defaultNS), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnsureDomainExists adds a domain to the Vutr DNS service if it does not exist
|
||||||
|
func (api *VultrApi) EnsureDomainExists(domain string) error {
|
||||||
|
ok, err := api.isDomainInAccount(domain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
// Vultr requires an initial IP, use a dummy one
|
||||||
|
err := api.client.CreateDNSDomain(domain, "127.0.0.1")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := api.isDomainInAccount(domain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Unexpected error adding domain %s to Vultr account", domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *VultrApi) isDomainInAccount(domain string) (bool, error) {
|
||||||
|
domains, err := api.client.GetDNSDomains()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var vd *vultr.DNSDomain
|
||||||
|
for _, d := range domains {
|
||||||
|
if d.Domain == domain {
|
||||||
|
vd = &d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vd == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toRecordConfig converts a Vultr DNSRecord to a RecordConfig #rtype_variations
|
||||||
|
func toRecordConfig(dc *models.DomainConfig, r *vultr.DNSRecord) (*models.RecordConfig, error) {
|
||||||
|
// Turns r.Name into a FQDN
|
||||||
|
// Vultr uses "" as the apex domain, instead of "@", and this handles it fine.
|
||||||
|
name := dnsutil.AddOrigin(r.Name, dc.Name)
|
||||||
|
|
||||||
|
data := r.Data
|
||||||
|
// Make target into a FQDN if it is a CNAME, NS, MX, or SRV
|
||||||
|
if r.Type == "CNAME" || r.Type == "NS" || r.Type == "MX" {
|
||||||
|
if !strings.HasSuffix(data, ".") {
|
||||||
|
data = data + "."
|
||||||
|
}
|
||||||
|
data = dnsutil.AddOrigin(data, dc.Name)
|
||||||
|
}
|
||||||
|
// Remove quotes if it is a TXT
|
||||||
|
if r.Type == "TXT" {
|
||||||
|
if !strings.HasPrefix(data, `"`) || !strings.HasSuffix(data, `"`) {
|
||||||
|
return nil, errors.New("Unexpected lack of quotes in TXT record from Vultr")
|
||||||
|
}
|
||||||
|
data = data[1 : len(data)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
rc := &models.RecordConfig{
|
||||||
|
NameFQDN: name,
|
||||||
|
Type: r.Type,
|
||||||
|
Target: data,
|
||||||
|
TTL: uint32(r.TTL),
|
||||||
|
Original: r,
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Type == "MX" {
|
||||||
|
rc.MxPreference = uint16(r.Priority)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Type == "SRV" {
|
||||||
|
rc.SrvPriority = uint16(r.Priority)
|
||||||
|
|
||||||
|
// Vultr returns in the format "[weight] [port] [target]"
|
||||||
|
splitData := strings.SplitN(rc.Target, " ", 3)
|
||||||
|
if len(splitData) != 3 {
|
||||||
|
return nil, fmt.Errorf("Unexpected data for SRV record returned by Vultr")
|
||||||
|
}
|
||||||
|
|
||||||
|
weight, err := strconv.ParseUint(splitData[0], 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rc.SrvWeight = uint16(weight)
|
||||||
|
|
||||||
|
port, err := strconv.ParseUint(splitData[1], 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rc.SrvPort = uint16(port)
|
||||||
|
|
||||||
|
target := splitData[2]
|
||||||
|
if !strings.HasSuffix(target, ".") {
|
||||||
|
target = target + "."
|
||||||
|
}
|
||||||
|
rc.Target = dnsutil.AddOrigin(target, dc.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Type == "CAA" {
|
||||||
|
// Vultr returns in the format "[flag] [tag] [value]"
|
||||||
|
splitData := strings.SplitN(rc.Target, " ", 3)
|
||||||
|
if len(splitData) != 3 {
|
||||||
|
return nil, fmt.Errorf("Unexpected data for CAA record returned by Vultr")
|
||||||
|
}
|
||||||
|
|
||||||
|
flag, err := strconv.ParseUint(splitData[0], 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rc.CaaFlag = uint8(flag)
|
||||||
|
|
||||||
|
rc.CaaTag = splitData[1]
|
||||||
|
|
||||||
|
value := splitData[2]
|
||||||
|
if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
|
||||||
|
value = value[1 : len(value)-1]
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(value, `'`) && strings.HasSuffix(value, `'`) {
|
||||||
|
value = value[1 : len(value)-1]
|
||||||
|
}
|
||||||
|
rc.Target = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toVultrRecord converts a RecordConfig converted by toRecordConfig back to a Vultr DNSRecord #rtype_variations
|
||||||
|
func toVultrRecord(dc *models.DomainConfig, rc *models.RecordConfig) *vultr.DNSRecord {
|
||||||
|
name := dnsutil.TrimDomainName(rc.NameFQDN, dc.Name)
|
||||||
|
|
||||||
|
// Vultr uses a blank string to represent the apex domain
|
||||||
|
if name == "@" {
|
||||||
|
name = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
data := rc.Target
|
||||||
|
|
||||||
|
// Vultr does not use a period suffix for the server for CNAME, NS, or MX
|
||||||
|
if strings.HasSuffix(data, ".") {
|
||||||
|
data = data[:len(data)-1]
|
||||||
|
}
|
||||||
|
// Vultr needs TXT record in quotes
|
||||||
|
if rc.Type == "TXT" {
|
||||||
|
data = fmt.Sprintf(`"%s"`, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
priority := 0
|
||||||
|
|
||||||
|
if rc.Type == "MX" {
|
||||||
|
priority = int(rc.MxPreference)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rc.Type == "SRV" {
|
||||||
|
priority = int(rc.SrvPriority)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &vultr.DNSRecord{
|
||||||
|
Type: rc.Type,
|
||||||
|
Name: name,
|
||||||
|
Data: data,
|
||||||
|
TTL: int(rc.TTL),
|
||||||
|
Priority: priority,
|
||||||
|
}
|
||||||
|
|
||||||
|
if rc.Type == "SRV" {
|
||||||
|
target := rc.Target
|
||||||
|
if strings.HasSuffix(target, ".") {
|
||||||
|
target = target[:len(target)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Data = fmt.Sprintf("%v %v %s", rc.SrvWeight, rc.SrvPort, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rc.Type == "CAA" {
|
||||||
|
r.Data = fmt.Sprintf(`%v %s "%s"`, rc.CaaFlag, rc.CaaTag, rc.Target)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
22
vendor/github.com/JamesClonk/vultr/LICENSE
generated
vendored
Normal file
22
vendor/github.com/JamesClonk/vultr/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Fabio Berchtold
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
71
vendor/github.com/JamesClonk/vultr/lib/account_info.go
generated
vendored
Normal file
71
vendor/github.com/JamesClonk/vultr/lib/account_info.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccountInfo of Vultr account
|
||||||
|
type AccountInfo struct {
|
||||||
|
Balance float64 `json:"balance"`
|
||||||
|
PendingCharges float64 `json:"pending_charges"`
|
||||||
|
LastPaymentDate string `json:"last_payment_date"`
|
||||||
|
LastPaymentAmount float64 `json:"last_payment_amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountInfo retrieves the Vultr account information about current balance, pending charges, etc..
|
||||||
|
func (c *Client) GetAccountInfo() (info AccountInfo, err error) {
|
||||||
|
if err := c.get(`account/info`, &info); err != nil {
|
||||||
|
return AccountInfo{}, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaller on AccountInfo.
|
||||||
|
// This is needed because the Vultr API is inconsistent in it's JSON responses for account info.
|
||||||
|
// Some fields can change type, from JSON number to JSON string and vice-versa.
|
||||||
|
func (a *AccountInfo) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
if a == nil {
|
||||||
|
*a = AccountInfo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields map[string]interface{}
|
||||||
|
if err := json.Unmarshal(data, &fields); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := fmt.Sprintf("%v", fields["balance"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
b, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.Balance = b
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["pending_charges"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
pc, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.PendingCharges = pc
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["last_payment_amount"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
lpa, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.LastPaymentAmount = lpa
|
||||||
|
|
||||||
|
a.LastPaymentDate = fmt.Sprintf("%v", fields["last_payment_date"])
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
38
vendor/github.com/JamesClonk/vultr/lib/applications.go
generated
vendored
Normal file
38
vendor/github.com/JamesClonk/vultr/lib/applications.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Application on Vultr
|
||||||
|
type Application struct {
|
||||||
|
ID string `json:"APPID"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ShortName string `json:"short_name"`
|
||||||
|
DeployName string `json:"deploy_name"`
|
||||||
|
Surcharge float64 `json:"surcharge"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type applications []Application
|
||||||
|
|
||||||
|
func (s applications) Len() int { return len(s) }
|
||||||
|
func (s applications) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s applications) Less(i, j int) bool {
|
||||||
|
return strings.ToLower(s[i].Name) < strings.ToLower(s[j].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetApplications returns a list of all available applications on Vultr
|
||||||
|
func (c *Client) GetApplications() ([]Application, error) {
|
||||||
|
var appMap map[string]Application
|
||||||
|
if err := c.get(`app/list`, &appMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var appList []Application
|
||||||
|
for _, app := range appMap {
|
||||||
|
appList = append(appList, app)
|
||||||
|
}
|
||||||
|
sort.Sort(applications(appList))
|
||||||
|
return appList, nil
|
||||||
|
}
|
210
vendor/github.com/JamesClonk/vultr/lib/block_storage.go
generated
vendored
Normal file
210
vendor/github.com/JamesClonk/vultr/lib/block_storage.go
generated
vendored
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockStorage on Vultr account
|
||||||
|
type BlockStorage struct {
|
||||||
|
ID string `json:"SUBID,string"`
|
||||||
|
Name string `json:"label"`
|
||||||
|
RegionID int `json:"DCID,string"`
|
||||||
|
SizeGB int `json:"size_gb,string"`
|
||||||
|
Created string `json:"date_created"`
|
||||||
|
Cost string `json:"cost_per_month"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
AttachedTo string `json:"attached_to_SUBID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type blockstorages []BlockStorage
|
||||||
|
|
||||||
|
func (b blockstorages) Len() int { return len(b) }
|
||||||
|
func (b blockstorages) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||||
|
func (b blockstorages) Less(i, j int) bool {
|
||||||
|
// sort order: name, size, status
|
||||||
|
if strings.ToLower(b[i].Name) < strings.ToLower(b[j].Name) {
|
||||||
|
return true
|
||||||
|
} else if strings.ToLower(b[i].Name) > strings.ToLower(b[j].Name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if b[i].SizeGB < b[j].SizeGB {
|
||||||
|
return true
|
||||||
|
} else if b[i].SizeGB > b[j].SizeGB {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return b[i].Status < b[j].Status
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaller on BlockStorage.
|
||||||
|
// This is needed because the Vultr API is inconsistent in it's JSON responses.
|
||||||
|
// Some fields can change type, from JSON number to JSON string and vice-versa.
|
||||||
|
func (b *BlockStorage) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
if b == nil {
|
||||||
|
*b = BlockStorage{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields map[string]interface{}
|
||||||
|
if err := json.Unmarshal(data, &fields); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := fmt.Sprintf("%v", fields["SUBID"])
|
||||||
|
if len(value) == 0 || value == "<nil>" || value == "0" {
|
||||||
|
b.ID = ""
|
||||||
|
} else {
|
||||||
|
id, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.ID = strconv.FormatFloat(id, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["DCID"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
region, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.RegionID = int(region)
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["size_gb"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
size, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.SizeGB = int(size)
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["attached_to_SUBID"])
|
||||||
|
if len(value) == 0 || value == "<nil>" || value == "0" {
|
||||||
|
b.AttachedTo = ""
|
||||||
|
} else {
|
||||||
|
attached, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.AttachedTo = strconv.FormatFloat(attached, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Name = fmt.Sprintf("%v", fields["label"])
|
||||||
|
b.Created = fmt.Sprintf("%v", fields["date_created"])
|
||||||
|
b.Status = fmt.Sprintf("%v", fields["status"])
|
||||||
|
b.Cost = fmt.Sprintf("%v", fields["cost_per_month"])
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockStorages returns a list of all active block storages on Vultr account
|
||||||
|
func (c *Client) GetBlockStorages() (storages []BlockStorage, err error) {
|
||||||
|
if err := c.get(`block/list`, &storages); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Sort(blockstorages(storages))
|
||||||
|
return storages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockStorage returns block storage with given ID
|
||||||
|
func (c *Client) GetBlockStorage(id string) (BlockStorage, error) {
|
||||||
|
storages, err := c.GetBlockStorages()
|
||||||
|
if err != nil {
|
||||||
|
return BlockStorage{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range storages {
|
||||||
|
if s.ID == id {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BlockStorage{}, fmt.Errorf("BlockStorage with ID %v not found", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBlockStorage creates a new block storage on Vultr account
|
||||||
|
func (c *Client) CreateBlockStorage(name string, regionID, size int) (BlockStorage, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"label": {name},
|
||||||
|
"DCID": {fmt.Sprintf("%v", regionID)},
|
||||||
|
"size_gb": {fmt.Sprintf("%v", size)},
|
||||||
|
}
|
||||||
|
|
||||||
|
var storage BlockStorage
|
||||||
|
if err := c.post(`block/create`, values, &storage); err != nil {
|
||||||
|
return BlockStorage{}, err
|
||||||
|
}
|
||||||
|
storage.RegionID = regionID
|
||||||
|
storage.Name = name
|
||||||
|
storage.SizeGB = size
|
||||||
|
|
||||||
|
return storage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResizeBlockStorage resizes an existing block storage
|
||||||
|
func (c *Client) ResizeBlockStorage(id string, size int) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"size_gb": {fmt.Sprintf("%v", size)},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`block/resize`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabelBlockStorage changes the label on an existing block storage
|
||||||
|
func (c *Client) LabelBlockStorage(id, name string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"label": {name},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`block/label_set`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachBlockStorage attaches block storage to an existing virtual machine
|
||||||
|
func (c *Client) AttachBlockStorage(id, serverID string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"attach_to_SUBID": {serverID},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`block/attach`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetachBlockStorage detaches block storage from virtual machine
|
||||||
|
func (c *Client) DetachBlockStorage(id string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`block/detach`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBlockStorage deletes an existing block storage
|
||||||
|
func (c *Client) DeleteBlockStorage(id string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`block/delete`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
249
vendor/github.com/JamesClonk/vultr/lib/client.go
generated
vendored
Normal file
249
vendor/github.com/JamesClonk/vultr/lib/client.go
generated
vendored
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/juju/ratelimit"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Version of this libary
|
||||||
|
Version = "1.13.0"
|
||||||
|
|
||||||
|
// APIVersion of Vultr
|
||||||
|
APIVersion = "v1"
|
||||||
|
|
||||||
|
// DefaultEndpoint to be used
|
||||||
|
DefaultEndpoint = "https://api.vultr.com/"
|
||||||
|
|
||||||
|
mediaType = "application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// retryableStatusCodes are API response status codes that indicate that
|
||||||
|
// the failed request can be retried without further actions.
|
||||||
|
var retryableStatusCodes = map[int]struct{}{
|
||||||
|
503: {}, // Rate limit hit
|
||||||
|
500: {}, // Internal server error. Try again at a later time.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client represents the Vultr API client
|
||||||
|
type Client struct {
|
||||||
|
// HTTP client for communication with the Vultr API
|
||||||
|
client *http.Client
|
||||||
|
|
||||||
|
// User agent for HTTP client
|
||||||
|
UserAgent string
|
||||||
|
|
||||||
|
// Endpoint URL for API requests
|
||||||
|
Endpoint *url.URL
|
||||||
|
|
||||||
|
// API key for accessing the Vultr API
|
||||||
|
APIKey string
|
||||||
|
|
||||||
|
// Max. number of request attempts
|
||||||
|
MaxAttempts int
|
||||||
|
|
||||||
|
// Throttling struct
|
||||||
|
bucket *ratelimit.Bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options represents optional settings and flags that can be passed to NewClient
|
||||||
|
type Options struct {
|
||||||
|
// HTTP client for communication with the Vultr API
|
||||||
|
HTTPClient *http.Client
|
||||||
|
|
||||||
|
// User agent for HTTP client
|
||||||
|
UserAgent string
|
||||||
|
|
||||||
|
// Endpoint URL for API requests
|
||||||
|
Endpoint string
|
||||||
|
|
||||||
|
// API rate limitation, calls per duration
|
||||||
|
RateLimitation time.Duration
|
||||||
|
|
||||||
|
// Max. number of times to retry API calls
|
||||||
|
MaxRetries int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates new Vultr API client. Options are optional and can be nil.
|
||||||
|
func NewClient(apiKey string, options *Options) *Client {
|
||||||
|
userAgent := "vultr-go/" + Version
|
||||||
|
transport := &http.Transport{
|
||||||
|
TLSNextProto: make(map[string]func(string, *tls.Conn) http.RoundTripper),
|
||||||
|
}
|
||||||
|
client := http.DefaultClient
|
||||||
|
client.Transport = transport
|
||||||
|
endpoint, _ := url.Parse(DefaultEndpoint)
|
||||||
|
rate := 505 * time.Millisecond
|
||||||
|
attempts := 1
|
||||||
|
|
||||||
|
if options != nil {
|
||||||
|
if options.HTTPClient != nil {
|
||||||
|
client = options.HTTPClient
|
||||||
|
}
|
||||||
|
if options.UserAgent != "" {
|
||||||
|
userAgent = options.UserAgent
|
||||||
|
}
|
||||||
|
if options.Endpoint != "" {
|
||||||
|
endpoint, _ = url.Parse(options.Endpoint)
|
||||||
|
}
|
||||||
|
if options.RateLimitation != 0 {
|
||||||
|
rate = options.RateLimitation
|
||||||
|
}
|
||||||
|
if options.MaxRetries != 0 {
|
||||||
|
attempts = options.MaxRetries + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
UserAgent: userAgent,
|
||||||
|
client: client,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
APIKey: apiKey,
|
||||||
|
MaxAttempts: attempts,
|
||||||
|
bucket: ratelimit.NewBucket(rate, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiPath(path string) string {
|
||||||
|
return fmt.Sprintf("/%s/%s", APIVersion, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiKeyPath(path, apiKey string) string {
|
||||||
|
if strings.Contains(path, "?") {
|
||||||
|
return path + "&api_key=" + apiKey
|
||||||
|
}
|
||||||
|
return path + "?api_key=" + apiKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) get(path string, data interface{}) error {
|
||||||
|
req, err := c.newRequest("GET", apiPath(path), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.do(req, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) post(path string, values url.Values, data interface{}) error {
|
||||||
|
req, err := c.newRequest("POST", apiPath(path), strings.NewReader(values.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.do(req, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) newRequest(method string, path string, body io.Reader) (*http.Request, error) {
|
||||||
|
relPath, err := url.Parse(apiKeyPath(path, c.APIKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
url := c.Endpoint.ResolveReference(relPath)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, url.String(), body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("User-Agent", c.UserAgent)
|
||||||
|
req.Header.Add("Accept", mediaType)
|
||||||
|
|
||||||
|
if req.Method == "POST" {
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) do(req *http.Request, data interface{}) error {
|
||||||
|
// Throttle http requests to avoid hitting Vultr's API rate-limit
|
||||||
|
c.bucket.Wait(1)
|
||||||
|
|
||||||
|
// Request body gets drained on each read so we
|
||||||
|
// need to save it's content for retrying requests
|
||||||
|
var err error
|
||||||
|
var requestBody []byte
|
||||||
|
if req.Body != nil {
|
||||||
|
requestBody, err = ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error reading request body: %v", err)
|
||||||
|
}
|
||||||
|
req.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiError error
|
||||||
|
for tryCount := 1; tryCount <= c.MaxAttempts; tryCount++ {
|
||||||
|
// Restore request body to the original state
|
||||||
|
if requestBody != nil {
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewBuffer(requestBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
if data != nil {
|
||||||
|
// avoid unmarshalling problem because Vultr API returns
|
||||||
|
// empty array instead of empty map when it shouldn't!
|
||||||
|
if string(body) == `[]` {
|
||||||
|
data = nil
|
||||||
|
} else {
|
||||||
|
if err := json.Unmarshal(body, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
apiError = errors.New(string(body))
|
||||||
|
if !isCodeRetryable(resp.StatusCode) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
delay := backoffDuration(tryCount)
|
||||||
|
time.Sleep(delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiError
|
||||||
|
}
|
||||||
|
|
||||||
|
// backoffDuration returns the duration to wait before retrying the request.
|
||||||
|
// Duration is an exponential function of the retry count with a jitter of ~0-30%.
|
||||||
|
func backoffDuration(retryCount int) time.Duration {
|
||||||
|
// Upper limit of delay at ~1 minute
|
||||||
|
if retryCount > 7 {
|
||||||
|
retryCount = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
delay := (1 << uint(retryCount)) * (rand.Intn(150) + 500)
|
||||||
|
return time.Duration(delay) * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
// isCodeRetryable returns true if the given status code means that we should retry.
|
||||||
|
func isCodeRetryable(statusCode int) bool {
|
||||||
|
if _, ok := retryableStatusCodes[statusCode]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
150
vendor/github.com/JamesClonk/vultr/lib/dns.go
generated
vendored
Normal file
150
vendor/github.com/JamesClonk/vultr/lib/dns.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNSDomain represents a DNS domain on Vultr
|
||||||
|
type DNSDomain struct {
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Created string `json:"date_created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dnsdomains []DNSDomain
|
||||||
|
|
||||||
|
func (d dnsdomains) Len() int { return len(d) }
|
||||||
|
func (d dnsdomains) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
|
||||||
|
func (d dnsdomains) Less(i, j int) bool {
|
||||||
|
return strings.ToLower(d[i].Domain) < strings.ToLower(d[j].Domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSRecord represents a DNS record on Vultr
|
||||||
|
type DNSRecord struct {
|
||||||
|
RecordID int `json:"RECORDID"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dnsrecords []DNSRecord
|
||||||
|
|
||||||
|
func (d dnsrecords) Len() int { return len(d) }
|
||||||
|
func (d dnsrecords) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
|
||||||
|
func (d dnsrecords) Less(i, j int) bool {
|
||||||
|
// sort order: type, data, name
|
||||||
|
if d[i].Type < d[j].Type {
|
||||||
|
return true
|
||||||
|
} else if d[i].Type > d[j].Type {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if d[i].Data < d[j].Data {
|
||||||
|
return true
|
||||||
|
} else if d[i].Data > d[j].Data {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.ToLower(d[i].Name) < strings.ToLower(d[j].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDNSDomains returns a list of available domains on Vultr account
|
||||||
|
func (c *Client) GetDNSDomains() (domains []DNSDomain, err error) {
|
||||||
|
if err := c.get(`dns/list`, &domains); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Sort(dnsdomains(domains))
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDNSRecords returns a list of all DNS records of a particular domain
|
||||||
|
func (c *Client) GetDNSRecords(domain string) (records []DNSRecord, err error) {
|
||||||
|
if err := c.get(`dns/records?domain=`+domain, &records); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Sort(dnsrecords(records))
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDNSDomain creates a new DNS domain name on Vultr
|
||||||
|
func (c *Client) CreateDNSDomain(domain, serverIP string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"domain": {domain},
|
||||||
|
"serverip": {serverIP},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`dns/create_domain`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDNSDomain deletes an existing DNS domain name
|
||||||
|
func (c *Client) DeleteDNSDomain(domain string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"domain": {domain},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`dns/delete_domain`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDNSRecord creates a new DNS record
|
||||||
|
func (c *Client) CreateDNSRecord(domain, name, rtype, data string, priority, ttl int) error {
|
||||||
|
values := url.Values{
|
||||||
|
"domain": {domain},
|
||||||
|
"name": {name},
|
||||||
|
"type": {rtype},
|
||||||
|
"data": {data},
|
||||||
|
"priority": {fmt.Sprintf("%v", priority)},
|
||||||
|
"ttl": {fmt.Sprintf("%v", ttl)},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`dns/create_record`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDNSRecord updates an existing DNS record
|
||||||
|
func (c *Client) UpdateDNSRecord(domain string, dnsrecord DNSRecord) error {
|
||||||
|
values := url.Values{
|
||||||
|
"domain": {domain},
|
||||||
|
"RECORDID": {fmt.Sprintf("%v", dnsrecord.RecordID)},
|
||||||
|
}
|
||||||
|
|
||||||
|
if dnsrecord.Name != "" {
|
||||||
|
values.Add("name", dnsrecord.Name)
|
||||||
|
}
|
||||||
|
if dnsrecord.Data != "" {
|
||||||
|
values.Add("data", dnsrecord.Data)
|
||||||
|
}
|
||||||
|
if dnsrecord.Priority != 0 {
|
||||||
|
values.Add("priority", fmt.Sprintf("%v", dnsrecord.Priority))
|
||||||
|
}
|
||||||
|
if dnsrecord.TTL != 0 {
|
||||||
|
values.Add("ttl", fmt.Sprintf("%v", dnsrecord.TTL))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`dns/update_record`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDNSRecord deletes an existing DNS record
|
||||||
|
func (c *Client) DeleteDNSRecord(domain string, recordID int) error {
|
||||||
|
values := url.Values{
|
||||||
|
"domain": {domain},
|
||||||
|
"RECORDID": {fmt.Sprintf("%v", recordID)},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`dns/delete_record`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
248
vendor/github.com/JamesClonk/vultr/lib/firewall.go
generated
vendored
Normal file
248
vendor/github.com/JamesClonk/vultr/lib/firewall.go
generated
vendored
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FirewallGroup represents a firewall group on Vultr
|
||||||
|
type FirewallGroup struct {
|
||||||
|
ID string `json:"FIREWALLGROUPID"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Created string `json:"date_created"`
|
||||||
|
Modified string `json:"date_modified"`
|
||||||
|
InstanceCount int `json:"instance_count"`
|
||||||
|
RuleCount int `json:"rule_count"`
|
||||||
|
MaxRuleCount int `json:"max_rule_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirewallRule represents a firewall rule on Vultr
|
||||||
|
type FirewallRule struct {
|
||||||
|
RuleNumber int `json:"rulenumber"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Port string `json:"port"`
|
||||||
|
Network *net.IPNet
|
||||||
|
}
|
||||||
|
|
||||||
|
type firewallGroups []FirewallGroup
|
||||||
|
|
||||||
|
func (f firewallGroups) Len() int { return len(f) }
|
||||||
|
func (f firewallGroups) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||||
|
func (f firewallGroups) Less(i, j int) bool {
|
||||||
|
// sort order: description
|
||||||
|
return strings.ToLower(f[i].Description) < strings.ToLower(f[j].Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
type firewallRules []FirewallRule
|
||||||
|
|
||||||
|
func (r firewallRules) Len() int { return len(r) }
|
||||||
|
func (r firewallRules) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||||
|
func (r firewallRules) Less(i, j int) bool {
|
||||||
|
// sort order: rule number
|
||||||
|
return r[i].RuleNumber < r[j].RuleNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaller on FirewallRule.
|
||||||
|
// This is needed because the Vultr API is inconsistent in it's JSON responses.
|
||||||
|
// Some fields can change type, from JSON number to JSON string and vice-versa.
|
||||||
|
func (r *FirewallRule) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
if r == nil {
|
||||||
|
*r = FirewallRule{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields map[string]interface{}
|
||||||
|
if err := json.Unmarshal(data, &fields); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := fmt.Sprintf("%v", fields["rulenumber"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
number, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.RuleNumber = int(number)
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["subnet_size"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
subnetSize, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Action = fmt.Sprintf("%v", fields["action"])
|
||||||
|
r.Protocol = fmt.Sprintf("%v", fields["protocol"])
|
||||||
|
r.Port = fmt.Sprintf("%v", fields["port"])
|
||||||
|
subnet := fmt.Sprintf("%v", fields["subnet"])
|
||||||
|
|
||||||
|
if subnetSize > 0 && len(subnet) > 0 {
|
||||||
|
_, r.Network, err = net.ParseCIDR(fmt.Sprintf("%s/%d", subnet, subnetSize))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to parse subnet from Vultr API")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, r.Network, _ = net.ParseCIDR("0.0.0.0/0")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFirewallGroups returns a list of all available firewall groups on Vultr
|
||||||
|
func (c *Client) GetFirewallGroups() ([]FirewallGroup, error) {
|
||||||
|
var groupMap map[string]FirewallGroup
|
||||||
|
if err := c.get(`firewall/group_list`, &groupMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupList []FirewallGroup
|
||||||
|
for _, g := range groupMap {
|
||||||
|
groupList = append(groupList, g)
|
||||||
|
}
|
||||||
|
sort.Sort(firewallGroups(groupList))
|
||||||
|
return groupList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFirewallGroup returns the firewall group with given ID
|
||||||
|
func (c *Client) GetFirewallGroup(id string) (FirewallGroup, error) {
|
||||||
|
groups, err := c.GetFirewallGroups()
|
||||||
|
if err != nil {
|
||||||
|
return FirewallGroup{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, g := range groups {
|
||||||
|
if g.ID == id {
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FirewallGroup{}, fmt.Errorf("Firewall group with ID %v not found", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFirewallGroup creates a new firewall group in Vultr account
|
||||||
|
func (c *Client) CreateFirewallGroup(description string) (string, error) {
|
||||||
|
values := url.Values{}
|
||||||
|
|
||||||
|
// Optional description
|
||||||
|
if len(description) > 0 {
|
||||||
|
values.Add("description", description)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result FirewallGroup
|
||||||
|
err := c.post(`firewall/group_create`, values, &result)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return result.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFirewallGroup deletes an existing firewall group
|
||||||
|
func (c *Client) DeleteFirewallGroup(groupID string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"FIREWALLGROUPID": {groupID},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`firewall/group_delete`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFirewallGroupDescription sets the description of an existing firewall group
|
||||||
|
func (c *Client) SetFirewallGroupDescription(groupID, description string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"FIREWALLGROUPID": {groupID},
|
||||||
|
"description": {description},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`firewall/group_set_description`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFirewallRules returns a list of rules for the given firewall group
|
||||||
|
func (c *Client) GetFirewallRules(groupID string) ([]FirewallRule, error) {
|
||||||
|
var ruleMap map[string]FirewallRule
|
||||||
|
ipTypes := []string{"v4", "v6"}
|
||||||
|
for _, ipType := range ipTypes {
|
||||||
|
args := fmt.Sprintf("direction=in&FIREWALLGROUPID=%s&ip_type=%s",
|
||||||
|
groupID, ipType)
|
||||||
|
if err := c.get(`firewall/rule_list?`+args, &ruleMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ruleList []FirewallRule
|
||||||
|
for _, r := range ruleMap {
|
||||||
|
ruleList = append(ruleList, r)
|
||||||
|
}
|
||||||
|
sort.Sort(firewallRules(ruleList))
|
||||||
|
return ruleList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFirewallRule creates a new firewall rule in Vultr account.
|
||||||
|
// groupID is the ID of the firewall group to create the rule in
|
||||||
|
// protocol must be one of: "icmp", "tcp", "udp", "gre"
|
||||||
|
// port can be a port number or colon separated port range (TCP/UDP only)
|
||||||
|
func (c *Client) CreateFirewallRule(groupID, protocol, port string,
|
||||||
|
network *net.IPNet) (int, error) {
|
||||||
|
ip := network.IP.String()
|
||||||
|
maskBits, _ := network.Mask.Size()
|
||||||
|
if ip == "<nil>" {
|
||||||
|
return 0, fmt.Errorf("Invalid network")
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipType string
|
||||||
|
if network.IP.To4() != nil {
|
||||||
|
ipType = "v4"
|
||||||
|
} else {
|
||||||
|
ipType = "v6"
|
||||||
|
}
|
||||||
|
|
||||||
|
values := url.Values{
|
||||||
|
"FIREWALLGROUPID": {groupID},
|
||||||
|
// possible values: "in"
|
||||||
|
"direction": {"in"},
|
||||||
|
// possible values: "icmp", "tcp", "udp", "gre"
|
||||||
|
"protocol": {protocol},
|
||||||
|
// possible values: "v4", "v6"
|
||||||
|
"ip_type": {ipType},
|
||||||
|
// IP address representing a subnet
|
||||||
|
"subnet": {ip},
|
||||||
|
// IP prefix size in bits
|
||||||
|
"subnet_size": {fmt.Sprintf("%v", maskBits)},
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(port) > 0 {
|
||||||
|
values.Add("port", port)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result FirewallRule
|
||||||
|
err := c.post(`firewall/rule_create`, values, &result)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return result.RuleNumber, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFirewallRule deletes an existing firewall rule
|
||||||
|
func (c *Client) DeleteFirewallRule(ruleNumber int, groupID string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"FIREWALLGROUPID": {groupID},
|
||||||
|
"rulenumber": {fmt.Sprintf("%v", ruleNumber)},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`firewall/rule_delete`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
192
vendor/github.com/JamesClonk/vultr/lib/ip.go
generated
vendored
Normal file
192
vendor/github.com/JamesClonk/vultr/lib/ip.go
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPv4 information of a virtual machine
|
||||||
|
type IPv4 struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Netmask string `json:"netmask"`
|
||||||
|
Gateway string `json:"gateway"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
ReverseDNS string `json:"reverse"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ipv4s []IPv4
|
||||||
|
|
||||||
|
func (s ipv4s) Len() int { return len(s) }
|
||||||
|
func (s ipv4s) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s ipv4s) Less(i, j int) bool {
|
||||||
|
// sort order: type, ip
|
||||||
|
if s[i].Type < s[j].Type {
|
||||||
|
return true
|
||||||
|
} else if s[i].Type > s[j].Type {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s[i].IP < s[j].IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv6 information of a virtual machine
|
||||||
|
type IPv6 struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Network string `json:"network"`
|
||||||
|
NetworkSize string `json:"network_size"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ipv6s []IPv6
|
||||||
|
|
||||||
|
func (s ipv6s) Len() int { return len(s) }
|
||||||
|
func (s ipv6s) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s ipv6s) Less(i, j int) bool {
|
||||||
|
// sort order: type, ip
|
||||||
|
if s[i].Type < s[j].Type {
|
||||||
|
return true
|
||||||
|
} else if s[i].Type > s[j].Type {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s[i].IP < s[j].IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReverseDNSIPv6 information of a virtual machine
|
||||||
|
type ReverseDNSIPv6 struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
ReverseDNS string `json:"reverse"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type reverseDNSIPv6s []ReverseDNSIPv6
|
||||||
|
|
||||||
|
func (s reverseDNSIPv6s) Len() int { return len(s) }
|
||||||
|
func (s reverseDNSIPv6s) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s reverseDNSIPv6s) Less(i, j int) bool { return s[i].IP < s[j].IP }
|
||||||
|
|
||||||
|
// ListIPv4 lists the IPv4 information of a virtual machine
|
||||||
|
func (c *Client) ListIPv4(id string) (list []IPv4, err error) {
|
||||||
|
var ipMap map[string][]IPv4
|
||||||
|
if err := c.get(`server/list_ipv4?SUBID=`+id, &ipMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iplist := range ipMap {
|
||||||
|
for _, ip := range iplist {
|
||||||
|
list = append(list, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(ipv4s(list))
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateIPv4 creates an IPv4 address and attaches it to a virtual machine
|
||||||
|
func (c *Client) CreateIPv4(id string, reboot bool) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"reboot": {fmt.Sprintf("%t", reboot)},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/create_ipv4`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteIPv4 deletes an IPv4 address and detaches it from a virtual machine
|
||||||
|
func (c *Client) DeleteIPv4(id, ip string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"ip": {ip},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/destroy_ipv4`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListIPv6 lists the IPv4 information of a virtual machine
|
||||||
|
func (c *Client) ListIPv6(id string) (list []IPv6, err error) {
|
||||||
|
var ipMap map[string][]IPv6
|
||||||
|
if err := c.get(`server/list_ipv6?SUBID=`+id, &ipMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iplist := range ipMap {
|
||||||
|
for _, ip := range iplist {
|
||||||
|
list = append(list, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(ipv6s(list))
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListIPv6ReverseDNS lists the IPv6 reverse DNS entries of a virtual machine
|
||||||
|
func (c *Client) ListIPv6ReverseDNS(id string) (list []ReverseDNSIPv6, err error) {
|
||||||
|
var ipMap map[string][]ReverseDNSIPv6
|
||||||
|
if err := c.get(`server/reverse_list_ipv6?SUBID=`+id, &ipMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iplist := range ipMap {
|
||||||
|
for _, ip := range iplist {
|
||||||
|
list = append(list, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(reverseDNSIPv6s(list))
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteIPv6ReverseDNS removes a reverse DNS entry for an IPv6 address of a virtual machine
|
||||||
|
func (c *Client) DeleteIPv6ReverseDNS(id string, ip string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"ip": {ip},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/reverse_delete_ipv6`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIPv6ReverseDNS sets a reverse DNS entry for an IPv6 address of a virtual machine
|
||||||
|
func (c *Client) SetIPv6ReverseDNS(id, ip, entry string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"ip": {ip},
|
||||||
|
"entry": {entry},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/reverse_set_ipv6`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultIPv4ReverseDNS sets a reverse DNS entry for an IPv4 address of a virtual machine to the original setting
|
||||||
|
func (c *Client) DefaultIPv4ReverseDNS(id, ip string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"ip": {ip},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/reverse_default_ipv4`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIPv4ReverseDNS sets a reverse DNS entry for an IPv4 address of a virtual machine
|
||||||
|
func (c *Client) SetIPv4ReverseDNS(id, ip, entry string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"ip": {ip},
|
||||||
|
"entry": {entry},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/reverse_set_ipv4`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
44
vendor/github.com/JamesClonk/vultr/lib/iso.go
generated
vendored
Normal file
44
vendor/github.com/JamesClonk/vultr/lib/iso.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ISO image on Vultr
|
||||||
|
type ISO struct {
|
||||||
|
ID int `json:"ISOID"`
|
||||||
|
Created string `json:"date_created"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
MD5sum string `json:"md5sum"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type isos []ISO
|
||||||
|
|
||||||
|
func (s isos) Len() int { return len(s) }
|
||||||
|
func (s isos) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s isos) Less(i, j int) bool {
|
||||||
|
// sort order: filename, created
|
||||||
|
if strings.ToLower(s[i].Filename) < strings.ToLower(s[j].Filename) {
|
||||||
|
return true
|
||||||
|
} else if strings.ToLower(s[i].Filename) > strings.ToLower(s[j].Filename) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s[i].Created < s[j].Created
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetISO returns a list of all ISO images on Vultr account
|
||||||
|
func (c *Client) GetISO() ([]ISO, error) {
|
||||||
|
var isoMap map[string]ISO
|
||||||
|
if err := c.get(`iso/list`, &isoMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var isoList []ISO
|
||||||
|
for _, iso := range isoMap {
|
||||||
|
isoList = append(isoList, iso)
|
||||||
|
}
|
||||||
|
sort.Sort(isos(isoList))
|
||||||
|
return isoList, nil
|
||||||
|
}
|
37
vendor/github.com/JamesClonk/vultr/lib/os.go
generated
vendored
Normal file
37
vendor/github.com/JamesClonk/vultr/lib/os.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OS image on Vultr
|
||||||
|
type OS struct {
|
||||||
|
ID int `json:"OSID"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Arch string `json:"arch"`
|
||||||
|
Family string `json:"family"`
|
||||||
|
Windows bool `json:"windows"`
|
||||||
|
Surcharge string `json:"surcharge"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type oses []OS
|
||||||
|
|
||||||
|
func (s oses) Len() int { return len(s) }
|
||||||
|
func (s oses) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s oses) Less(i, j int) bool { return strings.ToLower(s[i].Name) < strings.ToLower(s[j].Name) }
|
||||||
|
|
||||||
|
// GetOS returns a list of all available operating systems on Vultr
|
||||||
|
func (c *Client) GetOS() ([]OS, error) {
|
||||||
|
var osMap map[string]OS
|
||||||
|
if err := c.get(`os/list`, &osMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var osList []OS
|
||||||
|
for _, os := range osMap {
|
||||||
|
osList = append(osList, os)
|
||||||
|
}
|
||||||
|
sort.Sort(oses(osList))
|
||||||
|
return osList, nil
|
||||||
|
}
|
78
vendor/github.com/JamesClonk/vultr/lib/plans.go
generated
vendored
Normal file
78
vendor/github.com/JamesClonk/vultr/lib/plans.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Plan on Vultr
|
||||||
|
type Plan struct {
|
||||||
|
ID int `json:"VPSPLANID,string"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
VCpus int `json:"vcpu_count,string"`
|
||||||
|
RAM string `json:"ram"`
|
||||||
|
Disk string `json:"disk"`
|
||||||
|
Bandwidth string `json:"bandwidth"`
|
||||||
|
Price string `json:"price_per_month"`
|
||||||
|
Regions []int `json:"available_locations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type plans []Plan
|
||||||
|
|
||||||
|
func (p plans) Len() int { return len(p) }
|
||||||
|
func (p plans) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
|
func (p plans) Less(i, j int) bool {
|
||||||
|
pa, _ := strconv.ParseFloat(strings.TrimSpace(p[i].Price), 64)
|
||||||
|
pb, _ := strconv.ParseFloat(strings.TrimSpace(p[j].Price), 64)
|
||||||
|
ra, _ := strconv.ParseInt(strings.TrimSpace(p[i].RAM), 10, 64)
|
||||||
|
rb, _ := strconv.ParseInt(strings.TrimSpace(p[j].RAM), 10, 64)
|
||||||
|
da, _ := strconv.ParseInt(strings.TrimSpace(p[i].Disk), 10, 64)
|
||||||
|
db, _ := strconv.ParseInt(strings.TrimSpace(p[j].Disk), 10, 64)
|
||||||
|
|
||||||
|
// sort order: price, vcpu, ram, disk
|
||||||
|
if pa < pb {
|
||||||
|
return true
|
||||||
|
} else if pa > pb {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if p[i].VCpus < p[j].VCpus {
|
||||||
|
return true
|
||||||
|
} else if p[i].VCpus > p[j].VCpus {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ra < rb {
|
||||||
|
return true
|
||||||
|
} else if ra > rb {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return da < db
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPlans returns a list of all available plans on Vultr account
|
||||||
|
func (c *Client) GetPlans() ([]Plan, error) {
|
||||||
|
var planMap map[string]Plan
|
||||||
|
if err := c.get(`plans/list`, &planMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var p plans
|
||||||
|
for _, plan := range planMap {
|
||||||
|
p = append(p, plan)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(plans(p))
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAvailablePlansForRegion returns available plans for specified region
|
||||||
|
func (c *Client) GetAvailablePlansForRegion(id int) (planIDs []int, err error) {
|
||||||
|
if err := c.get(fmt.Sprintf(`regions/availability?DCID=%v`, id), &planIDs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
44
vendor/github.com/JamesClonk/vultr/lib/regions.go
generated
vendored
Normal file
44
vendor/github.com/JamesClonk/vultr/lib/regions.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
|
// Region on Vultr
|
||||||
|
type Region struct {
|
||||||
|
ID int `json:"DCID,string"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
Continent string `json:"continent"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Ddos bool `json:"ddos_protection"`
|
||||||
|
BlockStorage bool `json:"block_storage"`
|
||||||
|
Code string `json:"regioncode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type regions []Region
|
||||||
|
|
||||||
|
func (s regions) Len() int { return len(s) }
|
||||||
|
func (s regions) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s regions) Less(i, j int) bool {
|
||||||
|
// sort order: continent, name
|
||||||
|
if s[i].Continent < s[j].Continent {
|
||||||
|
return true
|
||||||
|
} else if s[i].Continent > s[j].Continent {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s[i].Name < s[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRegions returns a list of all available Vultr regions
|
||||||
|
func (c *Client) GetRegions() ([]Region, error) {
|
||||||
|
var regionMap map[string]Region
|
||||||
|
if err := c.get(`regions/list`, ®ionMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var regionList []Region
|
||||||
|
for _, os := range regionMap {
|
||||||
|
regionList = append(regionList, os)
|
||||||
|
}
|
||||||
|
sort.Sort(regions(regionList))
|
||||||
|
return regionList, nil
|
||||||
|
}
|
192
vendor/github.com/JamesClonk/vultr/lib/reservedip.go
generated
vendored
Normal file
192
vendor/github.com/JamesClonk/vultr/lib/reservedip.go
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IP on Vultr
|
||||||
|
type IP struct {
|
||||||
|
ID string `json:"SUBID,string"`
|
||||||
|
RegionID int `json:"DCID,string"`
|
||||||
|
IPType string `json:"ip_type"`
|
||||||
|
Subnet string `json:"subnet"`
|
||||||
|
SubnetSize int `json:"subnet_size"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
AttachedTo string `json:"attached_SUBID,string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ips []IP
|
||||||
|
|
||||||
|
func (s ips) Len() int { return len(s) }
|
||||||
|
func (s ips) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s ips) Less(i, j int) bool {
|
||||||
|
// sort order: label, iptype, subnet
|
||||||
|
if strings.ToLower(s[i].Label) < strings.ToLower(s[j].Label) {
|
||||||
|
return true
|
||||||
|
} else if strings.ToLower(s[i].Label) > strings.ToLower(s[j].Label) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s[i].IPType < s[j].IPType {
|
||||||
|
return true
|
||||||
|
} else if s[i].IPType > s[j].IPType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s[i].Subnet < s[j].Subnet
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaller on IP.
|
||||||
|
// This is needed because the Vultr API is inconsistent in it's JSON responses.
|
||||||
|
// Some fields can change type, from JSON number to JSON string and vice-versa.
|
||||||
|
func (i *IP) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
if i == nil {
|
||||||
|
*i = IP{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields map[string]interface{}
|
||||||
|
if err := json.Unmarshal(data, &fields); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := fmt.Sprintf("%v", fields["SUBID"])
|
||||||
|
if len(value) == 0 || value == "<nil>" || value == "0" {
|
||||||
|
i.ID = ""
|
||||||
|
} else {
|
||||||
|
id, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.ID = strconv.FormatFloat(id, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["DCID"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
region, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.RegionID = int(region)
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["attached_SUBID"])
|
||||||
|
if len(value) == 0 || value == "<nil>" || value == "0" || value == "false" {
|
||||||
|
i.AttachedTo = ""
|
||||||
|
} else {
|
||||||
|
attached, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.AttachedTo = strconv.FormatFloat(attached, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["subnet_size"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
size, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.SubnetSize = int(size)
|
||||||
|
|
||||||
|
i.IPType = fmt.Sprintf("%v", fields["ip_type"])
|
||||||
|
i.Subnet = fmt.Sprintf("%v", fields["subnet"])
|
||||||
|
i.Label = fmt.Sprintf("%v", fields["label"])
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListReservedIP returns a list of all available reserved IPs on Vultr account
|
||||||
|
func (c *Client) ListReservedIP() ([]IP, error) {
|
||||||
|
var ipMap map[string]IP
|
||||||
|
|
||||||
|
err := c.get(`reservedip/list`, &ipMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ipList := make([]IP, 0)
|
||||||
|
for _, ip := range ipMap {
|
||||||
|
ipList = append(ipList, ip)
|
||||||
|
}
|
||||||
|
sort.Sort(ips(ipList))
|
||||||
|
return ipList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReservedIP returns reserved IP with given ID
|
||||||
|
func (c *Client) GetReservedIP(id string) (IP, error) {
|
||||||
|
var ipMap map[string]IP
|
||||||
|
|
||||||
|
err := c.get(`reservedip/list`, &ipMap)
|
||||||
|
if err != nil {
|
||||||
|
return IP{}, err
|
||||||
|
}
|
||||||
|
if ip, ok := ipMap[id]; ok {
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
return IP{}, fmt.Errorf("IP with ID %v not found", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateReservedIP creates a new reserved IP on Vultr account
|
||||||
|
func (c *Client) CreateReservedIP(regionID int, ipType string, label string) (string, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"DCID": {fmt.Sprintf("%v", regionID)},
|
||||||
|
"ip_type": {ipType},
|
||||||
|
}
|
||||||
|
if len(label) > 0 {
|
||||||
|
values.Add("label", label)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := IP{}
|
||||||
|
err := c.post(`reservedip/create`, values, &result)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return result.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroyReservedIP deletes an existing reserved IP
|
||||||
|
func (c *Client) DestroyReservedIP(id string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
}
|
||||||
|
return c.post(`reservedip/destroy`, values, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachReservedIP attaches a reserved IP to a virtual machine
|
||||||
|
func (c *Client) AttachReservedIP(ip string, serverID string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"ip_address": {ip},
|
||||||
|
"attach_SUBID": {serverID},
|
||||||
|
}
|
||||||
|
return c.post(`reservedip/attach`, values, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetachReservedIP detaches a reserved IP from an existing virtual machine
|
||||||
|
func (c *Client) DetachReservedIP(serverID string, ip string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"ip_address": {ip},
|
||||||
|
"detach_SUBID": {serverID},
|
||||||
|
}
|
||||||
|
return c.post(`reservedip/detach`, values, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertReservedIP converts an existing virtual machines IP to a reserved IP
|
||||||
|
func (c *Client) ConvertReservedIP(serverID string, ip string) (string, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {serverID},
|
||||||
|
"ip_address": {ip},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := IP{}
|
||||||
|
err := c.post(`reservedip/convert`, values, &result)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return result.ID, err
|
||||||
|
}
|
126
vendor/github.com/JamesClonk/vultr/lib/scripts.go
generated
vendored
Normal file
126
vendor/github.com/JamesClonk/vultr/lib/scripts.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartupScript on Vultr account
|
||||||
|
type StartupScript struct {
|
||||||
|
ID string `json:"SCRIPTID"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Content string `json:"script"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type startupscripts []StartupScript
|
||||||
|
|
||||||
|
func (s startupscripts) Len() int { return len(s) }
|
||||||
|
func (s startupscripts) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s startupscripts) Less(i, j int) bool {
|
||||||
|
return strings.ToLower(s[i].Name) < strings.ToLower(s[j].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaller on StartupScript.
|
||||||
|
// Necessary because the SCRIPTID field has inconsistent types.
|
||||||
|
func (s *StartupScript) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
if s == nil {
|
||||||
|
*s = StartupScript{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields map[string]interface{}
|
||||||
|
if err := json.Unmarshal(data, &fields); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ID = fmt.Sprintf("%v", fields["SCRIPTID"])
|
||||||
|
s.Name = fmt.Sprintf("%v", fields["name"])
|
||||||
|
s.Type = fmt.Sprintf("%v", fields["type"])
|
||||||
|
s.Content = fmt.Sprintf("%v", fields["script"])
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStartupScripts returns a list of all startup scripts on the current Vultr account
|
||||||
|
func (c *Client) GetStartupScripts() (scripts []StartupScript, err error) {
|
||||||
|
var scriptMap map[string]StartupScript
|
||||||
|
if err := c.get(`startupscript/list`, &scriptMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, script := range scriptMap {
|
||||||
|
if script.Type == "" {
|
||||||
|
script.Type = "boot" // set default script type
|
||||||
|
}
|
||||||
|
scripts = append(scripts, script)
|
||||||
|
}
|
||||||
|
sort.Sort(startupscripts(scripts))
|
||||||
|
return scripts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStartupScript returns the startup script with the given ID
|
||||||
|
func (c *Client) GetStartupScript(id string) (StartupScript, error) {
|
||||||
|
scripts, err := c.GetStartupScripts()
|
||||||
|
if err != nil {
|
||||||
|
return StartupScript{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range scripts {
|
||||||
|
if s.ID == id {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StartupScript{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateStartupScript creates a new startup script
|
||||||
|
func (c *Client) CreateStartupScript(name, content, scriptType string) (StartupScript, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"name": {name},
|
||||||
|
"script": {content},
|
||||||
|
"type": {scriptType},
|
||||||
|
}
|
||||||
|
|
||||||
|
var script StartupScript
|
||||||
|
if err := c.post(`startupscript/create`, values, &script); err != nil {
|
||||||
|
return StartupScript{}, err
|
||||||
|
}
|
||||||
|
script.Name = name
|
||||||
|
script.Content = content
|
||||||
|
script.Type = scriptType
|
||||||
|
|
||||||
|
return script, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStartupScript updates an existing startup script
|
||||||
|
func (c *Client) UpdateStartupScript(script StartupScript) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SCRIPTID": {script.ID},
|
||||||
|
}
|
||||||
|
if script.Name != "" {
|
||||||
|
values.Add("name", script.Name)
|
||||||
|
}
|
||||||
|
if script.Content != "" {
|
||||||
|
values.Add("script", script.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`startupscript/update`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteStartupScript deletes an existing startup script from Vultr account
|
||||||
|
func (c *Client) DeleteStartupScript(id string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SCRIPTID": {id},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`startupscript/destroy`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
561
vendor/github.com/JamesClonk/vultr/lib/servers.go
generated
vendored
Normal file
561
vendor/github.com/JamesClonk/vultr/lib/servers.go
generated
vendored
Normal file
@ -0,0 +1,561 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server (virtual machine) on Vultr account
|
||||||
|
type Server struct {
|
||||||
|
ID string `json:"SUBID"`
|
||||||
|
Name string `json:"label"`
|
||||||
|
OS string `json:"os"`
|
||||||
|
RAM string `json:"ram"`
|
||||||
|
Disk string `json:"disk"`
|
||||||
|
MainIP string `json:"main_ip"`
|
||||||
|
VCpus int `json:"vcpu_count,string"`
|
||||||
|
Location string `json:"location"`
|
||||||
|
RegionID int `json:"DCID,string"`
|
||||||
|
DefaultPassword string `json:"default_password"`
|
||||||
|
Created string `json:"date_created"`
|
||||||
|
PendingCharges float64 `json:"pending_charges"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Cost string `json:"cost_per_month"`
|
||||||
|
CurrentBandwidth float64 `json:"current_bandwidth_gb"`
|
||||||
|
AllowedBandwidth float64 `json:"allowed_bandwidth_gb,string"`
|
||||||
|
NetmaskV4 string `json:"netmask_v4"`
|
||||||
|
GatewayV4 string `json:"gateway_v4"`
|
||||||
|
PowerStatus string `json:"power_status"`
|
||||||
|
ServerState string `json:"server_state"`
|
||||||
|
PlanID int `json:"VPSPLANID,string"`
|
||||||
|
V6Networks []V6Network `json:"v6_networks"`
|
||||||
|
InternalIP string `json:"internal_ip"`
|
||||||
|
KVMUrl string `json:"kvm_url"`
|
||||||
|
AutoBackups string `json:"auto_backups"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
OSID string `json:"OSID"`
|
||||||
|
AppID string `json:"APPID"`
|
||||||
|
FirewallGroupID string `json:"FIREWALLGROUPID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerOptions are optional parameters to be used during server creation
|
||||||
|
type ServerOptions struct {
|
||||||
|
IPXEChainURL string
|
||||||
|
ISO int
|
||||||
|
Script int
|
||||||
|
UserData string
|
||||||
|
Snapshot string
|
||||||
|
SSHKey string
|
||||||
|
ReservedIP string
|
||||||
|
IPV6 bool
|
||||||
|
PrivateNetworking bool
|
||||||
|
AutoBackups bool
|
||||||
|
DontNotifyOnActivate bool
|
||||||
|
Hostname string
|
||||||
|
Tag string
|
||||||
|
AppID string
|
||||||
|
FirewallGroupID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type servers []Server
|
||||||
|
|
||||||
|
func (s servers) Len() int { return len(s) }
|
||||||
|
func (s servers) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s servers) Less(i, j int) bool {
|
||||||
|
// sort order: name, ip
|
||||||
|
if strings.ToLower(s[i].Name) < strings.ToLower(s[j].Name) {
|
||||||
|
return true
|
||||||
|
} else if strings.ToLower(s[i].Name) > strings.ToLower(s[j].Name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s[i].MainIP < s[j].MainIP
|
||||||
|
}
|
||||||
|
|
||||||
|
// V6Network represents a IPv6 network of a Vultr server
|
||||||
|
type V6Network struct {
|
||||||
|
Network string `json:"v6_network"`
|
||||||
|
MainIP string `json:"v6_main_ip"`
|
||||||
|
NetworkSize string `json:"v6_network_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISOStatus represents an ISO image attached to a Vultr server
|
||||||
|
type ISOStatus struct {
|
||||||
|
State string `json:"state"`
|
||||||
|
ISOID string `json:"ISOID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaller on Server.
|
||||||
|
// This is needed because the Vultr API is inconsistent in it's JSON responses for servers.
|
||||||
|
// Some fields can change type, from JSON number to JSON string and vice-versa.
|
||||||
|
func (s *Server) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
if s == nil {
|
||||||
|
*s = Server{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields map[string]interface{}
|
||||||
|
if err := json.Unmarshal(data, &fields); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := fmt.Sprintf("%v", fields["vcpu_count"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
vcpu, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.VCpus = int(vcpu)
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["DCID"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
region, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.RegionID = int(region)
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["VPSPLANID"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
plan, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.PlanID = int(plan)
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["pending_charges"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
pc, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.PendingCharges = pc
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["current_bandwidth_gb"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
cb, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.CurrentBandwidth = cb
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["allowed_bandwidth_gb"])
|
||||||
|
if len(value) == 0 || value == "<nil>" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
ab, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.AllowedBandwidth = ab
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["OSID"])
|
||||||
|
if value == "<nil>" {
|
||||||
|
value = ""
|
||||||
|
}
|
||||||
|
s.OSID = value
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["APPID"])
|
||||||
|
if value == "<nil>" || value == "0" {
|
||||||
|
value = ""
|
||||||
|
}
|
||||||
|
s.AppID = value
|
||||||
|
|
||||||
|
value = fmt.Sprintf("%v", fields["FIREWALLGROUPID"])
|
||||||
|
if value == "<nil>" || value == "0" {
|
||||||
|
value = ""
|
||||||
|
}
|
||||||
|
s.FirewallGroupID = value
|
||||||
|
|
||||||
|
s.ID = fmt.Sprintf("%v", fields["SUBID"])
|
||||||
|
s.Name = fmt.Sprintf("%v", fields["label"])
|
||||||
|
s.OS = fmt.Sprintf("%v", fields["os"])
|
||||||
|
s.RAM = fmt.Sprintf("%v", fields["ram"])
|
||||||
|
s.Disk = fmt.Sprintf("%v", fields["disk"])
|
||||||
|
s.MainIP = fmt.Sprintf("%v", fields["main_ip"])
|
||||||
|
s.Location = fmt.Sprintf("%v", fields["location"])
|
||||||
|
s.DefaultPassword = fmt.Sprintf("%v", fields["default_password"])
|
||||||
|
s.Created = fmt.Sprintf("%v", fields["date_created"])
|
||||||
|
s.Status = fmt.Sprintf("%v", fields["status"])
|
||||||
|
s.Cost = fmt.Sprintf("%v", fields["cost_per_month"])
|
||||||
|
s.NetmaskV4 = fmt.Sprintf("%v", fields["netmask_v4"])
|
||||||
|
s.GatewayV4 = fmt.Sprintf("%v", fields["gateway_v4"])
|
||||||
|
s.PowerStatus = fmt.Sprintf("%v", fields["power_status"])
|
||||||
|
s.ServerState = fmt.Sprintf("%v", fields["server_state"])
|
||||||
|
|
||||||
|
v6networks := make([]V6Network, 0)
|
||||||
|
if networks, ok := fields["v6_networks"].([]interface{}); ok {
|
||||||
|
for _, network := range networks {
|
||||||
|
if network, ok := network.(map[string]interface{}); ok {
|
||||||
|
v6network := V6Network{
|
||||||
|
Network: fmt.Sprintf("%v", network["v6_network"]),
|
||||||
|
MainIP: fmt.Sprintf("%v", network["v6_main_ip"]),
|
||||||
|
NetworkSize: fmt.Sprintf("%v", network["v6_network_size"]),
|
||||||
|
}
|
||||||
|
v6networks = append(v6networks, v6network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.V6Networks = v6networks
|
||||||
|
}
|
||||||
|
|
||||||
|
s.InternalIP = fmt.Sprintf("%v", fields["internal_ip"])
|
||||||
|
s.KVMUrl = fmt.Sprintf("%v", fields["kvm_url"])
|
||||||
|
s.AutoBackups = fmt.Sprintf("%v", fields["auto_backups"])
|
||||||
|
s.Tag = fmt.Sprintf("%v", fields["tag"])
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServers returns a list of current virtual machines on Vultr account
|
||||||
|
func (c *Client) GetServers() (serverList []Server, err error) {
|
||||||
|
var serverMap map[string]Server
|
||||||
|
if err := c.get(`server/list`, &serverMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, server := range serverMap {
|
||||||
|
serverList = append(serverList, server)
|
||||||
|
}
|
||||||
|
sort.Sort(servers(serverList))
|
||||||
|
return serverList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServersByTag returns a list of all virtual machines matching by tag
|
||||||
|
func (c *Client) GetServersByTag(tag string) (serverList []Server, err error) {
|
||||||
|
var serverMap map[string]Server
|
||||||
|
if err := c.get(`server/list?tag=`+tag, &serverMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, server := range serverMap {
|
||||||
|
serverList = append(serverList, server)
|
||||||
|
}
|
||||||
|
sort.Sort(servers(serverList))
|
||||||
|
return serverList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServer returns the virtual machine with the given ID
|
||||||
|
func (c *Client) GetServer(id string) (server Server, err error) {
|
||||||
|
if err := c.get(`server/list?SUBID=`+id, &server); err != nil {
|
||||||
|
return Server{}, err
|
||||||
|
}
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateServer creates a new virtual machine on Vultr. ServerOptions are optional settings.
|
||||||
|
func (c *Client) CreateServer(name string, regionID, planID, osID int, options *ServerOptions) (Server, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"label": {name},
|
||||||
|
"DCID": {fmt.Sprintf("%v", regionID)},
|
||||||
|
"VPSPLANID": {fmt.Sprintf("%v", planID)},
|
||||||
|
"OSID": {fmt.Sprintf("%v", osID)},
|
||||||
|
}
|
||||||
|
|
||||||
|
if options != nil {
|
||||||
|
if options.IPXEChainURL != "" {
|
||||||
|
values.Add("ipxe_chain_url", options.IPXEChainURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.ISO != 0 {
|
||||||
|
values.Add("ISOID", fmt.Sprintf("%v", options.ISO))
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Script != 0 {
|
||||||
|
values.Add("SCRIPTID", fmt.Sprintf("%v", options.Script))
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.UserData != "" {
|
||||||
|
values.Add("userdata", base64.StdEncoding.EncodeToString([]byte(options.UserData)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Snapshot != "" {
|
||||||
|
values.Add("SNAPSHOTID", options.Snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.SSHKey != "" {
|
||||||
|
values.Add("SSHKEYID", options.SSHKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.ReservedIP != "" {
|
||||||
|
values.Add("reserved_ip_v4", options.ReservedIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
values.Add("enable_ipv6", "no")
|
||||||
|
if options.IPV6 {
|
||||||
|
values.Set("enable_ipv6", "yes")
|
||||||
|
}
|
||||||
|
|
||||||
|
values.Add("enable_private_network", "no")
|
||||||
|
if options.PrivateNetworking {
|
||||||
|
values.Set("enable_private_network", "yes")
|
||||||
|
}
|
||||||
|
|
||||||
|
values.Add("auto_backups", "no")
|
||||||
|
if options.AutoBackups {
|
||||||
|
values.Set("auto_backups", "yes")
|
||||||
|
}
|
||||||
|
|
||||||
|
values.Add("notify_activate", "yes")
|
||||||
|
if options.DontNotifyOnActivate {
|
||||||
|
values.Set("notify_activate", "no")
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Hostname != "" {
|
||||||
|
values.Add("hostname", options.Hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Tag != "" {
|
||||||
|
values.Add("tag", options.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.AppID != "" {
|
||||||
|
values.Add("APPID", options.AppID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.FirewallGroupID != "" {
|
||||||
|
values.Add("FIREWALLGROUPID", options.FirewallGroupID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var server Server
|
||||||
|
if err := c.post(`server/create`, values, &server); err != nil {
|
||||||
|
return Server{}, err
|
||||||
|
}
|
||||||
|
server.Name = name
|
||||||
|
server.RegionID = regionID
|
||||||
|
server.PlanID = planID
|
||||||
|
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenameServer renames an existing virtual machine
|
||||||
|
func (c *Client) RenameServer(id, name string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"label": {name},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/label_set`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagServer replaces the tag on an existing virtual machine
|
||||||
|
func (c *Client) TagServer(id, tag string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"tag": {tag},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/tag_set`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartServer starts an existing virtual machine
|
||||||
|
func (c *Client) StartServer(id string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/start`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HaltServer stops an existing virtual machine
|
||||||
|
func (c *Client) HaltServer(id string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/halt`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RebootServer reboots an existing virtual machine
|
||||||
|
func (c *Client) RebootServer(id string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/reboot`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReinstallServer reinstalls the operating system on an existing virtual machine
|
||||||
|
func (c *Client) ReinstallServer(id string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/reinstall`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeOSofServer changes the virtual machine to a different operating system
|
||||||
|
func (c *Client) ChangeOSofServer(id string, osID int) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"OSID": {fmt.Sprintf("%v", osID)},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/os_change`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListOSforServer lists all available operating systems to which an existing virtual machine can be changed
|
||||||
|
func (c *Client) ListOSforServer(id string) (os []OS, err error) {
|
||||||
|
var osMap map[string]OS
|
||||||
|
if err := c.get(`server/os_change_list?SUBID=`+id, &osMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range osMap {
|
||||||
|
os = append(os, o)
|
||||||
|
}
|
||||||
|
sort.Sort(oses(os))
|
||||||
|
return os, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachISOtoServer attaches an ISO image to an existing virtual machine and reboots it
|
||||||
|
func (c *Client) AttachISOtoServer(id string, isoID int) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"ISOID": {fmt.Sprintf("%v", isoID)},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/iso_attach`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetachISOfromServer detaches the currently mounted ISO image from the virtual machine and reboots it
|
||||||
|
func (c *Client) DetachISOfromServer(id string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/iso_detach`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetISOStatusofServer retrieves the current ISO image state of an existing virtual machine
|
||||||
|
func (c *Client) GetISOStatusofServer(id string) (isoStatus ISOStatus, err error) {
|
||||||
|
if err := c.get(`server/iso_status?SUBID=`+id, &isoStatus); err != nil {
|
||||||
|
return ISOStatus{}, err
|
||||||
|
}
|
||||||
|
return isoStatus, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteServer deletes an existing virtual machine
|
||||||
|
func (c *Client) DeleteServer(id string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/destroy`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFirewallGroup adds a virtual machine to a firewall group
|
||||||
|
func (c *Client) SetFirewallGroup(id, firewallgroup string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"FIREWALLGROUPID": {firewallgroup},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/firewall_group_set`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsetFirewallGroup removes a virtual machine from a firewall group
|
||||||
|
func (c *Client) UnsetFirewallGroup(id string) error {
|
||||||
|
return c.SetFirewallGroup(id, "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// BandwidthOfServer retrieves the bandwidth used by a virtual machine
|
||||||
|
func (c *Client) BandwidthOfServer(id string) (bandwidth []map[string]string, err error) {
|
||||||
|
var bandwidthMap map[string][][]string
|
||||||
|
if err := c.get(`server/bandwidth?SUBID=`+id, &bandwidthMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse incoming bytes
|
||||||
|
for _, b := range bandwidthMap["incoming_bytes"] {
|
||||||
|
bMap := make(map[string]string)
|
||||||
|
bMap["date"] = b[0]
|
||||||
|
bMap["incoming"] = b[1]
|
||||||
|
bandwidth = append(bandwidth, bMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse outgoing bytes (we'll assume that incoming and outgoing dates are always a match)
|
||||||
|
for _, b := range bandwidthMap["outgoing_bytes"] {
|
||||||
|
for i := range bandwidth {
|
||||||
|
if bandwidth[i]["date"] == b[0] {
|
||||||
|
bandwidth[i]["outgoing"] = b[1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bandwidth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeApplicationofServer changes the virtual machine to a different application
|
||||||
|
func (c *Client) ChangeApplicationofServer(id string, appID string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"APPID": {appID},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`server/app_change`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListApplicationsforServer lists all available operating systems to which an existing virtual machine can be changed
|
||||||
|
func (c *Client) ListApplicationsforServer(id string) (apps []Application, err error) {
|
||||||
|
var appMap map[string]Application
|
||||||
|
if err := c.get(`server/app_change_list?SUBID=`+id, &appMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, app := range appMap {
|
||||||
|
apps = append(apps, app)
|
||||||
|
}
|
||||||
|
sort.Sort(applications(apps))
|
||||||
|
return apps, nil
|
||||||
|
}
|
72
vendor/github.com/JamesClonk/vultr/lib/snapshots.go
generated
vendored
Normal file
72
vendor/github.com/JamesClonk/vultr/lib/snapshots.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Snapshot of a virtual machine on Vultr account
|
||||||
|
type Snapshot struct {
|
||||||
|
ID string `json:"SNAPSHOTID"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Size string `json:"size"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Created string `json:"date_created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type snapshots []Snapshot
|
||||||
|
|
||||||
|
func (s snapshots) Len() int { return len(s) }
|
||||||
|
func (s snapshots) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s snapshots) Less(i, j int) bool {
|
||||||
|
// sort order: description, created
|
||||||
|
if strings.ToLower(s[i].Description) < strings.ToLower(s[j].Description) {
|
||||||
|
return true
|
||||||
|
} else if strings.ToLower(s[i].Description) > strings.ToLower(s[j].Description) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s[i].Created < s[j].Created
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSnapshots retrieves a list of all snapshots on Vultr account
|
||||||
|
func (c *Client) GetSnapshots() (snapshotList []Snapshot, err error) {
|
||||||
|
var snapshotMap map[string]Snapshot
|
||||||
|
if err := c.get(`snapshot/list`, &snapshotMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, snapshot := range snapshotMap {
|
||||||
|
snapshotList = append(snapshotList, snapshot)
|
||||||
|
}
|
||||||
|
sort.Sort(snapshots(snapshotList))
|
||||||
|
return snapshotList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSnapshot creates a new virtual machine snapshot
|
||||||
|
func (c *Client) CreateSnapshot(id, description string) (Snapshot, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"SUBID": {id},
|
||||||
|
"description": {description},
|
||||||
|
}
|
||||||
|
|
||||||
|
var snapshot Snapshot
|
||||||
|
if err := c.post(`snapshot/create`, values, &snapshot); err != nil {
|
||||||
|
return Snapshot{}, err
|
||||||
|
}
|
||||||
|
snapshot.Description = description
|
||||||
|
|
||||||
|
return snapshot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSnapshot deletes an existing virtual machine snapshot
|
||||||
|
func (c *Client) DeleteSnapshot(id string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SNAPSHOTID": {id},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`snapshot/destroy`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
82
vendor/github.com/JamesClonk/vultr/lib/sshkeys.go
generated
vendored
Normal file
82
vendor/github.com/JamesClonk/vultr/lib/sshkeys.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SSHKey on Vultr account
|
||||||
|
type SSHKey struct {
|
||||||
|
ID string `json:"SSHKEYID"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Key string `json:"ssh_key"`
|
||||||
|
Created string `json:"date_created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshkeys []SSHKey
|
||||||
|
|
||||||
|
func (s sshkeys) Len() int { return len(s) }
|
||||||
|
func (s sshkeys) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s sshkeys) Less(i, j int) bool { return strings.ToLower(s[i].Name) < strings.ToLower(s[j].Name) }
|
||||||
|
|
||||||
|
// GetSSHKeys returns a list of SSHKeys from Vultr account
|
||||||
|
func (c *Client) GetSSHKeys() (keys []SSHKey, err error) {
|
||||||
|
var keyMap map[string]SSHKey
|
||||||
|
if err := c.get(`sshkey/list`, &keyMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range keyMap {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Sort(sshkeys(keys))
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSSHKey creates new SSHKey on Vultr
|
||||||
|
func (c *Client) CreateSSHKey(name, key string) (SSHKey, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"name": {name},
|
||||||
|
"ssh_key": {key},
|
||||||
|
}
|
||||||
|
|
||||||
|
var sshKey SSHKey
|
||||||
|
if err := c.post(`sshkey/create`, values, &sshKey); err != nil {
|
||||||
|
return SSHKey{}, err
|
||||||
|
}
|
||||||
|
sshKey.Name = name
|
||||||
|
sshKey.Key = key
|
||||||
|
|
||||||
|
return sshKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSSHKey updates an existing SSHKey entry
|
||||||
|
func (c *Client) UpdateSSHKey(key SSHKey) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SSHKEYID": {key.ID},
|
||||||
|
}
|
||||||
|
if key.Name != "" {
|
||||||
|
values.Add("name", key.Name)
|
||||||
|
}
|
||||||
|
if key.Key != "" {
|
||||||
|
values.Add("ssh_key", key.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`sshkey/update`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSSHKey deletes an existing SSHKey from Vultr account
|
||||||
|
func (c *Client) DeleteSSHKey(id string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"SSHKEYID": {id},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.post(`sshkey/destroy`, values, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
191
vendor/github.com/juju/ratelimit/LICENSE
generated
vendored
Normal file
191
vendor/github.com/juju/ratelimit/LICENSE
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
All files in this repository are licensed as follows. If you contribute
|
||||||
|
to this repository, it is assumed that you license your contribution
|
||||||
|
under the same license unless you state otherwise.
|
||||||
|
|
||||||
|
All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file.
|
||||||
|
|
||||||
|
This software is licensed under the LGPLv3, included below.
|
||||||
|
|
||||||
|
As a special exception to the GNU Lesser General Public License version 3
|
||||||
|
("LGPL3"), the copyright holders of this Library give you permission to
|
||||||
|
convey to a third party a Combined Work that links statically or dynamically
|
||||||
|
to this Library without providing any Minimal Corresponding Source or
|
||||||
|
Minimal Application Code as set out in 4d or providing the installation
|
||||||
|
information set out in section 4e, provided that you comply with the other
|
||||||
|
provisions of LGPL3 and provided that you meet, for the Application the
|
||||||
|
terms and conditions of the license(s) which apply to the Application.
|
||||||
|
|
||||||
|
Except as stated in this special exception, the provisions of LGPL3 will
|
||||||
|
continue to comply in full to this Library. If you modify this Library, you
|
||||||
|
may apply this exception to your version of this Library, but you are not
|
||||||
|
obliged to do so. If you do not wish to do so, delete this exception
|
||||||
|
statement from your version. This exception does not (and cannot) modify any
|
||||||
|
license terms which apply to the Application, with which you must still
|
||||||
|
comply.
|
||||||
|
|
||||||
|
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
117
vendor/github.com/juju/ratelimit/README.md
generated
vendored
Normal file
117
vendor/github.com/juju/ratelimit/README.md
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
# ratelimit
|
||||||
|
--
|
||||||
|
import "github.com/juju/ratelimit"
|
||||||
|
|
||||||
|
The ratelimit package provides an efficient token bucket implementation. See
|
||||||
|
http://en.wikipedia.org/wiki/Token_bucket.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
#### func Reader
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Reader(r io.Reader, bucket *Bucket) io.Reader
|
||||||
|
```
|
||||||
|
Reader returns a reader that is rate limited by the given token bucket. Each
|
||||||
|
token in the bucket represents one byte.
|
||||||
|
|
||||||
|
#### func Writer
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Writer(w io.Writer, bucket *Bucket) io.Writer
|
||||||
|
```
|
||||||
|
Writer returns a writer that is rate limited by the given token bucket. Each
|
||||||
|
token in the bucket represents one byte.
|
||||||
|
|
||||||
|
#### type Bucket
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Bucket struct {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Bucket represents a token bucket that fills at a predetermined rate. Methods on
|
||||||
|
Bucket may be called concurrently.
|
||||||
|
|
||||||
|
#### func NewBucket
|
||||||
|
|
||||||
|
```go
|
||||||
|
func NewBucket(fillInterval time.Duration, capacity int64) *Bucket
|
||||||
|
```
|
||||||
|
NewBucket returns a new token bucket that fills at the rate of one token every
|
||||||
|
fillInterval, up to the given maximum capacity. Both arguments must be positive.
|
||||||
|
The bucket is initially full.
|
||||||
|
|
||||||
|
#### func NewBucketWithQuantum
|
||||||
|
|
||||||
|
```go
|
||||||
|
func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket
|
||||||
|
```
|
||||||
|
NewBucketWithQuantum is similar to NewBucket, but allows the specification of
|
||||||
|
the quantum size - quantum tokens are added every fillInterval.
|
||||||
|
|
||||||
|
#### func NewBucketWithRate
|
||||||
|
|
||||||
|
```go
|
||||||
|
func NewBucketWithRate(rate float64, capacity int64) *Bucket
|
||||||
|
```
|
||||||
|
NewBucketWithRate returns a token bucket that fills the bucket at the rate of
|
||||||
|
rate tokens per second up to the given maximum capacity. Because of limited
|
||||||
|
clock resolution, at high rates, the actual rate may be up to 1% different from
|
||||||
|
the specified rate.
|
||||||
|
|
||||||
|
#### func (*Bucket) Rate
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (tb *Bucket) Rate() float64
|
||||||
|
```
|
||||||
|
Rate returns the fill rate of the bucket, in tokens per second.
|
||||||
|
|
||||||
|
#### func (*Bucket) Take
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (tb *Bucket) Take(count int64) time.Duration
|
||||||
|
```
|
||||||
|
Take takes count tokens from the bucket without blocking. It returns the time
|
||||||
|
that the caller should wait until the tokens are actually available.
|
||||||
|
|
||||||
|
Note that if the request is irrevocable - there is no way to return tokens to
|
||||||
|
the bucket once this method commits us to taking them.
|
||||||
|
|
||||||
|
#### func (*Bucket) TakeAvailable
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (tb *Bucket) TakeAvailable(count int64) int64
|
||||||
|
```
|
||||||
|
TakeAvailable takes up to count immediately available tokens from the bucket. It
|
||||||
|
returns the number of tokens removed, or zero if there are no available tokens.
|
||||||
|
It does not block.
|
||||||
|
|
||||||
|
#### func (*Bucket) TakeMaxDuration
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (tb *Bucket) TakeMaxDuration(count int64, maxWait time.Duration) (time.Duration, bool)
|
||||||
|
```
|
||||||
|
TakeMaxDuration is like Take, except that it will only take tokens from the
|
||||||
|
bucket if the wait time for the tokens is no greater than maxWait.
|
||||||
|
|
||||||
|
If it would take longer than maxWait for the tokens to become available, it does
|
||||||
|
nothing and reports false, otherwise it returns the time that the caller should
|
||||||
|
wait until the tokens are actually available, and reports true.
|
||||||
|
|
||||||
|
#### func (*Bucket) Wait
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (tb *Bucket) Wait(count int64)
|
||||||
|
```
|
||||||
|
Wait takes count tokens from the bucket, waiting until they are available.
|
||||||
|
|
||||||
|
#### func (*Bucket) WaitMaxDuration
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool
|
||||||
|
```
|
||||||
|
WaitMaxDuration is like Wait except that it will only take tokens from the
|
||||||
|
bucket if it needs to wait for no greater than maxWait. It reports whether any
|
||||||
|
tokens have been removed from the bucket If no tokens have been removed, it
|
||||||
|
returns immediately.
|
284
vendor/github.com/juju/ratelimit/ratelimit.go
generated
vendored
Normal file
284
vendor/github.com/juju/ratelimit/ratelimit.go
generated
vendored
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
// Copyright 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3 with static-linking exception.
|
||||||
|
// See LICENCE file for details.
|
||||||
|
|
||||||
|
// Package ratelimit provides an efficient token bucket implementation
|
||||||
|
// that can be used to limit the rate of arbitrary things.
|
||||||
|
// See http://en.wikipedia.org/wiki/Token_bucket.
|
||||||
|
package ratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bucket represents a token bucket that fills at a predetermined rate.
|
||||||
|
// Methods on Bucket may be called concurrently.
|
||||||
|
type Bucket struct {
|
||||||
|
startTime time.Time
|
||||||
|
capacity int64
|
||||||
|
quantum int64
|
||||||
|
fillInterval time.Duration
|
||||||
|
clock Clock
|
||||||
|
|
||||||
|
// The mutex guards the fields following it.
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
// avail holds the number of available tokens
|
||||||
|
// in the bucket, as of availTick ticks from startTime.
|
||||||
|
// It will be negative when there are consumers
|
||||||
|
// waiting for tokens.
|
||||||
|
avail int64
|
||||||
|
availTick int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock is used to inject testable fakes.
|
||||||
|
type Clock interface {
|
||||||
|
Now() time.Time
|
||||||
|
Sleep(d time.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// realClock implements Clock in terms of standard time functions.
|
||||||
|
type realClock struct{}
|
||||||
|
|
||||||
|
// Now is identical to time.Now.
|
||||||
|
func (realClock) Now() time.Time {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep is identical to time.Sleep.
|
||||||
|
func (realClock) Sleep(d time.Duration) {
|
||||||
|
time.Sleep(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBucket returns a new token bucket that fills at the
|
||||||
|
// rate of one token every fillInterval, up to the given
|
||||||
|
// maximum capacity. Both arguments must be
|
||||||
|
// positive. The bucket is initially full.
|
||||||
|
func NewBucket(fillInterval time.Duration, capacity int64) *Bucket {
|
||||||
|
return NewBucketWithClock(fillInterval, capacity, realClock{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBucketWithClock is identical to NewBucket but injects a testable clock
|
||||||
|
// interface.
|
||||||
|
func NewBucketWithClock(fillInterval time.Duration, capacity int64, clock Clock) *Bucket {
|
||||||
|
return NewBucketWithQuantumAndClock(fillInterval, capacity, 1, clock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rateMargin specifes the allowed variance of actual
|
||||||
|
// rate from specified rate. 1% seems reasonable.
|
||||||
|
const rateMargin = 0.01
|
||||||
|
|
||||||
|
// NewBucketWithRate returns a token bucket that fills the bucket
|
||||||
|
// at the rate of rate tokens per second up to the given
|
||||||
|
// maximum capacity. Because of limited clock resolution,
|
||||||
|
// at high rates, the actual rate may be up to 1% different from the
|
||||||
|
// specified rate.
|
||||||
|
func NewBucketWithRate(rate float64, capacity int64) *Bucket {
|
||||||
|
return NewBucketWithRateAndClock(rate, capacity, realClock{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBucketWithRateAndClock is identical to NewBucketWithRate but injects a
|
||||||
|
// testable clock interface.
|
||||||
|
func NewBucketWithRateAndClock(rate float64, capacity int64, clock Clock) *Bucket {
|
||||||
|
for quantum := int64(1); quantum < 1<<50; quantum = nextQuantum(quantum) {
|
||||||
|
fillInterval := time.Duration(1e9 * float64(quantum) / rate)
|
||||||
|
if fillInterval <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tb := NewBucketWithQuantumAndClock(fillInterval, capacity, quantum, clock)
|
||||||
|
if diff := math.Abs(tb.Rate() - rate); diff/rate <= rateMargin {
|
||||||
|
return tb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("cannot find suitable quantum for " + strconv.FormatFloat(rate, 'g', -1, 64))
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextQuantum returns the next quantum to try after q.
|
||||||
|
// We grow the quantum exponentially, but slowly, so we
|
||||||
|
// get a good fit in the lower numbers.
|
||||||
|
func nextQuantum(q int64) int64 {
|
||||||
|
q1 := q * 11 / 10
|
||||||
|
if q1 == q {
|
||||||
|
q1++
|
||||||
|
}
|
||||||
|
return q1
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBucketWithQuantum is similar to NewBucket, but allows
|
||||||
|
// the specification of the quantum size - quantum tokens
|
||||||
|
// are added every fillInterval.
|
||||||
|
func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket {
|
||||||
|
return NewBucketWithQuantumAndClock(fillInterval, capacity, quantum, realClock{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBucketWithQuantumAndClock is identical to NewBucketWithQuantum but injects
|
||||||
|
// a testable clock interface.
|
||||||
|
func NewBucketWithQuantumAndClock(fillInterval time.Duration, capacity, quantum int64, clock Clock) *Bucket {
|
||||||
|
if fillInterval <= 0 {
|
||||||
|
panic("token bucket fill interval is not > 0")
|
||||||
|
}
|
||||||
|
if capacity <= 0 {
|
||||||
|
panic("token bucket capacity is not > 0")
|
||||||
|
}
|
||||||
|
if quantum <= 0 {
|
||||||
|
panic("token bucket quantum is not > 0")
|
||||||
|
}
|
||||||
|
return &Bucket{
|
||||||
|
clock: clock,
|
||||||
|
startTime: clock.Now(),
|
||||||
|
capacity: capacity,
|
||||||
|
quantum: quantum,
|
||||||
|
avail: capacity,
|
||||||
|
fillInterval: fillInterval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait takes count tokens from the bucket, waiting until they are
|
||||||
|
// available.
|
||||||
|
func (tb *Bucket) Wait(count int64) {
|
||||||
|
if d := tb.Take(count); d > 0 {
|
||||||
|
tb.clock.Sleep(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitMaxDuration is like Wait except that it will
|
||||||
|
// only take tokens from the bucket if it needs to wait
|
||||||
|
// for no greater than maxWait. It reports whether
|
||||||
|
// any tokens have been removed from the bucket
|
||||||
|
// If no tokens have been removed, it returns immediately.
|
||||||
|
func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool {
|
||||||
|
d, ok := tb.TakeMaxDuration(count, maxWait)
|
||||||
|
if d > 0 {
|
||||||
|
tb.clock.Sleep(d)
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
const infinityDuration time.Duration = 0x7fffffffffffffff
|
||||||
|
|
||||||
|
// Take takes count tokens from the bucket without blocking. It returns
|
||||||
|
// the time that the caller should wait until the tokens are actually
|
||||||
|
// available.
|
||||||
|
//
|
||||||
|
// Note that if the request is irrevocable - there is no way to return
|
||||||
|
// tokens to the bucket once this method commits us to taking them.
|
||||||
|
func (tb *Bucket) Take(count int64) time.Duration {
|
||||||
|
d, _ := tb.take(tb.clock.Now(), count, infinityDuration)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// TakeMaxDuration is like Take, except that
|
||||||
|
// it will only take tokens from the bucket if the wait
|
||||||
|
// time for the tokens is no greater than maxWait.
|
||||||
|
//
|
||||||
|
// If it would take longer than maxWait for the tokens
|
||||||
|
// to become available, it does nothing and reports false,
|
||||||
|
// otherwise it returns the time that the caller should
|
||||||
|
// wait until the tokens are actually available, and reports
|
||||||
|
// true.
|
||||||
|
func (tb *Bucket) TakeMaxDuration(count int64, maxWait time.Duration) (time.Duration, bool) {
|
||||||
|
return tb.take(tb.clock.Now(), count, maxWait)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TakeAvailable takes up to count immediately available tokens from the
|
||||||
|
// bucket. It returns the number of tokens removed, or zero if there are
|
||||||
|
// no available tokens. It does not block.
|
||||||
|
func (tb *Bucket) TakeAvailable(count int64) int64 {
|
||||||
|
return tb.takeAvailable(tb.clock.Now(), count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// takeAvailable is the internal version of TakeAvailable - it takes the
|
||||||
|
// current time as an argument to enable easy testing.
|
||||||
|
func (tb *Bucket) takeAvailable(now time.Time, count int64) int64 {
|
||||||
|
if count <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
tb.mu.Lock()
|
||||||
|
defer tb.mu.Unlock()
|
||||||
|
|
||||||
|
tb.adjust(now)
|
||||||
|
if tb.avail <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if count > tb.avail {
|
||||||
|
count = tb.avail
|
||||||
|
}
|
||||||
|
tb.avail -= count
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Available returns the number of available tokens. It will be negative
|
||||||
|
// when there are consumers waiting for tokens. Note that if this
|
||||||
|
// returns greater than zero, it does not guarantee that calls that take
|
||||||
|
// tokens from the buffer will succeed, as the number of available
|
||||||
|
// tokens could have changed in the meantime. This method is intended
|
||||||
|
// primarily for metrics reporting and debugging.
|
||||||
|
func (tb *Bucket) Available() int64 {
|
||||||
|
return tb.available(tb.clock.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
// available is the internal version of available - it takes the current time as
|
||||||
|
// an argument to enable easy testing.
|
||||||
|
func (tb *Bucket) available(now time.Time) int64 {
|
||||||
|
tb.mu.Lock()
|
||||||
|
defer tb.mu.Unlock()
|
||||||
|
tb.adjust(now)
|
||||||
|
return tb.avail
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capacity returns the capacity that the bucket was created with.
|
||||||
|
func (tb *Bucket) Capacity() int64 {
|
||||||
|
return tb.capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate returns the fill rate of the bucket, in tokens per second.
|
||||||
|
func (tb *Bucket) Rate() float64 {
|
||||||
|
return 1e9 * float64(tb.quantum) / float64(tb.fillInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// take is the internal version of Take - it takes the current time as
|
||||||
|
// an argument to enable easy testing.
|
||||||
|
func (tb *Bucket) take(now time.Time, count int64, maxWait time.Duration) (time.Duration, bool) {
|
||||||
|
if count <= 0 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
tb.mu.Lock()
|
||||||
|
defer tb.mu.Unlock()
|
||||||
|
|
||||||
|
currentTick := tb.adjust(now)
|
||||||
|
avail := tb.avail - count
|
||||||
|
if avail >= 0 {
|
||||||
|
tb.avail = avail
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
// Round up the missing tokens to the nearest multiple
|
||||||
|
// of quantum - the tokens won't be available until
|
||||||
|
// that tick.
|
||||||
|
endTick := currentTick + (-avail+tb.quantum-1)/tb.quantum
|
||||||
|
endTime := tb.startTime.Add(time.Duration(endTick) * tb.fillInterval)
|
||||||
|
waitTime := endTime.Sub(now)
|
||||||
|
if waitTime > maxWait {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
tb.avail = avail
|
||||||
|
return waitTime, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust adjusts the current bucket capacity based on the current time.
|
||||||
|
// It returns the current tick.
|
||||||
|
func (tb *Bucket) adjust(now time.Time) (currentTick int64) {
|
||||||
|
currentTick = int64(now.Sub(tb.startTime) / tb.fillInterval)
|
||||||
|
|
||||||
|
if tb.avail >= tb.capacity {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tb.avail += (currentTick - tb.availTick) * tb.quantum
|
||||||
|
if tb.avail > tb.capacity {
|
||||||
|
tb.avail = tb.capacity
|
||||||
|
}
|
||||||
|
tb.availTick = currentTick
|
||||||
|
return
|
||||||
|
}
|
51
vendor/github.com/juju/ratelimit/reader.go
generated
vendored
Normal file
51
vendor/github.com/juju/ratelimit/reader.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3 with static-linking exception.
|
||||||
|
// See LICENCE file for details.
|
||||||
|
|
||||||
|
package ratelimit
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type reader struct {
|
||||||
|
r io.Reader
|
||||||
|
bucket *Bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader returns a reader that is rate limited by
|
||||||
|
// the given token bucket. Each token in the bucket
|
||||||
|
// represents one byte.
|
||||||
|
func Reader(r io.Reader, bucket *Bucket) io.Reader {
|
||||||
|
return &reader{
|
||||||
|
r: r,
|
||||||
|
bucket: bucket,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reader) Read(buf []byte) (int, error) {
|
||||||
|
n, err := r.r.Read(buf)
|
||||||
|
if n <= 0 {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
r.bucket.Wait(int64(n))
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type writer struct {
|
||||||
|
w io.Writer
|
||||||
|
bucket *Bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer returns a reader that is rate limited by
|
||||||
|
// the given token bucket. Each token in the bucket
|
||||||
|
// represents one byte.
|
||||||
|
func Writer(w io.Writer, bucket *Bucket) io.Writer {
|
||||||
|
return &writer{
|
||||||
|
w: w,
|
||||||
|
bucket: bucket,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writer) Write(buf []byte) (int, error) {
|
||||||
|
w.bucket.Wait(int64(len(buf)))
|
||||||
|
return w.w.Write(buf)
|
||||||
|
}
|
12
vendor/vendor.json
vendored
12
vendor/vendor.json
vendored
@ -31,6 +31,12 @@
|
|||||||
"revision": "33a99fdf1d5ee1f79b5077e9c06f955ad356d5f4",
|
"revision": "33a99fdf1d5ee1f79b5077e9c06f955ad356d5f4",
|
||||||
"revisionTime": "2013-01-12T09:33:55Z"
|
"revisionTime": "2013-01-12T09:33:55Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "E0NbOzBCBYViPKvM4XcloBu6GWE=",
|
||||||
|
"path": "github.com/JamesClonk/vultr/lib",
|
||||||
|
"revision": "2fd0705ce648e602e6c9c57329a174270a4f6688",
|
||||||
|
"revisionTime": "2017-08-08T19:54:39Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "GZj+/EV9o0GyIsZ5/S8gSy3ceKM=",
|
"checksumSHA1": "GZj+/EV9o0GyIsZ5/S8gSy3ceKM=",
|
||||||
"path": "github.com/TomOnTime/utfutil",
|
"path": "github.com/TomOnTime/utfutil",
|
||||||
@ -267,6 +273,12 @@
|
|||||||
"revision": "3433f3ea46d9f8019119e7dd41274e112a2359a9",
|
"revision": "3433f3ea46d9f8019119e7dd41274e112a2359a9",
|
||||||
"revisionTime": "2015-11-17T09:58:22-08:00"
|
"revisionTime": "2015-11-17T09:58:22-08:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "a8Ge6pE7oxux9ZMZVAlyEeGzCng=",
|
||||||
|
"path": "github.com/juju/ratelimit",
|
||||||
|
"revision": "5b9ff866471762aa2ab2dced63c9fb6f53921342",
|
||||||
|
"revisionTime": "2017-05-23T01:21:41Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "github.com/kolo/xmlrpc",
|
"path": "github.com/kolo/xmlrpc",
|
||||||
"revision": "0826b98aaa29c0766956cb40d45cf7482a597671",
|
"revision": "0826b98aaa29c0766956cb40d45cf7482a597671",
|
||||||
|
Reference in New Issue
Block a user