Merge pull request #7060 from andrewsomething/do-post-processor

Add digitalocean-import post-processor.
This commit is contained in:
Megan Marsh 2019-02-14 11:58:00 -08:00 committed by GitHub
commit be21850e32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 2156 additions and 270 deletions

View File

@ -12,16 +12,16 @@ import (
type Artifact struct {
// The name of the snapshot
snapshotName string
SnapshotName string
// The ID of the image
snapshotId int
SnapshotId int
// The name of the region
regionNames []string
RegionNames []string
// The client for making API calls
client *godo.Client
Client *godo.Client
}
func (*Artifact) BuilderId() string {
@ -34,11 +34,11 @@ func (*Artifact) Files() []string {
}
func (a *Artifact) Id() string {
return fmt.Sprintf("%s:%s", strings.Join(a.regionNames[:], ","), strconv.FormatUint(uint64(a.snapshotId), 10))
return fmt.Sprintf("%s:%s", strings.Join(a.RegionNames[:], ","), strconv.FormatUint(uint64(a.SnapshotId), 10))
}
func (a *Artifact) String() string {
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v) in regions '%v'", a.snapshotName, a.snapshotId, strings.Join(a.regionNames[:], ","))
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v) in regions '%v'", a.SnapshotName, a.SnapshotId, strings.Join(a.RegionNames[:], ","))
}
func (a *Artifact) State(name string) interface{} {
@ -46,7 +46,7 @@ func (a *Artifact) State(name string) interface{} {
}
func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %d (%s)", a.snapshotId, a.snapshotName)
_, err := a.client.Images.Delete(context.TODO(), a.snapshotId)
log.Printf("Destroying image: %d (%s)", a.SnapshotId, a.SnapshotName)
_, err := a.Client.Images.Delete(context.TODO(), a.SnapshotId)
return err
}

View File

@ -113,10 +113,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
artifact := &Artifact{
snapshotName: state.Get("snapshot_name").(string),
snapshotId: state.Get("snapshot_image_id").(int),
regionNames: state.Get("regions").([]string),
client: client,
SnapshotName: state.Get("snapshot_name").(string),
SnapshotId: state.Get("snapshot_image_id").(int),
RegionNames: state.Get("regions").([]string),
Client: client,
}
return artifact, nil

View File

@ -91,7 +91,7 @@ func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multiste
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("transferring Snapshot ID: %d", imageTransfer.ID))
if err := waitForImageState(godo.ActionCompleted, imageTransfer.ID, action.ID,
if err := WaitForImageState(godo.ActionCompleted, imageTransfer.ID, action.ID,
client, 20*time.Minute); err != nil {
// If we get an error the first time, actually report it
err := fmt.Errorf("Error waiting for snapshot transfer: %s", err)

View File

@ -158,9 +158,9 @@ func waitForActionState(
}
}
// waitForImageState simply blocks until the image action is in
// WaitForImageState simply blocks until the image action is in
// a state we expect, while eventually timing out.
func waitForImageState(
func WaitForImageState(
desiredState string, imageId, actionId int,
client *godo.Client, timeout time.Duration) error {
done := make(chan struct{})

View File

@ -52,6 +52,7 @@ import (
artificepostprocessor "github.com/hashicorp/packer/post-processor/artifice"
checksumpostprocessor "github.com/hashicorp/packer/post-processor/checksum"
compresspostprocessor "github.com/hashicorp/packer/post-processor/compress"
digitaloceanimportpostprocessor "github.com/hashicorp/packer/post-processor/digitalocean-import"
dockerimportpostprocessor "github.com/hashicorp/packer/post-processor/docker-import"
dockerpushpostprocessor "github.com/hashicorp/packer/post-processor/docker-push"
dockersavepostprocessor "github.com/hashicorp/packer/post-processor/docker-save"
@ -148,6 +149,7 @@ var PostProcessors = map[string]packer.PostProcessor{
"artifice": new(artificepostprocessor.PostProcessor),
"checksum": new(checksumpostprocessor.PostProcessor),
"compress": new(compresspostprocessor.PostProcessor),
"digitalocean-import": new(digitaloceanimportpostprocessor.PostProcessor),
"docker-import": new(dockerimportpostprocessor.PostProcessor),
"docker-push": new(dockerpushpostprocessor.PostProcessor),
"docker-save": new(dockersavepostprocessor.PostProcessor),

View File

@ -0,0 +1,365 @@
package digitaloceanimport
import (
"context"
"fmt"
"golang.org/x/oauth2"
"log"
"os"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/digitalocean/godo"
"github.com/hashicorp/packer/builder/digitalocean"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
const BuilderId = "packer.post-processor.digitalocean-import"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
APIToken string `mapstructure:"api_token"`
SpacesKey string `mapstructure:"spaces_key"`
SpacesSecret string `mapstructure:"spaces_secret"`
SpacesRegion string `mapstructure:"spaces_region"`
SpaceName string `mapstructure:"space_name"`
ObjectName string `mapstructure:"space_object_name"`
SkipClean bool `mapstructure:"skip_clean"`
Tags []string `mapstructure:"image_tags"`
Name string `mapstructure:"image_name"`
Description string `mapstructure:"image_description"`
Distribution string `mapstructure:"image_distribution"`
ImageRegions []string `mapstructure:"image_regions"`
Timeout time.Duration `mapstructure:"timeout"`
ctx interpolate.Context
}
type PostProcessor struct {
config Config
}
type apiTokenSource struct {
AccessToken string
}
type logger struct {
logger *log.Logger
}
func (t *apiTokenSource) Token() (*oauth2.Token, error) {
return &oauth2.Token{
AccessToken: t.AccessToken,
}, nil
}
func (l logger) Log(args ...interface{}) {
l.logger.Println(args...)
}
func (p *PostProcessor) Configure(raws ...interface{}) error {
err := config.Decode(&p.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &p.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{"space_object_name"},
},
}, raws...)
if err != nil {
return err
}
if p.config.SpacesKey == "" {
p.config.SpacesKey = os.Getenv("DIGITALOCEAN_SPACES_ACCESS_KEY")
}
if p.config.SpacesSecret == "" {
p.config.SpacesSecret = os.Getenv("DIGITALOCEAN_SPACES_SECRET_KEY")
}
if p.config.APIToken == "" {
p.config.APIToken = os.Getenv("DIGITALOCEAN_API_TOKEN")
}
if p.config.ObjectName == "" {
p.config.ObjectName = "packer-import-{{timestamp}}"
}
if p.config.Distribution == "" {
p.config.Distribution = "Unkown"
}
if p.config.Timeout == 0 {
p.config.Timeout = 20 * time.Minute
}
errs := new(packer.MultiError)
if err = interpolate.Validate(p.config.ObjectName, &p.config.ctx); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error parsing space_object_name template: %s", err))
}
requiredArgs := map[string]*string{
"api_token": &p.config.APIToken,
"spaces_key": &p.config.SpacesKey,
"spaces_secret": &p.config.SpacesSecret,
"spaces_region": &p.config.SpacesRegion,
"space_name": &p.config.SpaceName,
"image_name": &p.config.Name,
"image_regions": &p.config.ImageRegions[0],
}
for key, ptr := range requiredArgs {
if *ptr == "" {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("%s must be set", key))
}
}
if len(errs.Errors) > 0 {
return errs
}
packer.LogSecretFilter.Set(p.config.SpacesKey, p.config.SpacesSecret, p.config.APIToken)
log.Println(p.config)
return nil
}
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
var err error
p.config.ObjectName, err = interpolate.Render(p.config.ObjectName, &p.config.ctx)
if err != nil {
return nil, false, fmt.Errorf("Error rendering space_object_name template: %s", err)
}
log.Printf("Rendered space_object_name as %s", p.config.ObjectName)
source := ""
artifacts := artifact.Files()
log.Println("Looking for image in artifact")
if len(artifacts) > 1 {
validSuffix := []string{"raw", "img", "qcow2", "vhdx", "vdi", "vmdk", "tar.bz2", "tar.xz", "tar.gz"}
for _, path := range artifact.Files() {
for _, suffix := range validSuffix {
if strings.HasSuffix(path, suffix) {
source = path
break
}
}
if source != "" {
break
}
}
} else {
source = artifact.Files()[0]
}
if source == "" {
return nil, false, fmt.Errorf("Image file not found")
}
spacesCreds := credentials.NewStaticCredentials(p.config.SpacesKey, p.config.SpacesSecret, "")
spacesEndpoint := fmt.Sprintf("https://%s.digitaloceanspaces.com", p.config.SpacesRegion)
spacesConfig := &aws.Config{
Credentials: spacesCreds,
Endpoint: aws.String(spacesEndpoint),
Region: aws.String(p.config.SpacesRegion),
LogLevel: aws.LogLevel(aws.LogDebugWithSigning),
Logger: &logger{
logger: log.New(os.Stderr, "", log.LstdFlags),
},
}
sess := session.New(spacesConfig)
ui.Message(fmt.Sprintf("Uploading %s to spaces://%s/%s", source, p.config.SpaceName, p.config.ObjectName))
err = uploadImageToSpaces(source, p, sess)
if err != nil {
return nil, false, err
}
ui.Message(fmt.Sprintf("Completed upload of %s to spaces://%s/%s", source, p.config.SpaceName, p.config.ObjectName))
client := godo.NewClient(oauth2.NewClient(oauth2.NoContext, &apiTokenSource{
AccessToken: p.config.APIToken,
}))
ui.Message(fmt.Sprintf("Started import of spaces://%s/%s", p.config.SpaceName, p.config.ObjectName))
image, err := importImageFromSpaces(p, client)
if err != nil {
return nil, false, err
}
ui.Message(fmt.Sprintf("Waiting for import of image %s to complete (may take a while)", p.config.Name))
err = waitUntilImageAvailable(client, image.ID, p.config.Timeout)
if err != nil {
return nil, false, fmt.Errorf("Import of image %s failed with error: %s", p.config.Name, err)
}
ui.Message(fmt.Sprintf("Import of image %s complete", p.config.Name))
if len(p.config.ImageRegions) > 1 {
// Remove the first region from the slice as the image is already there.
regions := p.config.ImageRegions
regions[0] = regions[len(regions)-1]
regions[len(regions)-1] = ""
regions = regions[:len(regions)-1]
ui.Message(fmt.Sprintf("Distributing image %s to additional regions: %v", p.config.Name, regions))
err = distributeImageToRegions(client, image.ID, regions, p.config.Timeout)
if err != nil {
return nil, false, err
}
}
log.Printf("Adding created image ID %v to output artifacts", image.ID)
artifact = &digitalocean.Artifact{
SnapshotName: image.Name,
SnapshotId: image.ID,
RegionNames: p.config.ImageRegions,
Client: client,
}
if !p.config.SkipClean {
ui.Message(fmt.Sprintf("Deleting import source spaces://%s/%s", p.config.SpaceName, p.config.ObjectName))
err = deleteImageFromSpaces(p, sess)
if err != nil {
return nil, false, err
}
}
return artifact, false, nil
}
func uploadImageToSpaces(source string, p *PostProcessor, s *session.Session) (err error) {
file, err := os.Open(source)
if err != nil {
return fmt.Errorf("Failed to open %s: %s", source, err)
}
uploader := s3manager.NewUploader(s)
_, err = uploader.Upload(&s3manager.UploadInput{
Body: file,
Bucket: &p.config.SpaceName,
Key: &p.config.ObjectName,
ACL: aws.String("public-read"),
})
if err != nil {
return fmt.Errorf("Failed to upload %s: %s", source, err)
}
file.Close()
return nil
}
func importImageFromSpaces(p *PostProcessor, client *godo.Client) (image *godo.Image, err error) {
log.Printf("Importing custom image from spaces://%s/%s", p.config.SpaceName, p.config.ObjectName)
url := fmt.Sprintf("https://%s.%s.digitaloceanspaces.com/%s", p.config.SpaceName, p.config.SpacesRegion, p.config.ObjectName)
createRequest := &godo.CustomImageCreateRequest{
Name: p.config.Name,
Url: url,
Region: p.config.ImageRegions[0],
Distribution: p.config.Distribution,
Description: p.config.Description,
Tags: p.config.Tags,
}
image, _, err = client.Images.Create(context.TODO(), createRequest)
if err != nil {
return image, fmt.Errorf("Failed to import from spaces://%s/%s: %s", p.config.SpaceName, p.config.ObjectName, err)
}
return image, nil
}
func waitUntilImageAvailable(client *godo.Client, imageId int, timeout time.Duration) (err error) {
done := make(chan struct{})
defer close(done)
result := make(chan error, 1)
go func() {
attempts := 0
for {
attempts += 1
log.Printf("Waiting for image to become available... (attempt: %d)", attempts)
image, _, err := client.Images.GetByID(context.TODO(), imageId)
if err != nil {
result <- err
return
}
if image.Status == "available" {
result <- nil
return
}
if image.ErrorMessage != "" {
result <- fmt.Errorf("%v", image.ErrorMessage)
return
}
time.Sleep(3 * time.Second)
select {
case <-done:
return
default:
}
}
}()
log.Printf("Waiting for up to %d seconds for image to become available", timeout/time.Second)
select {
case err := <-result:
return err
case <-time.After(timeout):
err := fmt.Errorf("Timeout while waiting to for action to become available")
return err
}
}
func distributeImageToRegions(client *godo.Client, imageId int, regions []string, timeout time.Duration) (err error) {
for _, region := range regions {
transferRequest := &godo.ActionRequest{
"type": "transfer",
"region": region,
}
log.Printf("Transferring image to %s", region)
action, _, err := client.ImageActions.Transfer(context.TODO(), imageId, transferRequest)
if err != nil {
return fmt.Errorf("Error transferring image: %s", err)
}
if err := digitalocean.WaitForImageState(godo.ActionCompleted, imageId, action.ID, client, timeout); err != nil {
if err != nil {
return fmt.Errorf("Error transferring image: %s", err)
}
}
}
return nil
}
func deleteImageFromSpaces(p *PostProcessor, s *session.Session) (err error) {
s3conn := s3.New(s)
_, err = s3conn.DeleteObject(&s3.DeleteObjectInput{
Bucket: &p.config.SpaceName,
Key: &p.config.ObjectName,
})
if err != nil {
return fmt.Errorf("Failed to delete spaces://%s/%s: %s", p.config.SpaceName, p.config.ObjectName, err)
}
return nil
}

