add projects support
This commit is contained in:
parent
b3d0928046
commit
0f1b9ca783
|
@ -1,5 +1,9 @@
|
|||
# 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
|
||||
|
|
|
@ -97,6 +97,10 @@ 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
|
||||
|
|
|
@ -125,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"`
|
||||
|
|
|
@ -49,6 +49,10 @@ 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"`
|
||||
|
|
|
@ -37,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"`
|
||||
|
|
12
godo.go
12
godo.go
|
@ -18,7 +18,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
libraryVersion = "1.5.0"
|
||||
libraryVersion = "1.6.0"
|
||||
defaultBaseURL = "https://api.digitalocean.com/"
|
||||
userAgent = "godo/" + libraryVersion
|
||||
mediaType = "application/json"
|
||||
|
@ -64,6 +64,7 @@ type Client struct {
|
|||
LoadBalancers LoadBalancersService
|
||||
Certificates CertificatesService
|
||||
Firewalls FirewallsService
|
||||
Projects ProjectsService
|
||||
|
||||
// Optional function called after every successful request made to the DO APIs
|
||||
onRequestCompleted RequestCompletionCallback
|
||||
|
@ -159,23 +160,24 @@ func NewClient(httpClient *http.Client) *Client {
|
|||
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.Firewalls = &FirewallsServiceOp{client: c}
|
||||
|
||||
return c
|
||||
}
|
||||
|
|
|
@ -47,6 +47,10 @@ 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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,609 @@
|
|||
package godo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProjects_List(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
projects := []Project{
|
||||
{
|
||||
ID: "project-1",
|
||||
Name: "project-1",
|
||||
},
|
||||
{
|
||||
ID: "project-2",
|
||||
Name: "project-2",
|
||||
},
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/projects", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, http.MethodGet)
|
||||
resp, _ := json.Marshal(projects)
|
||||
fmt.Fprint(w, fmt.Sprintf(`{"projects":%s}`, string(resp)))
|
||||
})
|
||||
|
||||
resp, _, err := client.Projects.List(ctx, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Projects.List returned error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(resp, projects) {
|
||||
t.Errorf("Projects.List returned %+v, expected %+v", resp, projects)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjects_ListWithMultiplePages(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mockResp := `
|
||||
{
|
||||
"projects": [
|
||||
{
|
||||
"uuid": "project-1",
|
||||
"name": "project-1"
|
||||
},
|
||||
{
|
||||
"uuid": "project-2",
|
||||
"name": "project-2"
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"pages": {
|
||||
"next": "http://example.com/v2/projects?page=2"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
mux.HandleFunc("/v2/projects", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, http.MethodGet)
|
||||
fmt.Fprint(w, mockResp)
|
||||
})
|
||||
|
||||
_, resp, err := client.Projects.List(ctx, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Projects.List returned error: %v", err)
|
||||
}
|
||||
|
||||
checkCurrentPage(t, resp, 1)
|
||||
}
|
||||
|
||||
func TestProjects_ListWithPageNumber(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mockResp := `
|
||||
{
|
||||
"projects": [
|
||||
{
|
||||
"uuid": "project-1",
|
||||
"name": "project-1"
|
||||
},
|
||||
{
|
||||
"uuid": "project-2",
|
||||
"name": "project-2"
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"pages": {
|
||||
"next": "http://example.com/v2/projects?page=3",
|
||||
"prev": "http://example.com/v2/projects?page=1",
|
||||
"last": "http://example.com/v2/projects?page=3",
|
||||
"first": "http://example.com/v2/projects?page=1"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
mux.HandleFunc("/v2/projects", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, http.MethodGet)
|
||||
fmt.Fprint(w, mockResp)
|
||||
})
|
||||
|
||||
_, resp, err := client.Projects.List(ctx, &ListOptions{Page: 2})
|
||||
if err != nil {
|
||||
t.Errorf("Projects.List returned error: %v", err)
|
||||
}
|
||||
|
||||
checkCurrentPage(t, resp, 2)
|
||||
}
|
||||
|
||||
func TestProjects_GetDefault(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
project := &Project{
|
||||
ID: "project-1",
|
||||
Name: "project-1",
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/projects/default", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, http.MethodGet)
|
||||
resp, _ := json.Marshal(project)
|
||||
fmt.Fprint(w, fmt.Sprintf(`{"project":%s}`, string(resp)))
|
||||
})
|
||||
|
||||
resp, _, err := client.Projects.GetDefault(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("Projects.GetDefault returned error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(resp, project) {
|
||||
t.Errorf("Projects.GetDefault returned %+v, expected %+v", resp, project)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjects_GetWithUUID(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
project := &Project{
|
||||
ID: "project-1",
|
||||
Name: "project-1",
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/projects/project-1", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, http.MethodGet)
|
||||
resp, _ := json.Marshal(project)
|
||||
fmt.Fprint(w, fmt.Sprintf(`{"project":%s}`, string(resp)))
|
||||
})
|
||||
|
||||
resp, _, err := client.Projects.Get(ctx, "project-1")
|
||||
if err != nil {
|
||||
t.Errorf("Projects.Get returned error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(resp, project) {
|
||||
t.Errorf("Projects.Get returned %+v, expected %+v", resp, project)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjects_Create(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
createRequest := &CreateProjectRequest{
|
||||
Name: "my project",
|
||||
Description: "for my stuff",
|
||||
Purpose: "Just trying out DigitalOcean",
|
||||
Environment: "Production",
|
||||
}
|
||||
|
||||
createResp := &Project{
|
||||
ID: "project-id",
|
||||
Name: createRequest.Name,
|
||||
Description: createRequest.Description,
|
||||
Purpose: createRequest.Purpose,
|
||||
Environment: createRequest.Environment,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/projects", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(CreateProjectRequest)
|
||||
err := json.NewDecoder(r.Body).Decode(v)
|
||||
if err != nil {
|
||||
t.Fatalf("decode json: %v", err)
|
||||
}
|
||||
|
||||
testMethod(t, r, http.MethodPost)
|
||||
if !reflect.DeepEqual(v, createRequest) {
|
||||
t.Errorf("Request body = %+v, expected %+v", v, createRequest)
|
||||
}
|
||||
|
||||
resp, _ := json.Marshal(createResp)
|
||||
fmt.Fprintf(w, fmt.Sprintf(`{"project":%s}`, string(resp)))
|
||||
})
|
||||
|
||||
project, _, err := client.Projects.Create(ctx, createRequest)
|
||||
if err != nil {
|
||||
t.Errorf("Projects.Create returned error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(project, createResp) {
|
||||
t.Errorf("Projects.Create returned %+v, expected %+v", project, createResp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjects_UpdateWithOneAttribute(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
updateRequest := &UpdateProjectRequest{
|
||||
Name: "my-great-project",
|
||||
}
|
||||
updateResp := &Project{
|
||||
ID: "project-id",
|
||||
Name: updateRequest.Name.(string),
|
||||
Description: "some-other-description",
|
||||
Purpose: "some-other-purpose",
|
||||
Environment: "some-other-env",
|
||||
IsDefault: false,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/projects/project-1", func(w http.ResponseWriter, r *http.Request) {
|
||||
reqBytes, respErr := ioutil.ReadAll(r.Body)
|
||||
if respErr != nil {
|
||||
t.Error("projects mock didn't work")
|
||||
}
|
||||
|
||||
req := strings.TrimSuffix(string(reqBytes), "\n")
|
||||
expectedReq := `{"name":"my-great-project","description":null,"purpose":null,"environment":null,"is_default":null}`
|
||||
if req != expectedReq {
|
||||
t.Errorf("projects req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req)
|
||||
}
|
||||
|
||||
resp, _ := json.Marshal(updateResp)
|
||||
fmt.Fprintf(w, fmt.Sprintf(`{"project":%s}`, string(resp)))
|
||||
})
|
||||
|
||||
project, _, err := client.Projects.Update(ctx, "project-1", updateRequest)
|
||||
if err != nil {
|
||||
t.Errorf("Projects.Update returned error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(project, updateResp) {
|
||||
t.Errorf("Projects.Update returned %+v, expected %+v", project, updateResp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjects_UpdateWithAllAttributes(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
updateRequest := &UpdateProjectRequest{
|
||||
Name: "my-great-project",
|
||||
Description: "some-description",
|
||||
Purpose: "some-purpose",
|
||||
Environment: "some-env",
|
||||
IsDefault: true,
|
||||
}
|
||||
updateResp := &Project{
|
||||
ID: "project-id",
|
||||
Name: updateRequest.Name.(string),
|
||||
Description: updateRequest.Description.(string),
|
||||
Purpose: updateRequest.Purpose.(string),
|
||||
Environment: updateRequest.Environment.(string),
|
||||
IsDefault: updateRequest.IsDefault.(bool),
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/projects/project-1", func(w http.ResponseWriter, r *http.Request) {
|
||||
reqBytes, respErr := ioutil.ReadAll(r.Body)
|
||||
if respErr != nil {
|
||||
t.Error("projects mock didn't work")
|
||||
}
|
||||
|
||||
req := strings.TrimSuffix(string(reqBytes), "\n")
|
||||
expectedReq := `{"name":"my-great-project","description":"some-description","purpose":"some-purpose","environment":"some-env","is_default":true}`
|
||||
if req != expectedReq {
|
||||
t.Errorf("projects req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req)
|
||||
}
|
||||
|
||||
resp, _ := json.Marshal(updateResp)
|
||||
fmt.Fprintf(w, fmt.Sprintf(`{"project":%s}`, string(resp)))
|
||||
})
|
||||
|
||||
project, _, err := client.Projects.Update(ctx, "project-1", updateRequest)
|
||||
if err != nil {
|
||||
t.Errorf("Projects.Update returned error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(project, updateResp) {
|
||||
t.Errorf("Projects.Update returned %+v, expected %+v", project, updateResp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjects_Destroy(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/v2/projects/project-1", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, http.MethodDelete)
|
||||
})
|
||||
|
||||
_, err := client.Projects.Delete(ctx, "project-1")
|
||||
if err != nil {
|
||||
t.Errorf("Projects.Delete returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjects_ListResources(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
resources := []ProjectResource{
|
||||
{
|
||||
URN: "do:droplet:1",
|
||||
AssignedAt: "2018-09-27 00:00:00",
|
||||
Links: &ProjectResourceLinks{
|
||||
Self: "http://example.com/v2/droplets/1",
|
||||
},
|
||||
},
|
||||
{
|
||||
URN: "do:floatingip:1.2.3.4",
|
||||
AssignedAt: "2018-09-27 00:00:00",
|
||||
Links: &ProjectResourceLinks{
|
||||
Self: "http://example.com/v2/floating_ips/1.2.3.4",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, http.MethodGet)
|
||||
resp, _ := json.Marshal(resources)
|
||||
fmt.Fprint(w, fmt.Sprintf(`{"resources":%s}`, string(resp)))
|
||||
})
|
||||
|
||||
resp, _, err := client.Projects.ListResources(ctx, "project-1", nil)
|
||||
if err != nil {
|
||||
t.Errorf("Projects.List returned error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(resp, resources) {
|
||||
t.Errorf("Projects.ListResources returned %+v, expected %+v", resp, resources)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjects_ListResourcesWithMultiplePages(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mockResp := `
|
||||
{
|
||||
"resources": [
|
||||
{
|
||||
"urn": "do:droplet:1",
|
||||
"assigned_at": "2018-09-27 00:00:00",
|
||||
"links": {
|
||||
"self": "http://example.com/v2/droplets/1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"urn": "do:floatingip:1.2.3.4",
|
||||
"assigned_at": "2018-09-27 00:00:00",
|
||||
"links": {
|
||||
"self": "http://example.com/v2/floating_ips/1.2.3.4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"pages": {
|
||||
"next": "http://example.com/v2/projects/project-1/resources?page=2"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, http.MethodGet)
|
||||
fmt.Fprint(w, mockResp)
|
||||
})
|
||||
|
||||
_, resp, err := client.Projects.ListResources(ctx, "project-1", nil)
|
||||
if err != nil {
|
||||
t.Errorf("Projects.ListResources returned error: %v", err)
|
||||
}
|
||||
|
||||
checkCurrentPage(t, resp, 1)
|
||||
}
|
||||
|
||||
func TestProjects_ListResourcesWithPageNumber(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mockResp := `
|
||||
{
|
||||
"resources": [
|
||||
{
|
||||
"urn": "do:droplet:1",
|
||||
"assigned_at": "2018-09-27 00:00:00",
|
||||
"links": {
|
||||
"self": "http://example.com/v2/droplets/1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"urn": "do:floatingip:1.2.3.4",
|
||||
"assigned_at": "2018-09-27 00:00:00",
|
||||
"links": {
|
||||
"self": "http://example.com/v2/floating_ips/1.2.3.4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"pages": {
|
||||
"next": "http://example.com/v2/projects/project-1/resources?page=3",
|
||||
"prev": "http://example.com/v2/projects/project-1/resources?page=1",
|
||||
"last": "http://example.com/v2/projects/project-1/resources?page=3",
|
||||
"first": "http://example.com/v2/projects/project-1/resources?page=1"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, http.MethodGet)
|
||||
fmt.Fprint(w, mockResp)
|
||||
})
|
||||
|
||||
_, resp, err := client.Projects.ListResources(ctx, "project-1", &ListOptions{Page: 2})
|
||||
if err != nil {
|
||||
t.Errorf("Projects.ListResources returned error: %v", err)
|
||||
}
|
||||
|
||||
checkCurrentPage(t, resp, 2)
|
||||
}
|
||||
|
||||
func TestProjects_AssignFleetResourcesWithTypes(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
assignableResources := []interface{}{
|
||||
&Droplet{ID: 1234},
|
||||
&FloatingIP{IP: "1.2.3.4"},
|
||||
}
|
||||
|
||||
mockResp := `
|
||||
{
|
||||
"resources": [
|
||||
{
|
||||
"urn": "do:droplet:1234",
|
||||
"assigned_at": "2018-09-27 00:00:00",
|
||||
"links": {
|
||||
"self": "http://example.com/v2/droplets/1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"urn": "do:floatingip:1.2.3.4",
|
||||
"assigned_at": "2018-09-27 00:00:00",
|
||||
"links": {
|
||||
"self": "http://example.com/v2/floating_ips/1.2.3.4"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, http.MethodPost)
|
||||
reqBytes, respErr := ioutil.ReadAll(r.Body)
|
||||
if respErr != nil {
|
||||
t.Error("projects mock didn't work")
|
||||
}
|
||||
|
||||
req := strings.TrimSuffix(string(reqBytes), "\n")
|
||||
expectedReq := `{"resources":["do:droplet:1234","do:floatingip:1.2.3.4"]}`
|
||||
if req != expectedReq {
|
||||
t.Errorf("projects assign req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, mockResp)
|
||||
})
|
||||
|
||||
_, _, err := client.Projects.AssignResources(ctx, "project-1", assignableResources...)
|
||||
if err != nil {
|
||||
t.Errorf("Projects.AssignResources returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjects_AssignFleetResourcesWithStrings(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
assignableResources := []interface{}{
|
||||
"do:droplet:1234",
|
||||
"do:floatingip:1.2.3.4",
|
||||
}
|
||||
|
||||
mockResp := `
|
||||
{
|
||||
"resources": [
|
||||
{
|
||||
"urn": "do:droplet:1234",
|
||||
"assigned_at": "2018-09-27 00:00:00",
|
||||
"links": {
|
||||
"self": "http://example.com/v2/droplets/1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"urn": "do:floatingip:1.2.3.4",
|
||||
"assigned_at": "2018-09-27 00:00:00",
|
||||
"links": {
|
||||
"self": "http://example.com/v2/floating_ips/1.2.3.4"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, http.MethodPost)
|
||||
reqBytes, respErr := ioutil.ReadAll(r.Body)
|
||||
if respErr != nil {
|
||||
t.Error("projects mock didn't work")
|
||||
}
|
||||
|
||||
req := strings.TrimSuffix(string(reqBytes), "\n")
|
||||
expectedReq := `{"resources":["do:droplet:1234","do:floatingip:1.2.3.4"]}`
|
||||
if req != expectedReq {
|
||||
t.Errorf("projects assign req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, mockResp)
|
||||
})
|
||||
|
||||
_, _, err := client.Projects.AssignResources(ctx, "project-1", assignableResources...)
|
||||
if err != nil {
|
||||
t.Errorf("Projects.AssignResources returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjects_AssignFleetResourcesWithStringsAndTypes(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
assignableResources := []interface{}{
|
||||
"do:droplet:1234",
|
||||
&FloatingIP{IP: "1.2.3.4"},
|
||||
}
|
||||
|
||||
mockResp := `
|
||||
{
|
||||
"resources": [
|
||||
{
|
||||
"urn": "do:droplet:1234",
|
||||
"assigned_at": "2018-09-27 00:00:00",
|
||||
"links": {
|
||||
"self": "http://example.com/v2/droplets/1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"urn": "do:floatingip:1.2.3.4",
|
||||
"assigned_at": "2018-09-27 00:00:00",
|
||||
"links": {
|
||||
"self": "http://example.com/v2/floating_ips/1.2.3.4"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, http.MethodPost)
|
||||
reqBytes, respErr := ioutil.ReadAll(r.Body)
|
||||
if respErr != nil {
|
||||
t.Error("projects mock didn't work")
|
||||
}
|
||||
|
||||
req := strings.TrimSuffix(string(reqBytes), "\n")
|
||||
expectedReq := `{"resources":["do:droplet:1234","do:floatingip:1.2.3.4"]}`
|
||||
if req != expectedReq {
|
||||
t.Errorf("projects assign req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, mockResp)
|
||||
})
|
||||
|
||||
_, _, err := client.Projects.AssignResources(ctx, "project-1", assignableResources...)
|
||||
if err != nil {
|
||||
t.Errorf("Projects.AssignResources returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjects_AssignFleetResourcesWithTypeWithoutURNReturnsError(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
type fakeType struct{}
|
||||
|
||||
assignableResources := []interface{}{
|
||||
fakeType{},
|
||||
}
|
||||
|
||||
_, _, err := client.Projects.AssignResources(ctx, "project-1", assignableResources...)
|
||||
if err == nil {
|
||||
t.Errorf("expected Projects.AssignResources to error, but it did not")
|
||||
}
|
||||
|
||||
if err.Error() != "godo.fakeType must either be a string or have a valid URN method" {
|
||||
t.Errorf("Projects.AssignResources returned the wrong error: %v", err)
|
||||
}
|
||||
}
|
|
@ -59,6 +59,10 @@ 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"`
|
||||
|
|
10
strings.go
10
strings.go
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue