package godo import ( "context" "fmt" "net/http" "time" ) const ( appsBasePath = "/v2/apps" ) // AppLogType is the type of app logs. type AppLogType string const ( // AppLogTypeBuild represents build logs. AppLogTypeBuild AppLogType = "BUILD" // AppLogTypeDeploy represents deploy logs. AppLogTypeDeploy AppLogType = "DEPLOY" // AppLogTypeRun represents run logs. AppLogTypeRun AppLogType = "RUN" ) // AppsService is an interface for interfacing with the App Platform endpoints // of the DigitalOcean API. type AppsService interface { Create(ctx context.Context, create *AppCreateRequest) (*App, *Response, error) Get(ctx context.Context, appID string) (*App, *Response, error) List(ctx context.Context, opts *ListOptions) ([]*App, *Response, error) Update(ctx context.Context, appID string, update *AppUpdateRequest) (*App, *Response, error) Delete(ctx context.Context, appID string) (*Response, error) GetDeployment(ctx context.Context, appID, deploymentID string) (*Deployment, *Response, error) ListDeployments(ctx context.Context, appID string, opts *ListOptions) ([]*Deployment, *Response, error) CreateDeployment(ctx context.Context, appID string) (*Deployment, *Response, error) GetLogs(ctx context.Context, appID, deploymentID, component string, logType AppLogType, follow bool) (*AppLogs, *Response, error) } // App represents an app. type App struct { ID string `json:"id"` Spec *AppSpec `json:"spec"` DefaultIngress string `json:"default_ingress"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at,omitempty"` ActiveDeployment *Deployment `json:"active_deployment,omitempty"` InProgressDeployment *Deployment `json:"in_progress_deployment,omitempty"` } // Deployment represents a deployment for an app. type Deployment struct { ID string `json:"id"` Spec *AppSpec `json:"spec"` Services []*DeploymentService `json:"services,omitempty"` Workers []*DeploymentWorker `json:"workers,omitempty"` StaticSites []*DeploymentStaticSite `json:"static_sites,omitempty"` Cause string `json:"cause"` Progress *DeploymentProgress `json:"progress"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at,omitempty"` } // DeploymentService represents a service component in a deployment. type DeploymentService struct { Name string `json:"name,omitempty"` SourceCommitHash string `json:"source_commit_hash"` } // DeploymentWorker represents a worker component in a deployment. type DeploymentWorker struct { Name string `json:"name,omitempty"` SourceCommitHash string `json:"source_commit_hash"` } // DeploymentStaticSite represents a static site component in a deployment. type DeploymentStaticSite struct { Name string `json:"name,omitempty"` SourceCommitHash string `json:"source_commit_hash"` } // DeploymentProgress represents the total progress of a deployment. type DeploymentProgress struct { PendingSteps int `json:"pending_steps"` RunningSteps int `json:"running_steps"` SuccessSteps int `json:"success_steps"` ErrorSteps int `json:"error_steps"` TotalSteps int `json:"total_steps"` Steps []*DeploymentProgressStep `json:"steps"` } // DeploymentProgressStep represents the progress of a deployment step. type DeploymentProgressStep struct { Name string `json:"name"` Status string `json:"status"` Steps []*DeploymentProgressStep `json:"steps,omitempty"` Attempts uint32 `json:"attempts"` StartedAt time.Time `json:"started_at,omitempty"` EndedAt time.Time `json:"ended_at,omitempty"` } // AppLogs represent app logs. type AppLogs struct { LiveURL string `json:"live_url"` HistoricURLs []string `json:"historic_urls"` } // AppCreateRequest represents a request to create an app. type AppCreateRequest struct { Spec *AppSpec `json:"spec"` } // AppUpdateRequest represents a request to update an app. type AppUpdateRequest struct { Spec *AppSpec `json:"spec"` } type appRoot struct { App *App `json:"app"` } type appsRoot struct { Apps []*App `json:"apps"` } type deploymentRoot struct { Deployment *Deployment `json:"deployment"` } type deploymentsRoot struct { Deployments []*Deployment `json:"deployments"` } // AppsServiceOp handles communication with Apps methods of the DigitalOcean API. type AppsServiceOp struct { client *Client } // Creates an app. func (s *AppsServiceOp) Create(ctx context.Context, create *AppCreateRequest) (*App, *Response, error) { path := appsBasePath req, err := s.client.NewRequest(ctx, http.MethodPost, path, create) if err != nil { return nil, nil, err } root := new(appRoot) resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } return root.App, resp, nil } // Get an app. func (s *AppsServiceOp) Get(ctx context.Context, appID string) (*App, *Response, error) { path := fmt.Sprintf("%s/%s", appsBasePath, appID) req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { return nil, nil, err } root := new(appRoot) resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } return root.App, resp, nil } // List apps. func (s *AppsServiceOp) List(ctx context.Context, opts *ListOptions) ([]*App, *Response, error) { path := appsBasePath req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { return nil, nil, err } root := new(appsRoot) resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } return root.Apps, resp, nil } // Update an app. func (s *AppsServiceOp) Update(ctx context.Context, appID string, update *AppUpdateRequest) (*App, *Response, error) { path := fmt.Sprintf("%s/%s", appsBasePath, appID) req, err := s.client.NewRequest(ctx, http.MethodPut, path, update) if err != nil { return nil, nil, err } root := new(appRoot) resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } return root.App, resp, nil } // Delete an app. func (s *AppsServiceOp) Delete(ctx context.Context, appID string) (*Response, error) { path := fmt.Sprintf("%s/%s", appsBasePath, appID) req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) if err != nil { return nil, err } resp, err := s.client.Do(ctx, req, nil) if err != nil { return resp, err } return resp, nil } // GetDeployment gets an app deployment. func (s *AppsServiceOp) GetDeployment(ctx context.Context, appID, deploymentID string) (*Deployment, *Response, error) { path := fmt.Sprintf("%s/%s/deployments/%s", appsBasePath, appID, deploymentID) req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { return nil, nil, err } root := new(deploymentRoot) resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } return root.Deployment, resp, nil } // ListDeployments lists an app deployments. func (s *AppsServiceOp) ListDeployments(ctx context.Context, appID string, opts *ListOptions) ([]*Deployment, *Response, error) { path := fmt.Sprintf("%s/%s/deployments", appsBasePath, appID) req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { return nil, nil, err } root := new(deploymentsRoot) resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } return root.Deployments, resp, nil } // CreateDeployment creates an app deployment. func (s *AppsServiceOp) CreateDeployment(ctx context.Context, appID string) (*Deployment, *Response, error) { path := fmt.Sprintf("%s/%s/deployments", appsBasePath, appID) req, err := s.client.NewRequest(ctx, http.MethodPost, path, nil) if err != nil { return nil, nil, err } root := new(deploymentRoot) resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } return root.Deployment, resp, nil } // GetLogs retrieves app logs. func (s *AppsServiceOp) GetLogs(ctx context.Context, appID, deploymentID, component string, logType AppLogType, follow bool) (*AppLogs, *Response, error) { url := fmt.Sprintf("%s/%s/deployments/%s/components/%s/logs?type=%s&follow=%t", appsBasePath, appID, deploymentID, component, logType, follow) req, err := s.client.NewRequest(ctx, http.MethodGet, url, nil) if err != nil { return nil, nil, err } logs := new(AppLogs) resp, err := s.client.Do(ctx, req, logs) if err != nil { return nil, resp, err } return logs, resp, nil }