303 lines
8.5 KiB
Go
303 lines
8.5 KiB
Go
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 := resource.(type) {
|
|
case ResourceWithURN:
|
|
ar.Resources[i] = resource.URN()
|
|
case string:
|
|
ar.Resources[i] = resource
|
|
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
|
|
}
|