View File

@ -0,0 +1,32 @@
package digitaloceanimport
import (
"bytes"
"testing"
"github.com/hashicorp/packer/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{}
}
func testPP(t *testing.T) *PostProcessor {
var p PostProcessor
if err := p.Configure(testConfig()); err != nil {
t.Fatalf("err: %s", err)
}
return &p
}
func testUi() *packer.BasicUi {
return &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
}
}
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
var _ packer.PostProcessor = new(PostProcessor)
}

View File

@ -1,5 +1,61 @@
# Change Log
## [v1.6.0] - 2018-10-16
- #185 Projects support [beta] - @mchitten
## [v1.5.0] - 2018-10-01
- #181 Adding tagging images support - @hugocorbucci
## [v1.4.2] - 2018-08-30
- #178 Allowing creating domain records with weight of 0 - @TFaga
- #177 Adding `VolumeLimit` to account - @lxfontes
## [v1.4.1] - 2018-08-23
- #176 Fix cdn flush cache API endpoint - @sunny-b
## [v1.4.0] - 2018-08-22
- #175 Add support for Spaces CDN - @sunny-b
## [v1.3.0] - 2018-05-24
- #170 Add support for volume formatting - @adamwg
## [v1.2.0] - 2018-05-08
- #166 Remove support for Go 1.6 - @iheanyi
- #165 Add support for Let's Encrypt Certificates - @viola
## [v1.1.3] - 2018-03-07
- #156 Handle non-json errors from the API - @aknuds1
- #158 Update droplet example to use latest instance type - @dan-v
## [v1.1.2] - 2018-03-06
- #157 storage: list volumes should handle only name or only region params - @andrewsykim
- #154 docs: replace first example with fully-runnable example - @xmudrii
- #152 Handle flags & tag properties of domain record - @jaymecd
## [v1.1.1] - 2017-09-29
- #151 Following user agent field recommendations - @joonas
- #148 AsRequest method to create load balancers requests - @lukegb
## [v1.1.0] - 2017-06-06
### Added
- #145 Add FirewallsService for managing Firewalls with the DigitalOcean API. - @viola
- #139 Add TTL field to the Domains. - @xmudrii
### Fixed
- #143 Fix oauth2.NoContext depreciation. - @jbowens
- #141 Fix DropletActions on tagged resources. - @xmudrii
## [v1.0.0] - 2017-03-10
### Added

View File

@ -12,6 +12,7 @@ Assuming your `$GOPATH` is set up according to your desires, run:
```sh
go get github.com/digitalocean/godo
go get -u github.com/stretchr/testify/assert
```
## Running tests

View File

@ -27,25 +27,37 @@ at the DigitalOcean Control Panel [Applications Page](https://cloud.digitalocean
You can then use your token to create a new client:
```go
import "golang.org/x/oauth2"
package main
import (
"context"
"github.com/digitalocean/godo"
"golang.org/x/oauth2"
)
const (
pat = "mytoken"
)
pat := "mytoken"
type TokenSource struct {
AccessToken string
AccessToken string
}
func (t *TokenSource) Token() (*oauth2.Token, error) {
token := &oauth2.Token{
AccessToken: t.AccessToken,
}
return token, nil
token := &oauth2.Token{
AccessToken: t.AccessToken,
}
return token, nil
}
tokenSource := &TokenSource{
AccessToken: pat,
func main() {
tokenSource := &TokenSource{
AccessToken: pat,
}
oauthClient := oauth2.NewClient(context.Background(), tokenSource)
client := godo.NewClient(oauthClient)
}
oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)
client := godo.NewClient(oauthClient)
```
## Examples
@ -59,7 +71,7 @@ dropletName := "super-cool-droplet"
createRequest := &godo.DropletCreateRequest{
Name: dropletName,
Region: "nyc3",
Size: "512mb",
Size: "s-1vcpu-1gb",
Image: godo.DropletCreateImage{
Slug: "ubuntu-14-04-x64",
},

View File

@ -1,6 +1,9 @@
package godo
import "context"
import (
"context"
"net/http"
)
// AccountService is an interface for interfacing with the Account
// endpoints of the DigitalOcean API
@ -21,6 +24,7 @@ var _ AccountService = &AccountServiceOp{}
type Account struct {
DropletLimit int `json:"droplet_limit,omitempty"`
FloatingIPLimit int `json:"floating_ip_limit,omitempty"`
VolumeLimit int `json:"volume_limit,omitempty"`
Email string `json:"email,omitempty"`
UUID string `json:"uuid,omitempty"`
EmailVerified bool `json:"email_verified,omitempty"`
@ -41,13 +45,13 @@ func (s *AccountServiceOp) Get(ctx context.Context) (*Account, *Response, error)
path := "v2/account"
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(accountRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

View File

@ -3,6 +3,7 @@ package godo
import (
"context"
"fmt"
"net/http"
)
const (
@ -60,13 +61,13 @@ func (s *ActionsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Action
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(actionsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -84,13 +85,13 @@ func (s *ActionsServiceOp) Get(ctx context.Context, id int) (*Action, *Response,
}
path := fmt.Sprintf("%s/%d", actionsBasePath, id)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(actionRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

195
vendor/github.com/digitalocean/godo/cdn.go generated vendored Normal file
View File

@ -0,0 +1,195 @@
package godo
import (
"context"
"fmt"
"net/http"
"time"
)
const cdnBasePath = "v2/cdn/endpoints"
// CDNService is an interface for managing Spaces CDN with the DigitalOcean API.
type CDNService interface {
List(context.Context, *ListOptions) ([]CDN, *Response, error)
Get(context.Context, string) (*CDN, *Response, error)
Create(context.Context, *CDNCreateRequest) (*CDN, *Response, error)
UpdateTTL(context.Context, string, *CDNUpdateRequest) (*CDN, *Response, error)
FlushCache(context.Context, string, *CDNFlushCacheRequest) (*Response, error)
Delete(context.Context, string) (*Response, error)
}
// CDNServiceOp handles communication with the CDN related methods of the
// DigitalOcean API.
type CDNServiceOp struct {
client *Client
}
var _ CDNService = &CDNServiceOp{}
// CDN represents a DigitalOcean CDN
type CDN struct {
ID string `json:"id"`
Origin string `json:"origin"`
Endpoint string `json:"endpoint"`
CreatedAt time.Time `json:"created_at"`
TTL uint32 `json:"ttl"`
}
// CDNRoot represents a response from the DigitalOcean API
type cdnRoot struct {
Endpoint *CDN `json:"endpoint"`
}
type cdnsRoot struct {
Endpoints []CDN `json:"endpoints"`
Links *Links `json:"links"`
}
// CDNCreateRequest represents a request to create a CDN.
type CDNCreateRequest struct {
Origin string `json:"origin"`
TTL uint32 `json:"ttl"`
}
// CDNUpdateRequest represents a request to update the ttl of a CDN.
type CDNUpdateRequest struct {
TTL uint32 `json:"ttl"`
}
// CDNFlushCacheRequest represents a request to flush cache of a CDN.
type CDNFlushCacheRequest struct {
Files []string `json:"files"`
}
// List all CDN endpoints
func (c CDNServiceOp) List(ctx context.Context, opt *ListOptions) ([]CDN, *Response, error) {
path, err := addOptions(cdnBasePath, opt)
if err != nil {
return nil, nil, err
}
req, err := c.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(cdnsRoot)
resp, err := c.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
return root.Endpoints, resp, err
}
// Get individual CDN. It requires a non-empty cdn id.
func (c CDNServiceOp) Get(ctx context.Context, id string) (*CDN, *Response, error) {
if len(id) == 0 {
return nil, nil, NewArgError("id", "cannot be an empty string")
}
path := fmt.Sprintf("%s/%s", cdnBasePath, id)
req, err := c.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(cdnRoot)
resp, err := c.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Endpoint, resp, err
}
// Create a new CDN
func (c CDNServiceOp) Create(ctx context.Context, createRequest *CDNCreateRequest) (*CDN, *Response, error) {
if createRequest == nil {
return nil, nil, NewArgError("createRequest", "cannot be nil")
}
req, err := c.client.NewRequest(ctx, http.MethodPost, cdnBasePath, createRequest)
if err != nil {
return nil, nil, err
}
root := new(cdnRoot)
resp, err := c.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Endpoint, resp, err
}
// UpdateTTL updates the ttl of individual CDN
func (c CDNServiceOp) UpdateTTL(ctx context.Context, id string, updateRequest *CDNUpdateRequest) (*CDN, *Response, error) {
if updateRequest == nil {
return nil, nil, NewArgError("updateRequest", "cannot be nil")
}
if len(id) == 0 {
return nil, nil, NewArgError("id", "cannot be an empty string")
}
path := fmt.Sprintf("%s/%s", cdnBasePath, id)
req, err := c.client.NewRequest(ctx, http.MethodPut, path, updateRequest)
if err != nil {
return nil, nil, err
}
root := new(cdnRoot)
resp, err := c.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Endpoint, resp, err
}
// FlushCache flushes the cache of an individual CDN. Requires a non-empty slice of file paths and/or wildcards
func (c CDNServiceOp) FlushCache(ctx context.Context, id string, flushCacheRequest *CDNFlushCacheRequest) (*Response, error) {
if flushCacheRequest == nil {
return nil, NewArgError("flushCacheRequest", "cannot be nil")
}
if len(id) == 0 {
return nil, NewArgError("id", "cannot be an empty string")
}
path := fmt.Sprintf("%s/%s/cache", cdnBasePath, id)
req, err := c.client.NewRequest(ctx, http.MethodDelete, path, flushCacheRequest)
if err != nil {
return nil, err
}
resp, err := c.client.Do(ctx, req, nil)
return resp, err
}
// Delete an individual CDN
func (c CDNServiceOp) Delete(ctx context.Context, id string) (*Response, error) {
if len(id) == 0 {
return nil, NewArgError("id", "cannot be an empty string")
}
path := fmt.Sprintf("%s/%s", cdnBasePath, id)
req, err := c.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
resp, err := c.client.Do(ctx, req, nil)
return resp, err
}

View File

@ -2,6 +2,7 @@ package godo
import (
"context"
"net/http"
"path"
)
@ -18,19 +19,24 @@ type CertificatesService interface {
// Certificate represents a DigitalOcean certificate configuration.
type Certificate struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
NotAfter string `json:"not_after,omitempty"`
SHA1Fingerprint string `json:"sha1_fingerprint,omitempty"`
Created string `json:"created_at,omitempty"`
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
DNSNames []string `json:"dns_names,omitempty"`
NotAfter string `json:"not_after,omitempty"`
SHA1Fingerprint string `json:"sha1_fingerprint,omitempty"`
Created string `json:"created_at,omitempty"`
State string `json:"state,omitempty"`
Type string `json:"type,omitempty"`
}
// CertificateRequest represents configuration for a new certificate.
type CertificateRequest struct {
Name string `json:"name,omitempty"`
PrivateKey string `json:"private_key,omitempty"`
LeafCertificate string `json:"leaf_certificate,omitempty"`
CertificateChain string `json:"certificate_chain,omitempty"`
Name string `json:"name,omitempty"`
DNSNames []string `json:"dns_names,omitempty"`
PrivateKey string `json:"private_key,omitempty"`
LeafCertificate string `json:"leaf_certificate,omitempty"`
CertificateChain string `json:"certificate_chain,omitempty"`
Type string `json:"type,omitempty"`
}
type certificateRoot struct {
@ -53,13 +59,13 @@ var _ CertificatesService = &CertificatesServiceOp{}
func (c *CertificatesServiceOp) Get(ctx context.Context, cID string) (*Certificate, *Response, error) {
urlStr := path.Join(certificatesBasePath, cID)
req, err := c.client.NewRequest(ctx, "GET", urlStr, nil)
req, err := c.client.NewRequest(ctx, http.MethodGet, urlStr, nil)
if err != nil {
return nil, nil, err
}
root := new(certificateRoot)
resp, err := c.client.Do(req, root)
resp, err := c.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -74,13 +80,13 @@ func (c *CertificatesServiceOp) List(ctx context.Context, opt *ListOptions) ([]C
return nil, nil, err
}
req, err := c.client.NewRequest(ctx, "GET", urlStr, nil)
req, err := c.client.NewRequest(ctx, http.MethodGet, urlStr, nil)
if err != nil {
return nil, nil, err
}
root := new(certificatesRoot)
resp, err := c.client.Do(req, root)
resp, err := c.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -93,13 +99,13 @@ func (c *CertificatesServiceOp) List(ctx context.Context, opt *ListOptions) ([]C
// Create a new certificate with provided configuration.
func (c *CertificatesServiceOp) Create(ctx context.Context, cr *CertificateRequest) (*Certificate, *Response, error) {
req, err := c.client.NewRequest(ctx, "POST", certificatesBasePath, cr)
req, err := c.client.NewRequest(ctx, http.MethodPost, certificatesBasePath, cr)
if err != nil {
return nil, nil, err
}
root := new(certificateRoot)
resp, err := c.client.Do(req, root)
resp, err := c.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -111,10 +117,10 @@ func (c *CertificatesServiceOp) Create(ctx context.Context, cr *CertificateReque
func (c *CertificatesServiceOp) Delete(ctx context.Context, cID string) (*Response, error) {
urlStr := path.Join(certificatesBasePath, cID)
req, err := c.client.NewRequest(ctx, "DELETE", urlStr, nil)
req, err := c.client.NewRequest(ctx, http.MethodDelete, urlStr, nil)
if err != nil {
return nil, err
}
return c.client.Do(req, nil)
return c.client.Do(ctx, req, nil)
}

View File

@ -3,6 +3,7 @@ package godo
import (
"context"
"fmt"
"net/http"
)
const domainsBasePath = "v2/domains"
@ -51,7 +52,7 @@ type domainsRoot struct {
// DomainCreateRequest respresents a request to create a domain.
type DomainCreateRequest struct {
Name string `json:"name"`
IPAddress string `json:"ip_address"`
IPAddress string `json:"ip_address,omitempty"`
}
// DomainRecordRoot is the root of an individual Domain Record response
@ -71,9 +72,12 @@ type DomainRecord struct {
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Data string `json:"data,omitempty"`
Priority int `json:"priority,omitempty"`
Priority int `json:"priority"`
Port int `json:"port,omitempty"`
Weight int `json:"weight,omitempty"`
TTL int `json:"ttl,omitempty"`
Weight int `json:"weight"`
Flags int `json:"flags"`
Tag string `json:"tag,omitempty"`
}
// DomainRecordEditRequest represents a request to update a domain record.
@ -81,15 +85,22 @@ type DomainRecordEditRequest struct {
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Data string `json:"data,omitempty"`
Priority int `json:"priority,omitempty"`
Priority int `json:"priority"`
Port int `json:"port,omitempty"`
Weight int `json:"weight,omitempty"`
TTL int `json:"ttl,omitempty"`
Weight int `json:"weight"`
Flags int `json:"flags"`
Tag string `json:"tag,omitempty"`
}
func (d Domain) String() string {
return Stringify(d)
}
func (d Domain) URN() string {
return ToURN("Domain", d.Name)
}
// List all domains.
func (s DomainsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Domain, *Response, error) {
path := domainsBasePath
@ -98,13 +109,13 @@ func (s DomainsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Domain,
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(domainsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -123,13 +134,13 @@ func (s *DomainsServiceOp) Get(ctx context.Context, name string) (*Domain, *Resp
path := fmt.Sprintf("%s/%s", domainsBasePath, name)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(domainRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -145,13 +156,13 @@ func (s *DomainsServiceOp) Create(ctx context.Context, createRequest *DomainCrea
path := domainsBasePath
req, err := s.client.NewRequest(ctx, "POST", path, createRequest)
req, err := s.client.NewRequest(ctx, http.MethodPost, path, createRequest)
if err != nil {
return nil, nil, err
}
root := new(domainRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -166,12 +177,12 @@ func (s *DomainsServiceOp) Delete(ctx context.Context, name string) (*Response,
path := fmt.Sprintf("%s/%s", domainsBasePath, name)
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
resp, err := s.client.Do(ctx, req, nil)
return resp, err
}
@ -198,13 +209,13 @@ func (s *DomainsServiceOp) Records(ctx context.Context, domain string, opt *List
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(domainRecordsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -227,13 +238,13 @@ func (s *DomainsServiceOp) Record(ctx context.Context, domain string, id int) (*
path := fmt.Sprintf("%s/%s/records/%d", domainsBasePath, domain, id)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
record := new(domainRecordRoot)
resp, err := s.client.Do(req, record)
resp, err := s.client.Do(ctx, req, record)
if err != nil {
return nil, resp, err
}
@ -253,12 +264,12 @@ func (s *DomainsServiceOp) DeleteRecord(ctx context.Context, domain string, id i
path := fmt.Sprintf("%s/%s/records/%d", domainsBasePath, domain, id)
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
resp, err := s.client.Do(ctx, req, nil)
return resp, err
}
@ -289,7 +300,7 @@ func (s *DomainsServiceOp) EditRecord(ctx context.Context,
}
d := new(DomainRecord)
resp, err := s.client.Do(req, d)
resp, err := s.client.Do(ctx, req, d)
if err != nil {
return nil, resp, err
}
@ -310,14 +321,14 @@ func (s *DomainsServiceOp) CreateRecord(ctx context.Context,
}
path := fmt.Sprintf("%s/%s/records", domainsBasePath, domain)
req, err := s.client.NewRequest(ctx, "POST", path, createRequest)
req, err := s.client.NewRequest(ctx, http.MethodPost, path, createRequest)
if err != nil {
return nil, nil, err
}
d := new(domainRecordRoot)
resp, err := s.client.Do(req, d)
resp, err := s.client.Do(ctx, req, d)
if err != nil {
return nil, resp, err
}

View File

@ -3,6 +3,7 @@ package godo
import (
"context"
"fmt"
"net/http"
"net/url"
)
@ -14,32 +15,31 @@ type ActionRequest map[string]interface{}
// See: https://developers.digitalocean.com/documentation/v2#droplet-actions
type DropletActionsService interface {
Shutdown(context.Context, int) (*Action, *Response, error)
ShutdownByTag(context.Context, string) (*Action, *Response, error)
ShutdownByTag(context.Context, string) ([]Action, *Response, error)
PowerOff(context.Context, int) (*Action, *Response, error)
PowerOffByTag(context.Context, string) (*Action, *Response, error)
PowerOffByTag(context.Context, string) ([]Action, *Response, error)
PowerOn(context.Context, int) (*Action, *Response, error)
PowerOnByTag(context.Context, string) (*Action, *Response, error)
PowerOnByTag(context.Context, string) ([]Action, *Response, error)
PowerCycle(context.Context, int) (*Action, *Response, error)
PowerCycleByTag(context.Context, string) (*Action, *Response, error)
PowerCycleByTag(context.Context, string) ([]Action, *Response, error)
Reboot(context.Context, int) (*Action, *Response, error)
Restore(context.Context, int, int) (*Action, *Response, error)
Resize(context.Context, int, string, bool) (*Action, *Response, error)
Rename(context.Context, int, string) (*Action, *Response, error)
Snapshot(context.Context, int, string) (*Action, *Response, error)
SnapshotByTag(context.Context, string, string) (*Action, *Response, error)
SnapshotByTag(context.Context, string, string) ([]Action, *Response, error)
EnableBackups(context.Context, int) (*Action, *Response, error)
EnableBackupsByTag(context.Context, string) (*Action, *Response, error)
EnableBackupsByTag(context.Context, string) ([]Action, *Response, error)
DisableBackups(context.Context, int) (*Action, *Response, error)
DisableBackupsByTag(context.Context, string) (*Action, *Response, error)
DisableBackupsByTag(context.Context, string) ([]Action, *Response, error)
PasswordReset(context.Context, int) (*Action, *Response, error)
RebuildByImageID(context.Context, int, int) (*Action, *Response, error)
RebuildByImageSlug(context.Context, int, string) (*Action, *Response, error)
ChangeKernel(context.Context, int, int) (*Action, *Response, error)
EnableIPv6(context.Context, int) (*Action, *Response, error)
EnableIPv6ByTag(context.Context, string) (*Action, *Response, error)
EnableIPv6ByTag(context.Context, string) ([]Action, *Response, error)
EnablePrivateNetworking(context.Context, int) (*Action, *Response, error)
EnablePrivateNetworkingByTag(context.Context, string) (*Action, *Response, error)
Upgrade(context.Context, int) (*Action, *Response, error)
EnablePrivateNetworkingByTag(context.Context, string) ([]Action, *Response, error)
Get(context.Context, int, int) (*Action, *Response, error)
GetByURI(context.Context, string) (*Action, *Response, error)
}
@ -59,7 +59,7 @@ func (s *DropletActionsServiceOp) Shutdown(ctx context.Context, id int) (*Action
}
// ShutdownByTag shuts down Droplets matched by a Tag.
func (s *DropletActionsServiceOp) ShutdownByTag(ctx context.Context, tag string) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) ShutdownByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
request := &ActionRequest{"type": "shutdown"}
return s.doActionByTag(ctx, tag, request)
}
@ -71,7 +71,7 @@ func (s *DropletActionsServiceOp) PowerOff(ctx context.Context, id int) (*Action
}
// PowerOffByTag powers off Droplets matched by a Tag.
func (s *DropletActionsServiceOp) PowerOffByTag(ctx context.Context, tag string) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) PowerOffByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
request := &ActionRequest{"type": "power_off"}
return s.doActionByTag(ctx, tag, request)
}
@ -83,7 +83,7 @@ func (s *DropletActionsServiceOp) PowerOn(ctx context.Context, id int) (*Action,
}
// PowerOnByTag powers on Droplets matched by a Tag.
func (s *DropletActionsServiceOp) PowerOnByTag(ctx context.Context, tag string) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) PowerOnByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
request := &ActionRequest{"type": "power_on"}
return s.doActionByTag(ctx, tag, request)
}
@ -95,7 +95,7 @@ func (s *DropletActionsServiceOp) PowerCycle(ctx context.Context, id int) (*Acti
}
// PowerCycleByTag power cycles Droplets matched by a Tag.
func (s *DropletActionsServiceOp) PowerCycleByTag(ctx context.Context, tag string) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) PowerCycleByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
request := &ActionRequest{"type": "power_cycle"}
return s.doActionByTag(ctx, tag, request)
}
@ -148,7 +148,7 @@ func (s *DropletActionsServiceOp) Snapshot(ctx context.Context, id int, name str
}
// SnapshotByTag snapshots Droplets matched by a Tag.
func (s *DropletActionsServiceOp) SnapshotByTag(ctx context.Context, tag string, name string) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) SnapshotByTag(ctx context.Context, tag string, name string) ([]Action, *Response, error) {
requestType := "snapshot"
request := &ActionRequest{
"type": requestType,
@ -164,7 +164,7 @@ func (s *DropletActionsServiceOp) EnableBackups(ctx context.Context, id int) (*A
}
// EnableBackupsByTag enables backups for Droplets matched by a Tag.
func (s *DropletActionsServiceOp) EnableBackupsByTag(ctx context.Context, tag string) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) EnableBackupsByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
request := &ActionRequest{"type": "enable_backups"}
return s.doActionByTag(ctx, tag, request)
}
@ -176,7 +176,7 @@ func (s *DropletActionsServiceOp) DisableBackups(ctx context.Context, id int) (*
}
// DisableBackupsByTag disables backups for Droplet matched by a Tag.
func (s *DropletActionsServiceOp) DisableBackupsByTag(ctx context.Context, tag string) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) DisableBackupsByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
request := &ActionRequest{"type": "disable_backups"}
return s.doActionByTag(ctx, tag, request)
}
@ -212,7 +212,7 @@ func (s *DropletActionsServiceOp) EnableIPv6(ctx context.Context, id int) (*Acti
}
// EnableIPv6ByTag enables IPv6 for Droplets matched by a Tag.
func (s *DropletActionsServiceOp) EnableIPv6ByTag(ctx context.Context, tag string) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) EnableIPv6ByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
request := &ActionRequest{"type": "enable_ipv6"}
return s.doActionByTag(ctx, tag, request)
}
@ -224,17 +224,11 @@ func (s *DropletActionsServiceOp) EnablePrivateNetworking(ctx context.Context, i
}
// EnablePrivateNetworkingByTag enables private networking for Droplets matched by a Tag.
func (s *DropletActionsServiceOp) EnablePrivateNetworkingByTag(ctx context.Context, tag string) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) EnablePrivateNetworkingByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
request := &ActionRequest{"type": "enable_private_networking"}
return s.doActionByTag(ctx, tag, request)
}
// Upgrade a Droplet.
func (s *DropletActionsServiceOp) Upgrade(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "upgrade"}
return s.doAction(ctx, id, request)
}
func (s *DropletActionsServiceOp) doAction(ctx context.Context, id int, request *ActionRequest) (*Action, *Response, error) {
if id < 1 {
return nil, nil, NewArgError("id", "cannot be less than 1")
@ -246,13 +240,13 @@ func (s *DropletActionsServiceOp) doAction(ctx context.Context, id int, request
path := dropletActionPath(id)
req, err := s.client.NewRequest(ctx, "POST", path, request)
req, err := s.client.NewRequest(ctx, http.MethodPost, path, request)
if err != nil {
return nil, nil, err
}
root := new(actionRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -260,7 +254,7 @@ func (s *DropletActionsServiceOp) doAction(ctx context.Context, id int, request
return root.Event, resp, err
}
func (s *DropletActionsServiceOp) doActionByTag(ctx context.Context, tag string, request *ActionRequest) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) doActionByTag(ctx context.Context, tag string, request *ActionRequest) ([]Action, *Response, error) {
if tag == "" {
return nil, nil, NewArgError("tag", "cannot be empty")
}
@ -271,18 +265,18 @@ func (s *DropletActionsServiceOp) doActionByTag(ctx context.Context, tag string,
path := dropletActionPathByTag(tag)
req, err := s.client.NewRequest(ctx, "POST", path, request)
req, err := s.client.NewRequest(ctx, http.MethodPost, path, request)
if err != nil {
return nil, nil, err
}
root := new(actionRoot)
resp, err := s.client.Do(req, root)
root := new(actionsRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Event, resp, err
return root.Actions, resp, err
}
// Get an action for a particular Droplet by id.
@ -311,13 +305,13 @@ func (s *DropletActionsServiceOp) GetByURI(ctx context.Context, rawurl string) (
}
func (s *DropletActionsServiceOp) get(ctx context.Context, path string) (*Action, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(actionRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
)
const dropletBasePath = "v2/droplets"
@ -124,6 +125,10 @@ func (d Droplet) String() string {
return Stringify(d)
}
func (d Droplet) URN() string {
return ToURN("Droplet", d.ID)
}
// DropletRoot represents a Droplet root
type dropletRoot struct {
Droplet *Droplet `json:"droplet"`
@ -274,13 +279,13 @@ func (n NetworkV6) String() string {
// Performs a list request given a path.
func (s *DropletsServiceOp) list(ctx context.Context, path string) ([]Droplet, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(dropletsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -321,13 +326,13 @@ func (s *DropletsServiceOp) Get(ctx context.Context, dropletID int) (*Droplet, *
path := fmt.Sprintf("%s/%d", dropletBasePath, dropletID)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(dropletRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -343,13 +348,13 @@ func (s *DropletsServiceOp) Create(ctx context.Context, createRequest *DropletCr
path := dropletBasePath
req, err := s.client.NewRequest(ctx, "POST", path, createRequest)
req, err := s.client.NewRequest(ctx, http.MethodPost, path, createRequest)
if err != nil {
return nil, nil, err
}
root := new(dropletRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -368,13 +373,13 @@ func (s *DropletsServiceOp) CreateMultiple(ctx context.Context, createRequest *D
path := dropletBasePath
req, err := s.client.NewRequest(ctx, "POST", path, createRequest)
req, err := s.client.NewRequest(ctx, http.MethodPost, path, createRequest)
if err != nil {
return nil, nil, err
}
root := new(dropletsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -387,12 +392,12 @@ func (s *DropletsServiceOp) CreateMultiple(ctx context.Context, createRequest *D
// Performs a delete request given a path
func (s *DropletsServiceOp) delete(ctx context.Context, path string) (*Response, error) {
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
resp, err := s.client.Do(ctx, req, nil)
return resp, err
}
@ -431,13 +436,13 @@ func (s *DropletsServiceOp) Kernels(ctx context.Context, dropletID int, opt *Lis
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(kernelsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if l := root.Links; l != nil {
resp.Links = l
}
@ -457,13 +462,13 @@ func (s *DropletsServiceOp) Actions(ctx context.Context, dropletID int, opt *Lis
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(actionsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -486,13 +491,13 @@ func (s *DropletsServiceOp) Backups(ctx context.Context, dropletID int, opt *Lis
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(backupsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -515,13 +520,13 @@ func (s *DropletsServiceOp) Snapshots(ctx context.Context, dropletID int, opt *L
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(dropletSnapshotsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -540,13 +545,13 @@ func (s *DropletsServiceOp) Neighbors(ctx context.Context, dropletID int) ([]Dro
path := fmt.Sprintf("%s/%d/neighbors", dropletBasePath, dropletID)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(dropletsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

267
vendor/github.com/digitalocean/godo/firewalls.go generated vendored Normal file
View File

@ -0,0 +1,267 @@
package godo
import (
"context"
"net/http"
"path"
"strconv"
)
const firewallsBasePath = "/v2/firewalls"
// FirewallsService is an interface for managing Firewalls with the DigitalOcean API.
// See: https://developers.digitalocean.com/documentation/documentation/v2/#firewalls
type FirewallsService interface {
Get(context.Context, string) (*Firewall, *Response, error)
Create(context.Context, *FirewallRequest) (*Firewall, *Response, error)
Update(context.Context, string, *FirewallRequest) (*Firewall, *Response, error)
Delete(context.Context, string) (*Response, error)
List(context.Context, *ListOptions) ([]Firewall, *Response, error)
ListByDroplet(context.Context, int, *ListOptions) ([]Firewall, *Response, error)
AddDroplets(context.Context, string, ...int) (*Response, error)
RemoveDroplets(context.Context, string, ...int) (*Response, error)
AddTags(context.Context, string, ...string) (*Response, error)
RemoveTags(context.Context, string, ...string) (*Response, error)
AddRules(context.Context, string, *FirewallRulesRequest) (*Response, error)
RemoveRules(context.Context, string, *FirewallRulesRequest) (*Response, error)
}
// FirewallsServiceOp handles communication with Firewalls methods of the DigitalOcean API.
type FirewallsServiceOp struct {
client *Client
}
// Firewall represents a DigitalOcean Firewall configuration.
type Firewall struct {
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
InboundRules []InboundRule `json:"inbound_rules"`
OutboundRules []OutboundRule `json:"outbound_rules"`
DropletIDs []int `json:"droplet_ids"`
Tags []string `json:"tags"`
Created string `json:"created_at"`
PendingChanges []PendingChange `json:"pending_changes"`
}
// String creates a human-readable description of a Firewall.
func (fw Firewall) String() string {
return Stringify(fw)
}
func (fw Firewall) URN() string {
return ToURN("Firewall", fw.ID)
}
// FirewallRequest represents the configuration to be applied to an existing or a new Firewall.
type FirewallRequest struct {
Name string `json:"name"`
InboundRules []InboundRule `json:"inbound_rules"`
OutboundRules []OutboundRule `json:"outbound_rules"`
DropletIDs []int `json:"droplet_ids"`
Tags []string `json:"tags"`
}
// FirewallRulesRequest represents rules configuration to be applied to an existing Firewall.
type FirewallRulesRequest struct {
InboundRules []InboundRule `json:"inbound_rules"`
OutboundRules []OutboundRule `json:"outbound_rules"`
}
// InboundRule represents a DigitalOcean Firewall inbound rule.
type InboundRule struct {
Protocol string `json:"protocol,omitempty"`
PortRange string `json:"ports,omitempty"`
Sources *Sources `json:"sources"`
}
// OutboundRule represents a DigitalOcean Firewall outbound rule.
type OutboundRule struct {
Protocol string `json:"protocol,omitempty"`
PortRange string `json:"ports,omitempty"`
Destinations *Destinations `json:"destinations"`
}
// Sources represents a DigitalOcean Firewall InboundRule sources.
type Sources struct {
Addresses []string `json:"addresses,omitempty"`
Tags []string `json:"tags,omitempty"`
DropletIDs []int `json:"droplet_ids,omitempty"`
LoadBalancerUIDs []string `json:"load_balancer_uids,omitempty"`
}
// PendingChange represents a DigitalOcean Firewall status details.
type PendingChange struct {
DropletID int `json:"droplet_id,omitempty"`
Removing bool `json:"removing,omitempty"`
Status string `json:"status,omitempty"`
}
// Destinations represents a DigitalOcean Firewall OutboundRule destinations.
type Destinations struct {
Addresses []string `json:"addresses,omitempty"`
Tags []string `json:"tags,omitempty"`
DropletIDs []int `json:"droplet_ids,omitempty"`
LoadBalancerUIDs []string `json:"load_balancer_uids,omitempty"`
}
var _ FirewallsService = &FirewallsServiceOp{}
// Get an existing Firewall by its identifier.
func (fw *FirewallsServiceOp) Get(ctx context.Context, fID string) (*Firewall, *Response, error) {
path := path.Join(firewallsBasePath, fID)
req, err := fw.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(firewallRoot)
resp, err := fw.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Firewall, resp, err
}
// Create a new Firewall with a given configuration.
func (fw *FirewallsServiceOp) Create(ctx context.Context, fr *FirewallRequest) (*Firewall, *Response, error) {
req, err := fw.client.NewRequest(ctx, http.MethodPost, firewallsBasePath, fr)
if err != nil {
return nil, nil, err
}
root := new(firewallRoot)
resp, err := fw.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Firewall, resp, err
}
// Update an existing Firewall with new configuration.
func (fw *FirewallsServiceOp) Update(ctx context.Context, fID string, fr *FirewallRequest) (*Firewall, *Response, error) {
path := path.Join(firewallsBasePath, fID)
req, err := fw.client.NewRequest(ctx, "PUT", path, fr)
if err != nil {
return nil, nil, err
}
root := new(firewallRoot)
resp, err := fw.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Firewall, resp, err
}
// Delete a Firewall by its identifier.
func (fw *FirewallsServiceOp) Delete(ctx context.Context, fID string) (*Response, error) {
path := path.Join(firewallsBasePath, fID)
return fw.createAndDoReq(ctx, http.MethodDelete, path, nil)
}
// List Firewalls.
func (fw *FirewallsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Firewall, *Response, error) {
path, err := addOptions(firewallsBasePath, opt)
if err != nil {
return nil, nil, err
}
return fw.listHelper(ctx, path)
}
// ListByDroplet Firewalls.
func (fw *FirewallsServiceOp) ListByDroplet(ctx context.Context, dID int, opt *ListOptions) ([]Firewall, *Response, error) {
basePath := path.Join(dropletBasePath, strconv.Itoa(dID), "firewalls")
path, err := addOptions(basePath, opt)
if err != nil {
return nil, nil, err
}
return fw.listHelper(ctx, path)
}
// AddDroplets to a Firewall.
func (fw *FirewallsServiceOp) AddDroplets(ctx context.Context, fID string, dropletIDs ...int) (*Response, error) {
path := path.Join(firewallsBasePath, fID, "droplets")
return fw.createAndDoReq(ctx, http.MethodPost, path, &dropletsRequest{IDs: dropletIDs})
}
// RemoveDroplets from a Firewall.
func (fw *FirewallsServiceOp) RemoveDroplets(ctx context.Context, fID string, dropletIDs ...int) (*Response, error) {
path := path.Join(firewallsBasePath, fID, "droplets")
return fw.createAndDoReq(ctx, http.MethodDelete, path, &dropletsRequest{IDs: dropletIDs})
}
// AddTags to a Firewall.
func (fw *FirewallsServiceOp) AddTags(ctx context.Context, fID string, tags ...string) (*Response, error) {
path := path.Join(firewallsBasePath, fID, "tags")
return fw.createAndDoReq(ctx, http.MethodPost, path, &tagsRequest{Tags: tags})
}
// RemoveTags from a Firewall.
func (fw *FirewallsServiceOp) RemoveTags(ctx context.Context, fID string, tags ...string) (*Response, error) {
path := path.Join(firewallsBasePath, fID, "tags")
return fw.createAndDoReq(ctx, http.MethodDelete, path, &tagsRequest{Tags: tags})
}
// AddRules to a Firewall.
func (fw *FirewallsServiceOp) AddRules(ctx context.Context, fID string, rr *FirewallRulesRequest) (*Response, error) {
path := path.Join(firewallsBasePath, fID, "rules")
return fw.createAndDoReq(ctx, http.MethodPost, path, rr)
}
// RemoveRules from a Firewall.
func (fw *FirewallsServiceOp) RemoveRules(ctx context.Context, fID string, rr *FirewallRulesRequest) (*Response, error) {
path := path.Join(firewallsBasePath, fID, "rules")
return fw.createAndDoReq(ctx, http.MethodDelete, path, rr)
}
type dropletsRequest struct {
IDs []int `json:"droplet_ids"`
}
type tagsRequest struct {
Tags []string `json:"tags"`
}
type firewallRoot struct {
Firewall *Firewall `json:"firewall"`
}
type firewallsRoot struct {
Firewalls []Firewall `json:"firewalls"`
Links *Links `json:"links"`
}
func (fw *FirewallsServiceOp) createAndDoReq(ctx context.Context, method, path string, v interface{}) (*Response, error) {
req, err := fw.client.NewRequest(ctx, method, path, v)
if err != nil {
return nil, err
}
return fw.client.Do(ctx, req, nil)
}
func (fw *FirewallsServiceOp) listHelper(ctx context.Context, path string) ([]Firewall, *Response, error) {
req, err := fw.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(firewallsRoot)
resp, err := fw.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
return root.Firewalls, resp, err
}

View File

@ -3,6 +3,7 @@ package godo
import (
"context"
"fmt"
"net/http"
)
const floatingBasePath = "v2/floating_ips"
@ -36,6 +37,10 @@ func (f FloatingIP) String() string {
return Stringify(f)
}
func (f FloatingIP) URN() string {
return ToURN("FloatingIP", f.IP)
}
type floatingIPsRoot struct {
FloatingIPs []FloatingIP `json:"floating_ips"`
Links *Links `json:"links"`
@ -62,13 +67,13 @@ func (f *FloatingIPsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Fl
return nil, nil, err
}
req, err := f.client.NewRequest(ctx, "GET", path, nil)
req, err := f.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(floatingIPsRoot)
resp, err := f.client.Do(req, root)
resp, err := f.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -83,13 +88,13 @@ func (f *FloatingIPsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Fl
func (f *FloatingIPsServiceOp) Get(ctx context.Context, ip string) (*FloatingIP, *Response, error) {
path := fmt.Sprintf("%s/%s", floatingBasePath, ip)
req, err := f.client.NewRequest(ctx, "GET", path, nil)
req, err := f.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(floatingIPRoot)
resp, err := f.client.Do(req, root)
resp, err := f.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -102,13 +107,13 @@ func (f *FloatingIPsServiceOp) Get(ctx context.Context, ip string) (*FloatingIP,
func (f *FloatingIPsServiceOp) Create(ctx context.Context, createRequest *FloatingIPCreateRequest) (*FloatingIP, *Response, error) {
path := floatingBasePath
req, err := f.client.NewRequest(ctx, "POST", path, createRequest)
req, err := f.client.NewRequest(ctx, http.MethodPost, path, createRequest)
if err != nil {
return nil, nil, err
}
root := new(floatingIPRoot)
resp, err := f.client.Do(req, root)
resp, err := f.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -123,12 +128,12 @@ func (f *FloatingIPsServiceOp) Create(ctx context.Context, createRequest *Floati
func (f *FloatingIPsServiceOp) Delete(ctx context.Context, ip string) (*Response, error) {
path := fmt.Sprintf("%s/%s", floatingBasePath, ip)
req, err := f.client.NewRequest(ctx, "DELETE", path, nil)
req, err := f.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
resp, err := f.client.Do(req, nil)
resp, err := f.client.Do(ctx, req, nil)
return resp, err
}

View File

@ -3,6 +3,7 @@ package godo
import (
"context"
"fmt"
"net/http"
)
// FloatingIPActionsService is an interface for interfacing with the
@ -56,13 +57,13 @@ func (s *FloatingIPActionsServiceOp) List(ctx context.Context, ip string, opt *L
func (s *FloatingIPActionsServiceOp) doAction(ctx context.Context, ip string, request *ActionRequest) (*Action, *Response, error) {
path := floatingIPActionPath(ip)
req, err := s.client.NewRequest(ctx, "POST", path, request)
req, err := s.client.NewRequest(ctx, http.MethodPost, path, request)
if err != nil {
return nil, nil, err
}
root := new(actionRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -71,13 +72,13 @@ func (s *FloatingIPActionsServiceOp) doAction(ctx context.Context, ip string, re
}
func (s *FloatingIPActionsServiceOp) get(ctx context.Context, path string) (*Action, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(actionRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -86,13 +87,13 @@ func (s *FloatingIPActionsServiceOp) get(ctx context.Context, path string) (*Act
}
func (s *FloatingIPActionsServiceOp) list(ctx context.Context, path string) ([]Action, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(actionsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

View File

@ -18,7 +18,7 @@ import (
)
const (
libraryVersion = "1.0.0"
libraryVersion = "1.6.0"
defaultBaseURL = "https://api.digitalocean.com/"
userAgent = "godo/" + libraryVersion
mediaType = "application/json"
@ -46,6 +46,7 @@ type Client struct {
// Services used for communicating with the API
Account AccountService
Actions ActionsService
CDNs CDNService
Domains DomainsService
Droplets DropletsService
DropletActions DropletActionsService
@ -62,6 +63,9 @@ type Client struct {
Tags TagsService
LoadBalancers LoadBalancersService
Certificates CertificatesService
Firewalls FirewallsService
Projects ProjectsService
Kubernetes KubernetesService
// Optional function called after every successful request made to the DO APIs
onRequestCompleted RequestCompletionCallback
@ -156,22 +160,26 @@ func NewClient(httpClient *http.Client) *Client {
c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent}
c.Account = &AccountServiceOp{client: c}
c.Actions = &ActionsServiceOp{client: c}
c.CDNs = &CDNServiceOp{client: c}
c.Certificates = &CertificatesServiceOp{client: c}
c.Domains = &DomainsServiceOp{client: c}
c.Droplets = &DropletsServiceOp{client: c}
c.DropletActions = &DropletActionsServiceOp{client: c}
c.Firewalls = &FirewallsServiceOp{client: c}
c.FloatingIPs = &FloatingIPsServiceOp{client: c}
c.FloatingIPActions = &FloatingIPActionsServiceOp{client: c}
c.Images = &ImagesServiceOp{client: c}
c.ImageActions = &ImageActionsServiceOp{client: c}
c.Keys = &KeysServiceOp{client: c}
c.LoadBalancers = &LoadBalancersServiceOp{client: c}
c.Projects = &ProjectsServiceOp{client: c}
c.Regions = &RegionsServiceOp{client: c}
c.Snapshots = &SnapshotsServiceOp{client: c}
c.Sizes = &SizesServiceOp{client: c}
c.Snapshots = &SnapshotsServiceOp{client: c}
c.Storage = &StorageServiceOp{client: c}
c.StorageActions = &StorageActionsServiceOp{client: c}
c.Tags = &TagsServiceOp{client: c}
c.LoadBalancers = &LoadBalancersServiceOp{client: c}
c.Certificates = &CertificatesServiceOp{client: c}
c.Kubernetes = &KubernetesServiceOp{client: c}
return c
}
@ -207,7 +215,7 @@ func SetBaseURL(bu string) ClientOpt {
// SetUserAgent is a client option for setting the user agent.
func SetUserAgent(ua string) ClientOpt {
return func(c *Client) error {
c.UserAgent = fmt.Sprintf("%s+%s", ua, c.UserAgent)
c.UserAgent = fmt.Sprintf("%s %s", ua, c.UserAgent)
return nil
}
}
@ -236,7 +244,6 @@ func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body int
return nil, err
}
req = req.WithContext(ctx)
req.Header.Add("Content-Type", mediaType)
req.Header.Add("Accept", mediaType)
req.Header.Add("User-Agent", c.UserAgent)
@ -293,8 +300,8 @@ func (r *Response) populateRate() {
// Do sends an API request and returns the API response. The API response is JSON decoded and stored in the value
// pointed to by v, or returned as an error if an API error has occurred. If v implements the io.Writer interface,
// the raw response will be written to v, without attempting to decode it.
func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
resp, err := c.client.Do(req)
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
resp, err := DoRequestWithClient(ctx, c.client, req)
if err != nil {
return nil, err
}
@ -332,6 +339,21 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
return response, err
}
// DoRequest submits an HTTP request.
func DoRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
return DoRequestWithClient(ctx, http.DefaultClient, req)
}
// DoRequestWithClient submits an HTTP request using the specified client.
func DoRequestWithClient(
ctx context.Context,
client *http.Client,
req *http.Request) (*http.Response, error) {
req = req.WithContext(ctx)
return client.Do(req)
}
func (r *ErrorResponse) Error() string {
if r.RequestID != "" {
return fmt.Sprintf("%v %v: %d (request %q) %v",
@ -354,7 +376,7 @@ func CheckResponse(r *http.Response) error {
if err == nil && len(data) > 0 {
err := json.Unmarshal(data, errorResponse)
if err != nil {
return err
errorResponse.Message = string(data)
}
}

View File

@ -3,6 +3,7 @@ package godo
import (
"context"
"fmt"
"net/http"
)
// ImageActionsService is an interface for interfacing with the image actions
@ -34,13 +35,13 @@ func (i *ImageActionsServiceOp) Transfer(ctx context.Context, imageID int, trans
path := fmt.Sprintf("v2/images/%d/actions", imageID)
req, err := i.client.NewRequest(ctx, "POST", path, transferRequest)
req, err := i.client.NewRequest(ctx, http.MethodPost, path, transferRequest)
if err != nil {
return nil, nil, err
}
root := new(actionRoot)
resp, err := i.client.Do(req, root)
resp, err := i.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -60,13 +61,13 @@ func (i *ImageActionsServiceOp) Convert(ctx context.Context, imageID int) (*Acti
"type": "convert",
}
req, err := i.client.NewRequest(ctx, "POST", path, convertRequest)
req, err := i.client.NewRequest(ctx, http.MethodPost, path, convertRequest)
if err != nil {
return nil, nil, err
}
root := new(actionRoot)
resp, err := i.client.Do(req, root)
resp, err := i.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -86,13 +87,13 @@ func (i *ImageActionsServiceOp) Get(ctx context.Context, imageID, actionID int)
path := fmt.Sprintf("v2/images/%d/actions/%d", imageID, actionID)
req, err := i.client.NewRequest(ctx, "GET", path, nil)
req, err := i.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(actionRoot)
resp, err := i.client.Do(req, root)
resp, err := i.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

View File

@ -3,6 +3,7 @@ package godo
import (
"context"
"fmt"
"net/http"
)
const imageBasePath = "v2/images"
@ -15,8 +16,10 @@ type ImagesService interface {
ListDistribution(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
ListApplication(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
ListUser(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
ListByTag(ctx context.Context, tag string, opt *ListOptions) ([]Image, *Response, error)
GetByID(context.Context, int) (*Image, *Response, error)
GetBySlug(context.Context, string) (*Image, *Response, error)
Create(context.Context, *CustomImageCreateRequest) (*Image, *Response, error)
Update(context.Context, int, *ImageUpdateRequest) (*Image, *Response, error)
Delete(context.Context, int) (*Response, error)
}
@ -31,15 +34,20 @@ var _ ImagesService = &ImagesServiceOp{}
// Image represents a DigitalOcean Image
type Image struct {
ID int `json:"id,float64,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Distribution string `json:"distribution,omitempty"`
Slug string `json:"slug,omitempty"`
Public bool `json:"public,omitempty"`
Regions []string `json:"regions,omitempty"`
MinDiskSize int `json:"min_disk_size,omitempty"`
Created string `json:"created_at,omitempty"`
ID int `json:"id,float64,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Distribution string `json:"distribution,omitempty"`
Slug string `json:"slug,omitempty"`
Public bool `json:"public,omitempty"`
Regions []string `json:"regions,omitempty"`
MinDiskSize int `json:"min_disk_size,omitempty"`
SizeGigaBytes float64 `json:"size_gigabytes,omitempty"`
Created string `json:"created_at,omitempty"`
Description string `json:"description,omitempty"`
Tags []string `json:"tags,omitempty"`
Status string `json:"status,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
}
// ImageUpdateRequest represents a request to update an image.
@ -47,6 +55,16 @@ type ImageUpdateRequest struct {
Name string `json:"name"`
}
// CustomImageCreateRequest represents a request to create a custom image.
type CustomImageCreateRequest struct {
Name string `json:"name"`
Url string `json:"url"`
Region string `json:"region"`
Distribution string `json:"distribution,omitempty"`
Description string `json:"description,omitempty"`
Tags []string `json:"tags,omitempty"`
}
type imageRoot struct {
Image *Image
}
@ -59,6 +77,7 @@ type imagesRoot struct {
type listImageOptions struct {
Private bool `url:"private,omitempty"`
Type string `url:"type,omitempty"`
Tag string `url:"tag_name,omitempty"`
}
func (i Image) String() string {
@ -88,6 +107,12 @@ func (s *ImagesServiceOp) ListUser(ctx context.Context, opt *ListOptions) ([]Ima
return s.list(ctx, opt, &listOpt)
}
// ListByTag lists all images with a specific tag applied.
func (s *ImagesServiceOp) ListByTag(ctx context.Context, tag string, opt *ListOptions) ([]Image, *Response, error) {
listOpt := listImageOptions{Tag: tag}
return s.list(ctx, opt, &listOpt)
}
// GetByID retrieves an image by id.
func (s *ImagesServiceOp) GetByID(ctx context.Context, imageID int) (*Image, *Response, error) {
if imageID < 1 {
@ -106,6 +131,25 @@ func (s *ImagesServiceOp) GetBySlug(ctx context.Context, slug string) (*Image, *
return s.get(ctx, interface{}(slug))
}
func (s *ImagesServiceOp) Create(ctx context.Context, createRequest *CustomImageCreateRequest) (*Image, *Response, error) {
if createRequest == nil {
return nil, nil, NewArgError("createRequest", "cannot be nil")
}
req, err := s.client.NewRequest(ctx, http.MethodPost, imageBasePath, createRequest)
if err != nil {
return nil, nil, err
}
root := new(imageRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Image, resp, err
}
// Update an image name.
func (s *ImagesServiceOp) Update(ctx context.Context, imageID int, updateRequest *ImageUpdateRequest) (*Image, *Response, error) {
if imageID < 1 {
@ -117,13 +161,13 @@ func (s *ImagesServiceOp) Update(ctx context.Context, imageID int, updateRequest
}
path := fmt.Sprintf("%s/%d", imageBasePath, imageID)
req, err := s.client.NewRequest(ctx, "PUT", path, updateRequest)
req, err := s.client.NewRequest(ctx, http.MethodPut, path, updateRequest)
if err != nil {
return nil, nil, err
}
root := new(imageRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -139,12 +183,12 @@ func (s *ImagesServiceOp) Delete(ctx context.Context, imageID int) (*Response, e
path := fmt.Sprintf("%s/%d", imageBasePath, imageID)
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
resp, err := s.client.Do(ctx, req, nil)
return resp, err
}
@ -153,13 +197,13 @@ func (s *ImagesServiceOp) Delete(ctx context.Context, imageID int) (*Response, e
func (s *ImagesServiceOp) get(ctx context.Context, ID interface{}) (*Image, *Response, error) {
path := fmt.Sprintf("%s/%v", imageBasePath, ID)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(imageRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -179,13 +223,13 @@ func (s *ImagesServiceOp) list(ctx context.Context, opt *ListOptions, listOpt *l
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(imagesRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

View File

@ -3,6 +3,7 @@ package godo
import (
"context"
"fmt"
"net/http"
)
const keysBasePath = "v2/account/keys"
@ -69,13 +70,13 @@ func (s *KeysServiceOp) List(ctx context.Context, opt *ListOptions) ([]Key, *Res
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(keysRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -88,13 +89,13 @@ func (s *KeysServiceOp) List(ctx context.Context, opt *ListOptions) ([]Key, *Res
// Performs a get given a path
func (s *KeysServiceOp) get(ctx context.Context, path string) (*Key, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(keyRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -128,13 +129,13 @@ func (s *KeysServiceOp) Create(ctx context.Context, createRequest *KeyCreateRequ
return nil, nil, NewArgError("createRequest", "cannot be nil")
}
req, err := s.client.NewRequest(ctx, "POST", keysBasePath, createRequest)
req, err := s.client.NewRequest(ctx, http.MethodPost, keysBasePath, createRequest)
if err != nil {
return nil, nil, err
}
root := new(keyRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -159,7 +160,7 @@ func (s *KeysServiceOp) UpdateByID(ctx context.Context, keyID int, updateRequest
}
root := new(keyRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -184,7 +185,7 @@ func (s *KeysServiceOp) UpdateByFingerprint(ctx context.Context, fingerprint str
}
root := new(keyRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -194,12 +195,12 @@ func (s *KeysServiceOp) UpdateByFingerprint(ctx context.Context, fingerprint str
// Delete key using a path
func (s *KeysServiceOp) delete(ctx context.Context, path string) (*Response, error) {
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
resp, err := s.client.Do(ctx, req, nil)
return resp, err
}

380
vendor/github.com/digitalocean/godo/kubernetes.go generated vendored Normal file
View File

@ -0,0 +1,380 @@
package godo
import (
"bytes"
"context"
"fmt"
"net/http"
"time"
)
const (
kubernetesBasePath = "/v2/kubernetes"
kubernetesClustersPath = kubernetesBasePath + "/clusters"
kubernetesOptionsPath = kubernetesBasePath + "/options"
)
// KubernetesService is an interface for interfacing with the kubernetes endpoints
// of the DigitalOcean API.
// See: https://developers.digitalocean.com/documentation/v2#kubernetes
type KubernetesService interface {
Create(context.Context, *KubernetesClusterCreateRequest) (*KubernetesCluster, *Response, error)
Get(context.Context, string) (*KubernetesCluster, *Response, error)
GetKubeConfig(context.Context, string) (*KubernetesClusterConfig, *Response, error)
List(context.Context, *ListOptions) ([]*KubernetesCluster, *Response, error)
Update(context.Context, string, *KubernetesClusterUpdateRequest) (*KubernetesCluster, *Response, error)
Delete(context.Context, string) (*Response, error)
CreateNodePool(ctx context.Context, clusterID string, req *KubernetesNodePoolCreateRequest) (*KubernetesNodePool, *Response, error)
GetNodePool(ctx context.Context, clusterID, poolID string) (*KubernetesNodePool, *Response, error)
ListNodePools(ctx context.Context, clusterID string, opts *ListOptions) ([]*KubernetesNodePool, *Response, error)
UpdateNodePool(ctx context.Context, clusterID, poolID string, req *KubernetesNodePoolUpdateRequest) (*KubernetesNodePool, *Response, error)
RecycleNodePoolNodes(ctx context.Context, clusterID, poolID string, req *KubernetesNodePoolRecycleNodesRequest) (*Response, error)
DeleteNodePool(ctx context.Context, clusterID, poolID string) (*Response, error)
GetOptions(context.Context) (*KubernetesOptions, *Response, error)
}
var _ KubernetesService = &KubernetesServiceOp{}
// KubernetesServiceOp handles communication with Kubernetes methods of the DigitalOcean API.
type KubernetesServiceOp struct {
client *Client
}
// KubernetesClusterCreateRequest represents a request to create a Kubernetes cluster.
type KubernetesClusterCreateRequest struct {
Name string `json:"name,omitempty"`
RegionSlug string `json:"region,omitempty"`
VersionSlug string `json:"version,omitempty"`
Tags []string `json:"tags,omitempty"`
NodePools []*KubernetesNodePoolCreateRequest `json:"node_pools,omitempty"`
}
// KubernetesClusterUpdateRequest represents a request to update a Kubernetes cluster.
type KubernetesClusterUpdateRequest struct {
Name string `json:"name,omitempty"`
Tags []string `json:"tags,omitempty"`
}
// KubernetesNodePoolCreateRequest represents a request to create a node pool for a
// Kubernetes cluster.
type KubernetesNodePoolCreateRequest struct {
Name string `json:"name,omitempty"`
Size string `json:"size,omitempty"`
Count int `json:"count,omitempty"`
Tags []string `json:"tags,omitempty"`
}
// KubernetesNodePoolUpdateRequest represents a request to update a node pool in a
// Kubernetes cluster.
type KubernetesNodePoolUpdateRequest struct {
Name string `json:"name,omitempty"`
Count int `json:"count,omitempty"`
Tags []string `json:"tags,omitempty"`
}
// KubernetesNodePoolRecycleNodesRequest represents a request to recycle a set of
// nodes in a node pool. This will recycle the nodes by ID.
type KubernetesNodePoolRecycleNodesRequest struct {
Nodes []string `json:"nodes,omitempty"`
}
// KubernetesCluster represents a Kubernetes cluster.
type KubernetesCluster struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
RegionSlug string `json:"region,omitempty"`
VersionSlug string `json:"version,omitempty"`
ClusterSubnet string `json:"cluster_subnet,omitempty"`
ServiceSubnet string `json:"service_subnet,omitempty"`
IPv4 string `json:"ipv4,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
Tags []string `json:"tags,omitempty"`
NodePools []*KubernetesNodePool `json:"node_pools,omitempty"`
Status *KubernetesClusterStatus `json:"status,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
// KubernetesClusterStatus describes the status of a cluster.
type KubernetesClusterStatus struct {
State string `json:"state,omitempty"`
Message string `json:"message,omitempty"`
}
// KubernetesNodePool represents a node pool in a Kubernetes cluster.
type KubernetesNodePool struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Size string `json:"size,omitempty"`
Count int `json:"count,omitempty"`
Tags []string `json:"tags,omitempty"`
Nodes []*KubernetesNode `json:"nodes,omitempty"`
}
// KubernetesNode represents a Node in a node pool in a Kubernetes cluster.
type KubernetesNode struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Status *KubernetesNodeStatus `json:"status,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
// KubernetesNodeStatus represents the status of a particular Node in a Kubernetes cluster.
type KubernetesNodeStatus struct {
State string `json:"state,omitempty"`
Message string `json:"message,omitempty"`
}
// KubernetesOptions represents options available for creating Kubernetes clusters.
type KubernetesOptions struct {
Versions []*KubernetesVersion `json:"versions,omitempty"`
}
// KubernetesVersion is a DigitalOcean Kubernetes release.
type KubernetesVersion struct {
Slug string `json:"slug,omitempty"`
KubernetesVersion string `json:"kubernetes_version,omitempty"`
}
type kubernetesClustersRoot struct {
Clusters []*KubernetesCluster `json:"kubernetes_clusters,omitempty"`
Links *Links `json:"links,omitempty"`
}
type kubernetesClusterRoot struct {
Cluster *KubernetesCluster `json:"kubernetes_cluster,omitempty"`
}
type kubernetesNodePoolRoot struct {
NodePool *KubernetesNodePool `json:"node_pool,omitempty"`
}
type kubernetesNodePoolsRoot struct {
NodePools []*KubernetesNodePool `json:"node_pools,omitempty"`
Links *Links `json:"links,omitempty"`
}
// Get retrieves the details of a Kubernetes cluster.
func (svc *KubernetesServiceOp) Get(ctx context.Context, clusterID string) (*KubernetesCluster, *Response, error) {
path := fmt.Sprintf("%s/%s", kubernetesClustersPath, clusterID)
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(kubernetesClusterRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Cluster, resp, nil
}
// Create creates a Kubernetes cluster.
func (svc *KubernetesServiceOp) Create(ctx context.Context, create *KubernetesClusterCreateRequest) (*KubernetesCluster, *Response, error) {
path := kubernetesClustersPath
req, err := svc.client.NewRequest(ctx, http.MethodPost, path, create)
if err != nil {
return nil, nil, err
}
root := new(kubernetesClusterRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Cluster, resp, nil
}
// Delete deletes a Kubernetes cluster. There is no way to recover a cluster
// once it has been destroyed.
func (svc *KubernetesServiceOp) Delete(ctx context.Context, clusterID string) (*Response, error) {
path := fmt.Sprintf("%s/%s", kubernetesClustersPath, clusterID)
req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
resp, err := svc.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}
// List returns a list of the Kubernetes clusters visible with the caller's API token.
func (svc *KubernetesServiceOp) List(ctx context.Context, opts *ListOptions) ([]*KubernetesCluster, *Response, error) {
path := kubernetesClustersPath
path, err := addOptions(path, opts)
if err != nil {
return nil, nil, err
}
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(kubernetesClustersRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Clusters, resp, nil
}
// KubernetesClusterConfig is the content of a Kubernetes config file, which can be
// used to interact with your Kubernetes cluster using `kubectl`.
// See: https://kubernetes.io/docs/tasks/tools/install-kubectl/
type KubernetesClusterConfig struct {
KubeconfigYAML []byte
}
// GetKubeConfig returns a Kubernetes config file for the specified cluster.
func (svc *KubernetesServiceOp) GetKubeConfig(ctx context.Context, clusterID string) (*KubernetesClusterConfig, *Response, error) {
path := fmt.Sprintf("%s/%s/kubeconfig", kubernetesClustersPath, clusterID)
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
configBytes := bytes.NewBuffer(nil)
resp, err := svc.client.Do(ctx, req, configBytes)
if err != nil {
return nil, resp, err
}
res := &KubernetesClusterConfig{
KubeconfigYAML: configBytes.Bytes(),
}
return res, resp, nil
}
// Update updates a Kubernetes cluster's properties.
func (svc *KubernetesServiceOp) Update(ctx context.Context, clusterID string, update *KubernetesClusterUpdateRequest) (*KubernetesCluster, *Response, error) {
path := fmt.Sprintf("%s/%s", kubernetesClustersPath, clusterID)
req, err := svc.client.NewRequest(ctx, http.MethodPut, path, update)
if err != nil {
return nil, nil, err
}
root := new(kubernetesClusterRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Cluster, resp, nil
}
// CreateNodePool creates a new node pool in an existing Kubernetes cluster.
func (svc *KubernetesServiceOp) CreateNodePool(ctx context.Context, clusterID string, create *KubernetesNodePoolCreateRequest) (*KubernetesNodePool, *Response, error) {
path := fmt.Sprintf("%s/%s/node_pools", kubernetesClustersPath, clusterID)
req, err := svc.client.NewRequest(ctx, http.MethodPost, path, create)
if err != nil {
return nil, nil, err
}
root := new(kubernetesNodePoolRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.NodePool, resp, nil
}
// GetNodePool retrieves an existing node pool in a Kubernetes cluster.
func (svc *KubernetesServiceOp) GetNodePool(ctx context.Context, clusterID, poolID string) (*KubernetesNodePool, *Response, error) {
path := fmt.Sprintf("%s/%s/node_pools/%s", kubernetesClustersPath, clusterID, poolID)
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(kubernetesNodePoolRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.NodePool, resp, nil
}
// ListNodePools lists all the node pools found in a Kubernetes cluster.
func (svc *KubernetesServiceOp) ListNodePools(ctx context.Context, clusterID string, opts *ListOptions) ([]*KubernetesNodePool, *Response, error) {
path := fmt.Sprintf("%s/%s/node_pools", kubernetesClustersPath, clusterID)
path, err := addOptions(path, opts)
if err != nil {
return nil, nil, err
}
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(kubernetesNodePoolsRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.NodePools, resp, nil
}
// UpdateNodePool updates the details of an existing node pool.
func (svc *KubernetesServiceOp) UpdateNodePool(ctx context.Context, clusterID, poolID string, update *KubernetesNodePoolUpdateRequest) (*KubernetesNodePool, *Response, error) {
path := fmt.Sprintf("%s/%s/node_pools/%s", kubernetesClustersPath, clusterID, poolID)
req, err := svc.client.NewRequest(ctx, http.MethodPut, path, update)
if err != nil {
return nil, nil, err
}
root := new(kubernetesNodePoolRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.NodePool, resp, nil
}
// RecycleNodePoolNodes schedules nodes in a node pool for recycling.
func (svc *KubernetesServiceOp) RecycleNodePoolNodes(ctx context.Context, clusterID, poolID string, recycle *KubernetesNodePoolRecycleNodesRequest) (*Response, error) {
path := fmt.Sprintf("%s/%s/node_pools/%s/recycle", kubernetesClustersPath, clusterID, poolID)
req, err := svc.client.NewRequest(ctx, http.MethodPost, path, recycle)
if err != nil {
return nil, err
}
resp, err := svc.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}
// DeleteNodePool deletes a node pool, and subsequently all the nodes in that pool.
func (svc *KubernetesServiceOp) DeleteNodePool(ctx context.Context, clusterID, poolID string) (*Response, error) {
path := fmt.Sprintf("%s/%s/node_pools/%s", kubernetesClustersPath, clusterID, poolID)
req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
resp, err := svc.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}
type kubernetesOptionsRoot struct {
Options *KubernetesOptions `json:"options,omitempty"`
Links *Links `json:"links,omitempty"`
}
// GetOptions returns options about the Kubernetes service, such as the versions available for
// cluster creation.
func (svc *KubernetesServiceOp) GetOptions(ctx context.Context) (*KubernetesOptions, *Response, error) {
path := kubernetesOptionsPath
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(kubernetesOptionsRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Options, resp, nil
}

View File

@ -3,10 +3,12 @@ package godo
import (
"context"
"fmt"
"net/http"
)
const loadBalancersBasePath = "/v2/load_balancers"
const forwardingRulesPath = "forwarding_rules"
const dropletsPath = "droplets"
// LoadBalancersService is an interface for managing load balancers with the DigitalOcean API.
@ -45,6 +47,36 @@ func (l LoadBalancer) String() string {
return Stringify(l)
}
func (l LoadBalancer) URN() string {
return ToURN("LoadBalancer", l.ID)
}
// AsRequest creates a LoadBalancerRequest that can be submitted to Update with the current values of the LoadBalancer.
// Modifying the returned LoadBalancerRequest will not modify the original LoadBalancer.
func (l LoadBalancer) AsRequest() *LoadBalancerRequest {
r := LoadBalancerRequest{
Name: l.Name,
Algorithm: l.Algorithm,
ForwardingRules: append([]ForwardingRule(nil), l.ForwardingRules...),
DropletIDs: append([]int(nil), l.DropletIDs...),
Tag: l.Tag,
RedirectHttpToHttps: l.RedirectHttpToHttps,
HealthCheck: l.HealthCheck,
}
if l.HealthCheck != nil {
r.HealthCheck = &HealthCheck{}
*r.HealthCheck = *l.HealthCheck
}
if l.StickySessions != nil {
r.StickySessions = &StickySessions{}
*r.StickySessions = *l.StickySessions
}
if l.Region != nil {
r.Region = l.Region.Slug
}
return &r
}
// ForwardingRule represents load balancer forwarding rules.
type ForwardingRule struct {
EntryProtocol string `json:"entry_protocol,omitempty"`
@ -142,13 +174,13 @@ var _ LoadBalancersService = &LoadBalancersServiceOp{}
func (l *LoadBalancersServiceOp) Get(ctx context.Context, lbID string) (*LoadBalancer, *Response, error) {
path := fmt.Sprintf("%s/%s", loadBalancersBasePath, lbID)
req, err := l.client.NewRequest(ctx, "GET", path, nil)
req, err := l.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(loadBalancerRoot)
resp, err := l.client.Do(req, root)
resp, err := l.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -163,13 +195,13 @@ func (l *LoadBalancersServiceOp) List(ctx context.Context, opt *ListOptions) ([]
return nil, nil, err
}
req, err := l.client.NewRequest(ctx, "GET", path, nil)
req, err := l.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(loadBalancersRoot)
resp, err := l.client.Do(req, root)
resp, err := l.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -182,13 +214,13 @@ func (l *LoadBalancersServiceOp) List(ctx context.Context, opt *ListOptions) ([]
// Create a new load balancer with a given configuration.
func (l *LoadBalancersServiceOp) Create(ctx context.Context, lbr *LoadBalancerRequest) (*LoadBalancer, *Response, error) {
req, err := l.client.NewRequest(ctx, "POST", loadBalancersBasePath, lbr)
req, err := l.client.NewRequest(ctx, http.MethodPost, loadBalancersBasePath, lbr)
if err != nil {
return nil, nil, err
}
root := new(loadBalancerRoot)
resp, err := l.client.Do(req, root)
resp, err := l.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -206,7 +238,7 @@ func (l *LoadBalancersServiceOp) Update(ctx context.Context, lbID string, lbr *L
}
root := new(loadBalancerRoot)
resp, err := l.client.Do(req, root)
resp, err := l.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -218,58 +250,58 @@ func (l *LoadBalancersServiceOp) Update(ctx context.Context, lbID string, lbr *L
func (l *LoadBalancersServiceOp) Delete(ctx context.Context, ldID string) (*Response, error) {
path := fmt.Sprintf("%s/%s", loadBalancersBasePath, ldID)
req, err := l.client.NewRequest(ctx, "DELETE", path, nil)
req, err := l.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
return l.client.Do(req, nil)
return l.client.Do(ctx, req, nil)
}
// AddDroplets adds droplets to a load balancer.
func (l *LoadBalancersServiceOp) AddDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error) {
path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, dropletsPath)
req, err := l.client.NewRequest(ctx, "POST", path, &dropletIDsRequest{IDs: dropletIDs})
req, err := l.client.NewRequest(ctx, http.MethodPost, path, &dropletIDsRequest{IDs: dropletIDs})
if err != nil {
return nil, err
}
return l.client.Do(req, nil)
return l.client.Do(ctx, req, nil)
}
// RemoveDroplets removes droplets from a load balancer.
func (l *LoadBalancersServiceOp) RemoveDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error) {
path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, dropletsPath)
req, err := l.client.NewRequest(ctx, "DELETE", path, &dropletIDsRequest{IDs: dropletIDs})
req, err := l.client.NewRequest(ctx, http.MethodDelete, path, &dropletIDsRequest{IDs: dropletIDs})
if err != nil {
return nil, err
}
return l.client.Do(req, nil)
return l.client.Do(ctx, req, nil)
}
// AddForwardingRules adds forwarding rules to a load balancer.
func (l *LoadBalancersServiceOp) AddForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error) {
path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, forwardingRulesPath)
req, err := l.client.NewRequest(ctx, "POST", path, &forwardingRulesRequest{Rules: rules})
req, err := l.client.NewRequest(ctx, http.MethodPost, path, &forwardingRulesRequest{Rules: rules})
if err != nil {
return nil, err
}
return l.client.Do(req, nil)
return l.client.Do(ctx, req, nil)
}
// RemoveForwardingRules removes forwarding rules from a load balancer.
func (l *LoadBalancersServiceOp) RemoveForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error) {
path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, forwardingRulesPath)
req, err := l.client.NewRequest(ctx, "DELETE", path, &forwardingRulesRequest{Rules: rules})
req, err := l.client.NewRequest(ctx, http.MethodDelete, path, &forwardingRulesRequest{Rules: rules})
if err != nil {
return nil, err
}
return l.client.Do(req, nil)
return l.client.Do(ctx, req, nil)
}

302
vendor/github.com/digitalocean/godo/projects.go generated vendored Normal file
View File

@ -0,0 +1,302 @@
package godo
import (
"context"
"encoding/json"
"fmt"
"net/http"
"path"
)
const (
// DefaultProject is the ID you should use if you are working with your
// default project.
DefaultProject = "default"
projectsBasePath = "/v2/projects"
)
// ProjectsService is an interface for creating and managing Projects with the DigitalOcean API.
// See: https://developers.digitalocean.com/documentation/documentation/v2/#projects
type ProjectsService interface {
List(context.Context, *ListOptions) ([]Project, *Response, error)
GetDefault(context.Context) (*Project, *Response, error)
Get(context.Context, string) (*Project, *Response, error)
Create(context.Context, *CreateProjectRequest) (*Project, *Response, error)
Update(context.Context, string, *UpdateProjectRequest) (*Project, *Response, error)
Delete(context.Context, string) (*Response, error)
ListResources(context.Context, string, *ListOptions) ([]ProjectResource, *Response, error)
AssignResources(context.Context, string, ...interface{}) ([]ProjectResource, *Response, error)
}
// ProjectsServiceOp handles communication with Projects methods of the DigitalOcean API.
type ProjectsServiceOp struct {
client *Client
}
// Project represents a DigitalOcean Project configuration.
type Project struct {
ID string `json:"id"`
OwnerUUID string `json:"owner_uuid"`
OwnerID uint64 `json:"owner_id"`
Name string `json:"name"`
Description string `json:"description"`
Purpose string `json:"purpose"`
Environment string `json:"environment"`
IsDefault bool `json:"is_default"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// String creates a human-readable description of a Project.
func (p Project) String() string {
return Stringify(p)
}
// CreateProjectRequest represents the request to create a new project.
type CreateProjectRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Purpose string `json:"purpose"`
Environment string `json:"environment"`
}
// UpdateProjectRequest represents the request to update project information.
// This type expects certain attribute types, but is built this way to allow
// nil values as well. See `updateProjectRequest` for the "real" types.
type UpdateProjectRequest struct {
Name interface{}
Description interface{}
Purpose interface{}
Environment interface{}
IsDefault interface{}
}
type updateProjectRequest struct {
Name *string `json:"name"`
Description *string `json:"description"`
Purpose *string `json:"purpose"`
Environment *string `json:"environment"`
IsDefault *bool `json:"is_default"`
}
// MarshalJSON takes an UpdateRequest and converts it to the "typed" request
// which is sent to the projects API. This is a PATCH request, which allows
// partial attributes, so `null` values are OK.
func (upr *UpdateProjectRequest) MarshalJSON() ([]byte, error) {
d := &updateProjectRequest{}
if str, ok := upr.Name.(string); ok {
d.Name = &str
}
if str, ok := upr.Description.(string); ok {
d.Description = &str
}
if str, ok := upr.Purpose.(string); ok {
d.Purpose = &str
}
if str, ok := upr.Environment.(string); ok {
d.Environment = &str
}
if val, ok := upr.IsDefault.(bool); ok {
d.IsDefault = &val
}
return json.Marshal(d)
}
type assignResourcesRequest struct {
Resources []string `json:"resources"`
}
// ProjectResource is the projects API's representation of a resource.
type ProjectResource struct {
URN string `json:"urn"`
AssignedAt string `json:"assigned_at"`
Links *ProjectResourceLinks `json:"links"`
Status string `json:"status,omitempty"`
}
// ProjetResourceLinks specify the link for more information about the resource.
type ProjectResourceLinks struct {
Self string `json:"self"`
}
type projectsRoot struct {
Projects []Project `json:"projects"`
Links *Links `json:"links"`
}
type projectRoot struct {
Project *Project `json:"project"`
}
type projectResourcesRoot struct {
Resources []ProjectResource `json:"resources"`
Links *Links `json:"links,omitempty"`
}
var _ ProjectsService = &ProjectsServiceOp{}
// List Projects.
func (p *ProjectsServiceOp) List(ctx context.Context, opts *ListOptions) ([]Project, *Response, error) {
path, err := addOptions(projectsBasePath, opts)
if err != nil {
return nil, nil, err
}
req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(projectsRoot)
resp, err := p.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
return root.Projects, resp, err
}
// GetDefault project.
func (p *ProjectsServiceOp) GetDefault(ctx context.Context) (*Project, *Response, error) {
return p.getHelper(ctx, "default")
}
// Get retrieves a single project by its ID.
func (p *ProjectsServiceOp) Get(ctx context.Context, projectID string) (*Project, *Response, error) {
return p.getHelper(ctx, projectID)
}
// Create a new project.
func (p *ProjectsServiceOp) Create(ctx context.Context, cr *CreateProjectRequest) (*Project, *Response, error) {
req, err := p.client.NewRequest(ctx, http.MethodPost, projectsBasePath, cr)
if err != nil {
return nil, nil, err
}
root := new(projectRoot)
resp, err := p.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Project, resp, err
}
// Update an existing project.
func (p *ProjectsServiceOp) Update(ctx context.Context, projectID string, ur *UpdateProjectRequest) (*Project, *Response, error) {
path := path.Join(projectsBasePath, projectID)
req, err := p.client.NewRequest(ctx, http.MethodPatch, path, ur)
if err != nil {
return nil, nil, err
}
root := new(projectRoot)
resp, err := p.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Project, resp, err
}
// Delete an existing project. You cannot have any resources in a project
// before deleting it. See the API documentation for more details.
func (p *ProjectsServiceOp) Delete(ctx context.Context, projectID string) (*Response, error) {
path := path.Join(projectsBasePath, projectID)
req, err := p.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
return p.client.Do(ctx, req, nil)
}
// ListResources lists all resources in a project.
func (p *ProjectsServiceOp) ListResources(ctx context.Context, projectID string, opts *ListOptions) ([]ProjectResource, *Response, error) {
basePath := path.Join(projectsBasePath, projectID, "resources")
path, err := addOptions(basePath, opts)
if err != nil {
return nil, nil, err
}
req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(projectResourcesRoot)
resp, err := p.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
return root.Resources, resp, err
}
// AssignResources assigns one or more resources to a project. AssignResources
// accepts resources in two possible formats:
// 1. The resource type, like `&Droplet{ID: 1}` or `&FloatingIP{IP: "1.2.3.4"}`
// 2. A valid DO URN as a string, like "do:droplet:1234"
//
// There is no unassign. To move a resource to another project, just assign
// it to that other project.
func (p *ProjectsServiceOp) AssignResources(ctx context.Context, projectID string, resources ...interface{}) ([]ProjectResource, *Response, error) {
path := path.Join(projectsBasePath, projectID, "resources")
ar := &assignResourcesRequest{
Resources: make([]string, len(resources)),
}
for i, resource := range resources {
switch resource.(type) {
case ResourceWithURN:
ar.Resources[i] = resource.(ResourceWithURN).URN()
case string:
ar.Resources[i] = resource.(string)
default:
return nil, nil, fmt.Errorf("%T must either be a string or have a valid URN method", resource)
}
}
req, err := p.client.NewRequest(ctx, http.MethodPost, path, ar)
if err != nil {
return nil, nil, err
}
root := new(projectResourcesRoot)
resp, err := p.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
return root.Resources, resp, err
}
func (p *ProjectsServiceOp) getHelper(ctx context.Context, projectID string) (*Project, *Response, error) {
path := path.Join(projectsBasePath, projectID)
req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(projectRoot)
resp, err := p.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Project, resp, err
}

View File

@ -1,6 +1,9 @@
package godo
import "context"
import (
"context"
"net/http"
)
// RegionsService is an interface for interfacing with the regions
// endpoints of the DigitalOcean API
@ -43,13 +46,13 @@ func (s *RegionsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Region
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(regionsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

View File

@ -1,6 +1,9 @@
package godo
import "context"
import (
"context"
"net/http"
)
// SizesService is an interface for interfacing with the size
// endpoints of the DigitalOcean API
@ -47,13 +50,13 @@ func (s *SizesServiceOp) List(ctx context.Context, opt *ListOptions) ([]Size, *R
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(sizesRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

View File

@ -3,6 +3,7 @@ package godo
import (
"context"
"fmt"
"net/http"
)
const snapshotBasePath = "v2/snapshots"
@ -81,12 +82,12 @@ func (s *SnapshotsServiceOp) Get(ctx context.Context, snapshotID string) (*Snaps
func (s *SnapshotsServiceOp) Delete(ctx context.Context, snapshotID string) (*Response, error) {
path := fmt.Sprintf("%s/%s", snapshotBasePath, snapshotID)
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
resp, err := s.client.Do(ctx, req, nil)
return resp, err
}
@ -95,13 +96,13 @@ func (s *SnapshotsServiceOp) Delete(ctx context.Context, snapshotID string) (*Re
func (s *SnapshotsServiceOp) get(ctx context.Context, ID string) (*Snapshot, *Response, error) {
path := fmt.Sprintf("%s/%s", snapshotBasePath, ID)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(snapshotRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -121,13 +122,13 @@ func (s *SnapshotsServiceOp) list(ctx context.Context, opt *ListOptions, listOpt
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(snapshotsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

View File

@ -3,6 +3,7 @@ package godo
import (
"context"
"fmt"
"net/http"
"time"
)
@ -43,19 +44,25 @@ var _ StorageService = &StorageServiceOp{}
// Volume represents a Digital Ocean block store volume.
type Volume struct {
ID string `json:"id"`
Region *Region `json:"region"`
Name string `json:"name"`
SizeGigaBytes int64 `json:"size_gigabytes"`
Description string `json:"description"`
DropletIDs []int `json:"droplet_ids"`
CreatedAt time.Time `json:"created_at"`
ID string `json:"id"`
Region *Region `json:"region"`
Name string `json:"name"`
SizeGigaBytes int64 `json:"size_gigabytes"`
Description string `json:"description"`
DropletIDs []int `json:"droplet_ids"`
CreatedAt time.Time `json:"created_at"`
FilesystemType string `json:"filesystem_type"`
FilesystemLabel string `json:"filesystem_label"`
}
func (f Volume) String() string {
return Stringify(f)
}
func (f Volume) URN() string {
return ToURN("Volume", f.ID)
}
type storageVolumesRoot struct {
Volumes []Volume `json:"volumes"`
Links *Links `json:"links"`
@ -69,11 +76,13 @@ type storageVolumeRoot struct {
// VolumeCreateRequest represents a request to create a block store
// volume.
type VolumeCreateRequest struct {
Region string `json:"region"`
Name string `json:"name"`
Description string `json:"description"`
SizeGigaBytes int64 `json:"size_gigabytes"`
SnapshotID string `json:"snapshot_id"`
Region string `json:"region"`
Name string `json:"name"`
Description string `json:"description"`
SizeGigaBytes int64 `json:"size_gigabytes"`
SnapshotID string `json:"snapshot_id"`
FilesystemType string `json:"filesystem_type"`
FilesystemLabel string `json:"filesystem_label"`
}
// ListVolumes lists all storage volumes.
@ -82,6 +91,10 @@ func (svc *StorageServiceOp) ListVolumes(ctx context.Context, params *ListVolume
if params != nil {
if params.Region != "" && params.Name != "" {
path = fmt.Sprintf("%s?name=%s&region=%s", path, params.Name, params.Region)
} else if params.Region != "" {
path = fmt.Sprintf("%s?region=%s", path, params.Region)
} else if params.Name != "" {
path = fmt.Sprintf("%s?name=%s", path, params.Name)
}
if params.ListOptions != nil {
@ -93,13 +106,13 @@ func (svc *StorageServiceOp) ListVolumes(ctx context.Context, params *ListVolume
}
}
req, err := svc.client.NewRequest(ctx, "GET", path, nil)
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(storageVolumesRoot)
resp, err := svc.client.Do(req, root)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -115,13 +128,13 @@ func (svc *StorageServiceOp) ListVolumes(ctx context.Context, params *ListVolume
func (svc *StorageServiceOp) CreateVolume(ctx context.Context, createRequest *VolumeCreateRequest) (*Volume, *Response, error) {
path := storageAllocPath
req, err := svc.client.NewRequest(ctx, "POST", path, createRequest)
req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createRequest)
if err != nil {
return nil, nil, err
}
root := new(storageVolumeRoot)
resp, err := svc.client.Do(req, root)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -132,13 +145,13 @@ func (svc *StorageServiceOp) CreateVolume(ctx context.Context, createRequest *Vo
func (svc *StorageServiceOp) GetVolume(ctx context.Context, id string) (*Volume, *Response, error) {
path := fmt.Sprintf("%s/%s", storageAllocPath, id)
req, err := svc.client.NewRequest(ctx, "GET", path, nil)
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(storageVolumeRoot)
resp, err := svc.client.Do(req, root)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -150,11 +163,11 @@ func (svc *StorageServiceOp) GetVolume(ctx context.Context, id string) (*Volume,
func (svc *StorageServiceOp) DeleteVolume(ctx context.Context, id string) (*Response, error) {
path := fmt.Sprintf("%s/%s", storageAllocPath, id)
req, err := svc.client.NewRequest(ctx, "DELETE", path, nil)
req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
return svc.client.Do(req, nil)
return svc.client.Do(ctx, req, nil)
}
// SnapshotCreateRequest represents a request to create a block store
@ -173,13 +186,13 @@ func (svc *StorageServiceOp) ListSnapshots(ctx context.Context, volumeID string,
return nil, nil, err
}
req, err := svc.client.NewRequest(ctx, "GET", path, nil)
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(snapshotsRoot)
resp, err := svc.client.Do(req, root)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -195,13 +208,13 @@ func (svc *StorageServiceOp) ListSnapshots(ctx context.Context, volumeID string,
func (svc *StorageServiceOp) CreateSnapshot(ctx context.Context, createRequest *SnapshotCreateRequest) (*Snapshot, *Response, error) {
path := fmt.Sprintf("%s/%s/snapshots", storageAllocPath, createRequest.VolumeID)
req, err := svc.client.NewRequest(ctx, "POST", path, createRequest)
req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createRequest)
if err != nil {
return nil, nil, err
}
root := new(snapshotRoot)
resp, err := svc.client.Do(req, root)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -212,13 +225,13 @@ func (svc *StorageServiceOp) CreateSnapshot(ctx context.Context, createRequest *
func (svc *StorageServiceOp) GetSnapshot(ctx context.Context, id string) (*Snapshot, *Response, error) {
path := fmt.Sprintf("%s/%s", storageSnapPath, id)
req, err := svc.client.NewRequest(ctx, "GET", path, nil)
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(snapshotRoot)
resp, err := svc.client.Do(req, root)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -230,9 +243,9 @@ func (svc *StorageServiceOp) GetSnapshot(ctx context.Context, id string) (*Snaps
func (svc *StorageServiceOp) DeleteSnapshot(ctx context.Context, id string) (*Response, error) {
path := fmt.Sprintf("%s/%s", storageSnapPath, id)
req, err := svc.client.NewRequest(ctx, "DELETE", path, nil)
req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
return svc.client.Do(req, nil)
return svc.client.Do(ctx, req, nil)
}

View File

@ -3,6 +3,7 @@ package godo
import (
"context"
"fmt"
"net/http"
)
// StorageActionsService is an interface for interfacing with the
@ -76,13 +77,13 @@ func (s *StorageActionsServiceOp) Resize(ctx context.Context, volumeID string, s
func (s *StorageActionsServiceOp) doAction(ctx context.Context, volumeID string, request *ActionRequest) (*Action, *Response, error) {
path := storageAllocationActionPath(volumeID)
req, err := s.client.NewRequest(ctx, "POST", path, request)
req, err := s.client.NewRequest(ctx, http.MethodPost, path, request)
if err != nil {
return nil, nil, err
}
root := new(actionRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -91,13 +92,13 @@ func (s *StorageActionsServiceOp) doAction(ctx context.Context, volumeID string,
}
func (s *StorageActionsServiceOp) get(ctx context.Context, path string) (*Action, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(actionRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -106,13 +107,13 @@ func (s *StorageActionsServiceOp) get(ctx context.Context, path string) (*Action
}
func (s *StorageActionsServiceOp) list(ctx context.Context, path string) ([]Action, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(actionsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

View File

@ -5,10 +5,20 @@ import (
"fmt"
"io"
"reflect"
"strings"
)
var timestampType = reflect.TypeOf(Timestamp{})
type ResourceWithURN interface {
URN() string
}
// ToURN converts the resource type and ID to a valid DO API URN.
func ToURN(resourceType string, id interface{}) string {
return fmt.Sprintf("%s:%s:%v", "do", strings.ToLower(resourceType), id)
}
// Stringify attempts to create a string representation of DigitalOcean types
func Stringify(message interface{}) string {
var buf bytes.Buffer

View File

@ -3,6 +3,7 @@ package godo
import (
"context"
"fmt"
"net/http"
)
const tagsBasePath = "v2/tags"
@ -34,6 +35,8 @@ type ResourceType string
const (
//DropletResourceType holds the string representing our ResourceType of Droplet.
DropletResourceType ResourceType = "droplet"
//ImageResourceType holds the string representing our ResourceType of Image.
ImageResourceType ResourceType = "image"
)
// Resource represent a single resource for associating/disassociating with tags
@ -44,13 +47,23 @@ type Resource struct {
// TaggedResources represent the set of resources a tag is attached to
type TaggedResources struct {
Droplets *TaggedDropletsResources `json:"droplets,omitempty"`
Count int `json:"count"`
LastTaggedURI string `json:"last_tagged_uri,omitempty"`
Droplets *TaggedDropletsResources `json:"droplets,omitempty"`
Images *TaggedImagesResources `json:"images"`
}
// TaggedDropletsResources represent the droplet resources a tag is attached to
type TaggedDropletsResources struct {
Count int `json:"count,float64,omitempty"`
LastTagged *Droplet `json:"last_tagged,omitempty"`
Count int `json:"count,float64,omitempty"`
LastTagged *Droplet `json:"last_tagged,omitempty"`
LastTaggedURI string `json:"last_tagged_uri,omitempty"`
}
// TaggedImagesResources represent the image resources a tag is attached to
type TaggedImagesResources struct {
Count int `json:"count,float64,omitempty"`
LastTaggedURI string `json:"last_tagged_uri,omitempty"`
}
// Tag represent DigitalOcean tag
@ -92,13 +105,13 @@ func (s *TagsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Tag, *Res
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(tagsRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -113,13 +126,13 @@ func (s *TagsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Tag, *Res
func (s *TagsServiceOp) Get(ctx context.Context, name string) (*Tag, *Response, error) {
path := fmt.Sprintf("%s/%s", tagsBasePath, name)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(tagRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -133,13 +146,13 @@ func (s *TagsServiceOp) Create(ctx context.Context, createRequest *TagCreateRequ
return nil, nil, NewArgError("createRequest", "cannot be nil")
}
req, err := s.client.NewRequest(ctx, "POST", tagsBasePath, createRequest)
req, err := s.client.NewRequest(ctx, http.MethodPost, tagsBasePath, createRequest)
if err != nil {
return nil, nil, err
}
root := new(tagRoot)
resp, err := s.client.Do(req, root)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
@ -154,12 +167,12 @@ func (s *TagsServiceOp) Delete(ctx context.Context, name string) (*Response, err
}
path := fmt.Sprintf("%s/%s", tagsBasePath, name)
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
resp, err := s.client.Do(ctx, req, nil)
return resp, err
}
@ -175,12 +188,12 @@ func (s *TagsServiceOp) TagResources(ctx context.Context, name string, tagReques
}
path := fmt.Sprintf("%s/%s/resources", tagsBasePath, name)
req, err := s.client.NewRequest(ctx, "POST", path, tagRequest)
req, err := s.client.NewRequest(ctx, http.MethodPost, path, tagRequest)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
resp, err := s.client.Do(ctx, req, nil)
return resp, err
}
@ -196,12 +209,12 @@ func (s *TagsServiceOp) UntagResources(ctx context.Context, name string, untagRe
}
path := fmt.Sprintf("%s/%s/resources", tagsBasePath, name)
req, err := s.client.NewRequest(ctx, "DELETE", path, untagRequest)
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, untagRequest)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
resp, err := s.client.Do(ctx, req, nil)
return resp, err
}

18
vendor/vendor.json vendored
View File

@ -717,11 +717,11 @@
"revisionTime": "2018-03-08T23:13:08Z"
},
{
"checksumSHA1": "W1LGm0UNirwMDVCMFv5vZrOpUJI=",
"checksumSHA1": "u6kWbz1BRsZT433AooftjLNNtdw=",
"comment": "v0.9.0-24-g6ca5b77",
"path": "github.com/digitalocean/godo",
"revision": "4c04abe183f449bd9ede285f0e5c7ee575d0dbe4",
"revisionTime": "2017-04-07T15:15:42Z"
"revision": "c4ae66932b5d6fb727d54e9ded9ee722f67f3ca9",
"revisionTime": "2018-11-13T19:55:25Z"
},
{
"checksumSHA1": "1n5MBJthemxmfqU2gN3qLCd8s04=",
@ -1487,18 +1487,18 @@
"revision": "2d205ac6ec17a839a94bdbfd16d2fa6c6dada2e0",
"revisionTime": "2016-03-31T20:48:55Z"
},
{
"checksumSHA1": "6JP37UqrI0H80Gpk0Y2P+KXgn5M=",
"path": "github.com/ryanuber/go-glob",
"revision": "256dc444b735e061061cf46c809487313d5b0065",
"revisionTime": "2017-01-28T01:21:29Z"
},
{
"checksumSHA1": "4gVuzkHbQoznf+lCSJhIJnvS5tc=",
"path": "github.com/rwtodd/Go.Sed/sed",
"revision": "d6d5d585814e4c3560c684f52e3d8aeed721313d",
"revisionTime": "2017-05-07T04:53:31Z"
},
{
"checksumSHA1": "6JP37UqrI0H80Gpk0Y2P+KXgn5M=",
"path": "github.com/ryanuber/go-glob",
"revision": "256dc444b735e061061cf46c809487313d5b0065",
"revisionTime": "2017-01-28T01:21:29Z"
},
{
"checksumSHA1": "zmC8/3V4ls53DJlNTKDZwPSC/dA=",
"path": "github.com/satori/go.uuid",

View File

@ -0,0 +1,100 @@
---
description: |
The Packer DigitalOcean Import post-processor takes an image artifact
from various builders and imports it to DigitalOcean.
layout: docs
page_title: 'DigitalOcean Import - Post-Processors'
sidebar_current: 'docs-post-processors-digitalocean-import'
---
# DigitalOcean Import Post-Processor
Type: `digitalocean-import`
The Packer DigitalOcean Import post-processor takes an image artifact from
various builders and imports it to DigitalOcean.
## How Does it Work?
The import process operates uploading a temporary copy of the image to
DigitalOcean Spaces and then importing it as a custom image via the
DigialOcean API. The temporary copy in Spaces can be discarded after the
import is complete.
For information about the requirements to use an image for a DigitalOcean
Droplet, see DigitalOcean's [Custom Images documentation](https://www.digitalocean.com/docs/images/custom-images/overview/).
## Configuration
There are some configuration options available for the post-processor.
Required:
- `api_token` (string) - A personal access token used to communicate with
the DigitalOcean v2 API. This may also be set using the
`DIGITALOCEAN_API_TOKEN` environmental variable.
- `spaces_key` (string) - The access key used to communicate with Spaces.
This may also be set using the `DIGITALOCEAN_SPACES_ACCESS_KEY`
environmental variable.
- `spaces_secret` (string) - The secret key used to communicate with Spaces.
This may also be set using the `DIGITALOCEAN_SPACES_SECRET_KEY`
environmental variable.
- `spaces_region` (string) - The name of the region, such as `nyc3`, in which
to upload the image to Spaces.
- `space_name` (string) - The name of the specific Space where the image file
will be copied to for import. This Space must exist when the
post-processor is run.
- `image_name` (string) - The name to be used for the resulting DigitalOcean
custom image.
- `image_regions` (array of string) - A list of DigitalOcean regions, such
as `nyc3`, where the resulting image will be available for use in creating
Droplets.
Optional:
- `image_description` (string) - The description to set for the resulting
imported image.
- `image_distribution` (string) - The name of the distribution to set for
the resulting imported image.
- `image_tags` (array of strings) - A list of tags to apply to the resulting
imported image.
- `skip_clean` (boolean) - Whether we should skip removing the image file
uploaded to Spaces after the import process has completed. "true" means
that we should leave it in the Space, "false" means to clean it out.
Defaults to `false`.
- `space_object_name` (string) - The name of the key used in the Space where
the image file will be copied to for import. If not specified, this will default to "packer-import-{{timestamp}}".
- `timeout` (number) - The length of time in minutes to wait for individual
steps in the process to successfully complete. This includes both importing
the image from Spaces as well as distributing the resulting image to
additional regions. If not specified, this will default to 20.
## Basic Example
Here is a basic example:
``` json
{
"type": "digitalocean-import",
"api_token": "{{user `token`}}",
"spaces_key": "{{user `key`}}",
"spaces_secret": "{{user `secret`}}",
"spaces_region": "nyc3",
"space_name": "import-bucket",
"image_name": "ubuntu-18.10-minimal-amd64",
"image_description": "Packer import {{timestamp}}",
"image_regions": ["nyc3", "nyc2"],
"image_tags": ["custom", "packer"]
}
```

View File

@ -269,6 +269,9 @@
<li<%= sidebar_current("docs-post-processors-checksum") %>>
<a href="/docs/post-processors/checksum.html">Checksum</a>
</li>
<li<%= sidebar_current("docs-post-processors-digitalocean-import") %>>
<a href="/docs/post-processors/digitalocean-import.html">DigitalOcean Import</a>
</li>
<li<%= sidebar_current("docs-post-processors-docker-import") %>>
<a href="/docs/post-processors/docker-import.html">Docker Import</a>
</li>