feat: Add templates to create working release (#422)

* Add templates

* Move API structs to codersdk

* Back to green tests!

* It all works, but now with tea! 🧋

* It works!

* Add cancellation to provisionerd

* Tests pass!

* Add deletion of workspaces and projects

* Fix agent lock

* Add clog

* Fix linting errors

* Remove unused CLI tests

* Rename daemon to start

* Fix leaking command

* Fix promptui test

* Update agent connection frequency

* Skip login tests on Windows

* Increase tunnel connect timeout

* Fix templater

* Lower test requirements

* Fix embed

* Disable promptui tests for Windows

* Fix write newline

* Fix PTY write newline

* Fix CloseReader

* Fix compilation on Windows

* Fix linting error

* Remove bubbletea

* Cleanup readwriter

* Use embedded templates instead of serving over API

* Move templates to examples

* Improve workspace create flow

* Fix Windows build

* Fix tests

* Fix linting errors

* Fix untar with extracting max size

* Fix newline char
This commit is contained in:
Kyle Carberry
2022-03-22 13:17:50 -06:00
committed by GitHub
parent 2818b3ce6d
commit c451f4e685
138 changed files with 7317 additions and 2334 deletions

View File

@ -6,28 +6,31 @@ import (
"fmt"
"io"
"net/http"
"github.com/coder/coder/coderd"
)
const (
ContentTypeTar = "application/x-tar"
)
// UploadResponse contains the hash to reference the uploaded file.
type UploadResponse struct {
Hash string `json:"hash"`
}
// Upload uploads an arbitrary file with the content type provided.
// This is used to upload a source-code archive.
func (c *Client) Upload(ctx context.Context, contentType string, content []byte) (coderd.UploadResponse, error) {
func (c *Client) Upload(ctx context.Context, contentType string, content []byte) (UploadResponse, error) {
res, err := c.request(ctx, http.MethodPost, "/api/v2/files", content, func(r *http.Request) {
r.Header.Set("Content-Type", contentType)
})
if err != nil {
return coderd.UploadResponse{}, err
return UploadResponse{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated && res.StatusCode != http.StatusOK {
return coderd.UploadResponse{}, readBodyAsError(res)
return UploadResponse{}, readBodyAsError(res)
}
var resp coderd.UploadResponse
var resp UploadResponse
return resp, json.NewDecoder(res.Body).Decode(&resp)
}

View File

@ -5,25 +5,63 @@ import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/coder/coder/coderd"
"github.com/google/uuid"
"github.com/coder/coder/database"
)
func (c *Client) Organization(ctx context.Context, id string) (coderd.Organization, error) {
// Organization is the JSON representation of a Coder organization.
type Organization struct {
ID string `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
CreatedAt time.Time `json:"created_at" validate:"required"`
UpdatedAt time.Time `json:"updated_at" validate:"required"`
}
// CreateProjectVersionRequest enables callers to create a new Project Version.
type CreateProjectVersionRequest struct {
// ProjectID optionally associates a version with a project.
ProjectID uuid.UUID `json:"project_id"`
StorageMethod database.ProvisionerStorageMethod `json:"storage_method" validate:"oneof=file,required"`
StorageSource string `json:"storage_source" validate:"required"`
Provisioner database.ProvisionerType `json:"provisioner" validate:"oneof=terraform echo,required"`
// ParameterValues allows for additional parameters to be provided
// during the dry-run provision stage.
ParameterValues []CreateParameterRequest `json:"parameter_values"`
}
// CreateProjectRequest provides options when creating a project.
type CreateProjectRequest struct {
Name string `json:"name" validate:"username,required"`
// VersionID is an in-progress or completed job to use as
// an initial version of the project.
//
// This is required on creation to enable a user-flow of validating a
// project works. There is no reason the data-model cannot support
// empty projects, but it doesn't make sense for users.
VersionID uuid.UUID `json:"project_version_id" validate:"required"`
ParameterValues []CreateParameterRequest `json:"parameter_values"`
}
func (c *Client) Organization(ctx context.Context, id string) (Organization, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s", id), nil)
if err != nil {
return coderd.Organization{}, err
return Organization{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.Organization{}, readBodyAsError(res)
return Organization{}, readBodyAsError(res)
}
var organization coderd.Organization
var organization Organization
return organization, json.NewDecoder(res.Body).Decode(&organization)
}
// ProvisionerDaemonsByOrganization returns provisioner daemons available for an organization.
func (c *Client) ProvisionerDaemonsByOrganization(ctx context.Context, organization string) ([]coderd.ProvisionerDaemon, error) {
func (c *Client) ProvisionerDaemonsByOrganization(ctx context.Context, organization string) ([]ProvisionerDaemon, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/provisionerdaemons", organization), nil)
if err != nil {
return nil, err
@ -32,41 +70,41 @@ func (c *Client) ProvisionerDaemonsByOrganization(ctx context.Context, organizat
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var daemons []coderd.ProvisionerDaemon
var daemons []ProvisionerDaemon
return daemons, json.NewDecoder(res.Body).Decode(&daemons)
}
// CreateProjectVersion processes source-code and optionally associates the version with a project.
// Executing without a project is useful for validating source-code.
func (c *Client) CreateProjectVersion(ctx context.Context, organization string, req coderd.CreateProjectVersionRequest) (coderd.ProjectVersion, error) {
func (c *Client) CreateProjectVersion(ctx context.Context, organization string, req CreateProjectVersionRequest) (ProjectVersion, error) {
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/organizations/%s/projectversions", organization), req)
if err != nil {
return coderd.ProjectVersion{}, err
return ProjectVersion{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.ProjectVersion{}, readBodyAsError(res)
return ProjectVersion{}, readBodyAsError(res)
}
var projectVersion coderd.ProjectVersion
var projectVersion ProjectVersion
return projectVersion, json.NewDecoder(res.Body).Decode(&projectVersion)
}
// CreateProject creates a new project inside an organization.
func (c *Client) CreateProject(ctx context.Context, organization string, request coderd.CreateProjectRequest) (coderd.Project, error) {
func (c *Client) CreateProject(ctx context.Context, organization string, request CreateProjectRequest) (Project, error) {
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/organizations/%s/projects", organization), request)
if err != nil {
return coderd.Project{}, err
return Project{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.Project{}, readBodyAsError(res)
return Project{}, readBodyAsError(res)
}
var project coderd.Project
var project Project
return project, json.NewDecoder(res.Body).Decode(&project)
}
// ProjectsByOrganization lists all projects inside of an organization.
func (c *Client) ProjectsByOrganization(ctx context.Context, organization string) ([]coderd.Project, error) {
func (c *Client) ProjectsByOrganization(ctx context.Context, organization string) ([]Project, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/projects", organization), nil)
if err != nil {
return nil, err
@ -75,20 +113,20 @@ func (c *Client) ProjectsByOrganization(ctx context.Context, organization string
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var projects []coderd.Project
var projects []Project
return projects, json.NewDecoder(res.Body).Decode(&projects)
}
// ProjectByName finds a project inside the organization provided with a case-insensitive name.
func (c *Client) ProjectByName(ctx context.Context, organization, name string) (coderd.Project, error) {
func (c *Client) ProjectByName(ctx context.Context, organization, name string) (Project, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/projects/%s", organization, name), nil)
if err != nil {
return coderd.Project{}, err
return Project{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.Project{}, readBodyAsError(res)
return Project{}, readBodyAsError(res)
}
var project coderd.Project
var project Project
return project, json.NewDecoder(res.Body).Decode(&project)
}

View File

@ -5,24 +5,56 @@ import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/coder/coder/coderd"
"github.com/google/uuid"
"github.com/coder/coder/database"
)
func (c *Client) CreateParameter(ctx context.Context, scope coderd.ParameterScope, id string, req coderd.CreateParameterRequest) (coderd.Parameter, error) {
type ParameterScope string
const (
ParameterOrganization ParameterScope = "organization"
ParameterProject ParameterScope = "project"
ParameterUser ParameterScope = "user"
ParameterWorkspace ParameterScope = "workspace"
)
// Parameter represents a set value for the scope.
type Parameter struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Scope ParameterScope `db:"scope" json:"scope"`
ScopeID string `db:"scope_id" json:"scope_id"`
Name string `db:"name" json:"name"`
SourceScheme database.ParameterSourceScheme `db:"source_scheme" json:"source_scheme"`
DestinationScheme database.ParameterDestinationScheme `db:"destination_scheme" json:"destination_scheme"`
}
// CreateParameterRequest is used to create a new parameter value for a scope.
type CreateParameterRequest struct {
Name string `json:"name" validate:"required"`
SourceValue string `json:"source_value" validate:"required"`
SourceScheme database.ParameterSourceScheme `json:"source_scheme" validate:"oneof=data,required"`
DestinationScheme database.ParameterDestinationScheme `json:"destination_scheme" validate:"oneof=environment_variable provisioner_variable,required"`
}
func (c *Client) CreateParameter(ctx context.Context, scope ParameterScope, id string, req CreateParameterRequest) (Parameter, error) {
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/parameters/%s/%s", scope, id), req)
if err != nil {
return coderd.Parameter{}, err
return Parameter{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.Parameter{}, readBodyAsError(res)
return Parameter{}, readBodyAsError(res)
}
var param coderd.Parameter
var param Parameter
return param, json.NewDecoder(res.Body).Decode(&param)
}
func (c *Client) DeleteParameter(ctx context.Context, scope coderd.ParameterScope, id, name string) error {
func (c *Client) DeleteParameter(ctx context.Context, scope ParameterScope, id, name string) error {
res, err := c.request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/parameters/%s/%s/%s", scope, id, name), nil)
if err != nil {
return err
@ -34,7 +66,7 @@ func (c *Client) DeleteParameter(ctx context.Context, scope coderd.ParameterScop
return nil
}
func (c *Client) Parameters(ctx context.Context, scope coderd.ParameterScope, id string) ([]coderd.Parameter, error) {
func (c *Client) Parameters(ctx context.Context, scope ParameterScope, id string) ([]Parameter, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/parameters/%s/%s", scope, id), nil)
if err != nil {
return nil, err
@ -43,6 +75,6 @@ func (c *Client) Parameters(ctx context.Context, scope coderd.ParameterScope, id
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var parameters []coderd.Parameter
var parameters []Parameter
return parameters, json.NewDecoder(res.Body).Decode(&parameters)
}

View File

@ -5,28 +5,73 @@ import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/google/uuid"
"github.com/coder/coder/coderd"
"github.com/coder/coder/database"
)
// Project is the JSON representation of a Coder project.
// This type matches the database object for now, but is
// abstracted for ease of change later on.
type Project struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
OrganizationID string `json:"organization_id"`
Name string `json:"name"`
Provisioner database.ProvisionerType `json:"provisioner"`
ActiveVersionID uuid.UUID `json:"active_version_id"`
WorkspaceOwnerCount uint32 `json:"workspace_owner_count"`
}
type UpdateActiveProjectVersion struct {
ID uuid.UUID `json:"id" validate:"required"`
}
// Project returns a single project.
func (c *Client) Project(ctx context.Context, project uuid.UUID) (coderd.Project, error) {
func (c *Client) Project(ctx context.Context, project uuid.UUID) (Project, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projects/%s", project), nil)
if err != nil {
return coderd.Project{}, nil
return Project{}, nil
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.Project{}, readBodyAsError(res)
return Project{}, readBodyAsError(res)
}
var resp coderd.Project
var resp Project
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
func (c *Client) DeleteProject(ctx context.Context, project uuid.UUID) error {
res, err := c.request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/projects/%s", project), nil)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return readBodyAsError(res)
}
return nil
}
// UpdateActiveProjectVersion updates the active project version to the ID provided.
// The project version must be attached to the project.
func (c *Client) UpdateActiveProjectVersion(ctx context.Context, project uuid.UUID, req UpdateActiveProjectVersion) error {
res, err := c.request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/projects/%s/versions", project), req)
if err != nil {
return nil
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return readBodyAsError(res)
}
return nil
}
// ProjectVersionsByProject lists versions associated with a project.
func (c *Client) ProjectVersionsByProject(ctx context.Context, project uuid.UUID) ([]coderd.ProjectVersion, error) {
func (c *Client) ProjectVersionsByProject(ctx context.Context, project uuid.UUID) ([]ProjectVersion, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projects/%s/versions", project), nil)
if err != nil {
return nil, err
@ -35,21 +80,21 @@ func (c *Client) ProjectVersionsByProject(ctx context.Context, project uuid.UUID
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var projectVersion []coderd.ProjectVersion
var projectVersion []ProjectVersion
return projectVersion, json.NewDecoder(res.Body).Decode(&projectVersion)
}
// ProjectVersionByName returns a project version by it's friendly name.
// This is used for path-based routing. Like: /projects/example/versions/helloworld
func (c *Client) ProjectVersionByName(ctx context.Context, project uuid.UUID, name string) (coderd.ProjectVersion, error) {
func (c *Client) ProjectVersionByName(ctx context.Context, project uuid.UUID, name string) (ProjectVersion, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projects/%s/versions/%s", project, name), nil)
if err != nil {
return coderd.ProjectVersion{}, err
return ProjectVersion{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.ProjectVersion{}, readBodyAsError(res)
return ProjectVersion{}, readBodyAsError(res)
}
var projectVersion coderd.ProjectVersion
var projectVersion ProjectVersion
return projectVersion, json.NewDecoder(res.Body).Decode(&projectVersion)
}

View File

@ -9,25 +9,55 @@ import (
"github.com/google/uuid"
"github.com/coder/coder/coderd"
"github.com/coder/coder/coderd/parameter"
"github.com/coder/coder/database"
)
// ProjectVersion represents a single version of a project.
type ProjectVersion struct {
ID uuid.UUID `json:"id"`
ProjectID *uuid.UUID `json:"project_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Name string `json:"name"`
Job ProvisionerJob `json:"job"`
}
// ProjectVersionParameterSchema represents a parameter parsed from project version source.
type ProjectVersionParameterSchema database.ParameterSchema
// ProjectVersionParameter represents a computed parameter value.
type ProjectVersionParameter parameter.ComputedValue
// ProjectVersion returns a project version by ID.
func (c *Client) ProjectVersion(ctx context.Context, id uuid.UUID) (coderd.ProjectVersion, error) {
func (c *Client) ProjectVersion(ctx context.Context, id uuid.UUID) (ProjectVersion, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projectversions/%s", id), nil)
if err != nil {
return coderd.ProjectVersion{}, err
return ProjectVersion{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.ProjectVersion{}, readBodyAsError(res)
return ProjectVersion{}, readBodyAsError(res)
}
var version coderd.ProjectVersion
var version ProjectVersion
return version, json.NewDecoder(res.Body).Decode(&version)
}
// CancelProjectVersion marks a project version job as canceled.
func (c *Client) CancelProjectVersion(ctx context.Context, version uuid.UUID) error {
res, err := c.request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/projectversions/%s/cancel", version), nil)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return readBodyAsError(res)
}
return nil
}
// ProjectVersionSchema returns schemas for a project version by ID.
func (c *Client) ProjectVersionSchema(ctx context.Context, version uuid.UUID) ([]coderd.ProjectVersionParameterSchema, error) {
func (c *Client) ProjectVersionSchema(ctx context.Context, version uuid.UUID) ([]ProjectVersionParameterSchema, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projectversions/%s/schema", version), nil)
if err != nil {
return nil, err
@ -36,12 +66,12 @@ func (c *Client) ProjectVersionSchema(ctx context.Context, version uuid.UUID) ([
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var params []coderd.ProjectVersionParameterSchema
var params []ProjectVersionParameterSchema
return params, json.NewDecoder(res.Body).Decode(&params)
}
// ProjectVersionParameters returns computed parameters for a project version.
func (c *Client) ProjectVersionParameters(ctx context.Context, version uuid.UUID) ([]coderd.ProjectVersionParameter, error) {
func (c *Client) ProjectVersionParameters(ctx context.Context, version uuid.UUID) ([]ProjectVersionParameter, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projectversions/%s/parameters", version), nil)
if err != nil {
return nil, err
@ -50,12 +80,12 @@ func (c *Client) ProjectVersionParameters(ctx context.Context, version uuid.UUID
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var params []coderd.ProjectVersionParameter
var params []ProjectVersionParameter
return params, json.NewDecoder(res.Body).Decode(&params)
}
// ProjectVersionResources returns resources a project version declares.
func (c *Client) ProjectVersionResources(ctx context.Context, version uuid.UUID) ([]coderd.WorkspaceResource, error) {
func (c *Client) ProjectVersionResources(ctx context.Context, version uuid.UUID) ([]WorkspaceResource, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projectversions/%s/resources", version), nil)
if err != nil {
return nil, err
@ -64,16 +94,16 @@ func (c *Client) ProjectVersionResources(ctx context.Context, version uuid.UUID)
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var resources []coderd.WorkspaceResource
var resources []WorkspaceResource
return resources, json.NewDecoder(res.Body).Decode(&resources)
}
// ProjectVersionLogsBefore returns logs that occurred before a specific time.
func (c *Client) ProjectVersionLogsBefore(ctx context.Context, version uuid.UUID, before time.Time) ([]coderd.ProvisionerJobLog, error) {
func (c *Client) ProjectVersionLogsBefore(ctx context.Context, version uuid.UUID, before time.Time) ([]ProvisionerJobLog, error) {
return c.provisionerJobLogsBefore(ctx, fmt.Sprintf("/api/v2/projectversions/%s/logs", version), before)
}
// ProjectVersionLogsAfter streams logs for a project version that occurred after a specific time.
func (c *Client) ProjectVersionLogsAfter(ctx context.Context, version uuid.UUID, after time.Time) (<-chan coderd.ProvisionerJobLog, error) {
func (c *Client) ProjectVersionLogsAfter(ctx context.Context, version uuid.UUID, after time.Time) (<-chan ProvisionerJobLog, error) {
return c.provisionerJobLogsAfter(ctx, fmt.Sprintf("/api/v2/projectversions/%s/logs", version), after)
}

View File

@ -10,15 +10,48 @@ import (
"strconv"
"time"
"github.com/google/uuid"
"github.com/hashicorp/yamux"
"golang.org/x/xerrors"
"nhooyr.io/websocket"
"github.com/coder/coder/coderd"
"github.com/coder/coder/database"
"github.com/coder/coder/provisionerd/proto"
"github.com/coder/coder/provisionersdk"
)
type ProvisionerDaemon database.ProvisionerDaemon
// ProvisionerJobStaus represents the at-time state of a job.
type ProvisionerJobStatus string
const (
ProvisionerJobPending ProvisionerJobStatus = "pending"
ProvisionerJobRunning ProvisionerJobStatus = "running"
ProvisionerJobSucceeded ProvisionerJobStatus = "succeeded"
ProvisionerJobCanceling ProvisionerJobStatus = "canceling"
ProvisionerJobCanceled ProvisionerJobStatus = "canceled"
ProvisionerJobFailed ProvisionerJobStatus = "failed"
)
type ProvisionerJob struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
StartedAt *time.Time `json:"started_at,omitempty"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
Error string `json:"error,omitempty"`
Status ProvisionerJobStatus `json:"status"`
WorkerID *uuid.UUID `json:"worker_id,omitempty"`
}
type ProvisionerJobLog struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
Source database.LogSource `json:"log_source"`
Level database.LogLevel `json:"log_level"`
Output string `json:"output"`
}
// ListenProvisionerDaemon returns the gRPC service for a provisioner daemon implementation.
func (c *Client) ListenProvisionerDaemon(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) {
serverURL, err := c.URL.Parse("/api/v2/provisionerdaemons/me/listen")
@ -48,7 +81,7 @@ func (c *Client) ListenProvisionerDaemon(ctx context.Context) (proto.DRPCProvisi
// provisionerJobLogsBefore provides log output that occurred before a time.
// This is abstracted from a specific job type to provide consistency between
// APIs. Logs is the only shared route between jobs.
func (c *Client) provisionerJobLogsBefore(ctx context.Context, path string, before time.Time) ([]coderd.ProvisionerJobLog, error) {
func (c *Client) provisionerJobLogsBefore(ctx context.Context, path string, before time.Time) ([]ProvisionerJobLog, error) {
values := url.Values{}
if !before.IsZero() {
values["before"] = []string{strconv.FormatInt(before.UTC().UnixMilli(), 10)}
@ -62,12 +95,12 @@ func (c *Client) provisionerJobLogsBefore(ctx context.Context, path string, befo
return nil, readBodyAsError(res)
}
var logs []coderd.ProvisionerJobLog
var logs []ProvisionerJobLog
return logs, json.NewDecoder(res.Body).Decode(&logs)
}
// provisionerJobLogsAfter streams logs that occurred after a specific time.
func (c *Client) provisionerJobLogsAfter(ctx context.Context, path string, after time.Time) (<-chan coderd.ProvisionerJobLog, error) {
func (c *Client) provisionerJobLogsAfter(ctx context.Context, path string, after time.Time) (<-chan ProvisionerJobLog, error) {
afterQuery := ""
if !after.IsZero() {
afterQuery = fmt.Sprintf("&after=%d", after.UTC().UnixMilli())
@ -81,11 +114,11 @@ func (c *Client) provisionerJobLogsAfter(ctx context.Context, path string, after
return nil, readBodyAsError(res)
}
logs := make(chan coderd.ProvisionerJobLog)
logs := make(chan ProvisionerJobLog)
decoder := json.NewDecoder(res.Body)
go func() {
defer close(logs)
var log coderd.ProvisionerJobLog
var log ProvisionerJobLog
for {
err = decoder.Decode(&log)
if err != nil {

View File

@ -5,10 +5,68 @@ import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/coder/coder/coderd"
"github.com/google/uuid"
)
// User represents a user in Coder.
type User struct {
ID string `json:"id" validate:"required"`
Email string `json:"email" validate:"required"`
CreatedAt time.Time `json:"created_at" validate:"required"`
Username string `json:"username" validate:"required"`
}
type CreateFirstUserRequest struct {
Email string `json:"email" validate:"required,email"`
Username string `json:"username" validate:"required,username"`
Password string `json:"password" validate:"required"`
Organization string `json:"organization" validate:"required,username"`
}
// CreateFirstUserResponse contains IDs for newly created user info.
type CreateFirstUserResponse struct {
UserID string `json:"user_id"`
OrganizationID string `json:"organization_id"`
}
type CreateUserRequest struct {
Email string `json:"email" validate:"required,email"`
Username string `json:"username" validate:"required,username"`
Password string `json:"password" validate:"required"`
OrganizationID string `json:"organization_id" validate:"required"`
}
// LoginWithPasswordRequest enables callers to authenticate with email and password.
type LoginWithPasswordRequest struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required"`
}
// LoginWithPasswordResponse contains a session token for the newly authenticated user.
type LoginWithPasswordResponse struct {
SessionToken string `json:"session_token" validate:"required"`
}
// GenerateAPIKeyResponse contains an API key for a user.
type GenerateAPIKeyResponse struct {
Key string `json:"key"`
}
type CreateOrganizationRequest struct {
Name string `json:"name" validate:"required,username"`
}
// CreateWorkspaceRequest provides options for creating a new workspace.
type CreateWorkspaceRequest struct {
ProjectID uuid.UUID `json:"project_id" validate:"required"`
Name string `json:"name" validate:"username,required"`
// ParameterValues allows for additional parameters to be provided
// during the initial provision.
ParameterValues []CreateParameterRequest `json:"parameter_values"`
}
// HasFirstUser returns whether the first user has been created.
func (c *Client) HasFirstUser(ctx context.Context) (bool, error) {
res, err := c.request(ctx, http.MethodGet, "/api/v2/users/first", nil)
@ -27,35 +85,35 @@ func (c *Client) HasFirstUser(ctx context.Context) (bool, error) {
// CreateFirstUser attempts to create the first user on a Coder deployment.
// This initial user has superadmin privileges. If >0 users exist, this request will fail.
func (c *Client) CreateFirstUser(ctx context.Context, req coderd.CreateFirstUserRequest) (coderd.CreateFirstUserResponse, error) {
func (c *Client) CreateFirstUser(ctx context.Context, req CreateFirstUserRequest) (CreateFirstUserResponse, error) {
res, err := c.request(ctx, http.MethodPost, "/api/v2/users/first", req)
if err != nil {
return coderd.CreateFirstUserResponse{}, err
return CreateFirstUserResponse{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.CreateFirstUserResponse{}, readBodyAsError(res)
return CreateFirstUserResponse{}, readBodyAsError(res)
}
var resp coderd.CreateFirstUserResponse
var resp CreateFirstUserResponse
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
// CreateUser creates a new user.
func (c *Client) CreateUser(ctx context.Context, req coderd.CreateUserRequest) (coderd.User, error) {
func (c *Client) CreateUser(ctx context.Context, req CreateUserRequest) (User, error) {
res, err := c.request(ctx, http.MethodPost, "/api/v2/users", req)
if err != nil {
return coderd.User{}, err
return User{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.User{}, readBodyAsError(res)
return User{}, readBodyAsError(res)
}
var user coderd.User
var user User
return user, json.NewDecoder(res.Body).Decode(&user)
}
// CreateAPIKey generates an API key for the user ID provided.
func (c *Client) CreateAPIKey(ctx context.Context, id string) (*coderd.GenerateAPIKeyResponse, error) {
func (c *Client) CreateAPIKey(ctx context.Context, id string) (*GenerateAPIKeyResponse, error) {
if id == "" {
id = "me"
}
@ -67,25 +125,25 @@ func (c *Client) CreateAPIKey(ctx context.Context, id string) (*coderd.GenerateA
if res.StatusCode > http.StatusCreated {
return nil, readBodyAsError(res)
}
apiKey := &coderd.GenerateAPIKeyResponse{}
apiKey := &GenerateAPIKeyResponse{}
return apiKey, json.NewDecoder(res.Body).Decode(apiKey)
}
// LoginWithPassword creates a session token authenticating with an email and password.
// Call `SetSessionToken()` to apply the newly acquired token to the client.
func (c *Client) LoginWithPassword(ctx context.Context, req coderd.LoginWithPasswordRequest) (coderd.LoginWithPasswordResponse, error) {
func (c *Client) LoginWithPassword(ctx context.Context, req LoginWithPasswordRequest) (LoginWithPasswordResponse, error) {
res, err := c.request(ctx, http.MethodPost, "/api/v2/users/login", req)
if err != nil {
return coderd.LoginWithPasswordResponse{}, err
return LoginWithPasswordResponse{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.LoginWithPasswordResponse{}, readBodyAsError(res)
return LoginWithPasswordResponse{}, readBodyAsError(res)
}
var resp coderd.LoginWithPasswordResponse
var resp LoginWithPasswordResponse
err = json.NewDecoder(res.Body).Decode(&resp)
if err != nil {
return coderd.LoginWithPasswordResponse{}, err
return LoginWithPasswordResponse{}, err
}
return resp, nil
}
@ -105,24 +163,24 @@ func (c *Client) Logout(ctx context.Context) error {
// User returns a user for the ID provided.
// If the ID string is empty, the current user will be returned.
func (c *Client) User(ctx context.Context, id string) (coderd.User, error) {
func (c *Client) User(ctx context.Context, id string) (User, error) {
if id == "" {
id = "me"
}
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s", id), nil)
if err != nil {
return coderd.User{}, err
return User{}, err
}
defer res.Body.Close()
if res.StatusCode > http.StatusOK {
return coderd.User{}, readBodyAsError(res)
return User{}, readBodyAsError(res)
}
var user coderd.User
var user User
return user, json.NewDecoder(res.Body).Decode(&user)
}
// OrganizationsByUser returns all organizations the user is a member of.
func (c *Client) OrganizationsByUser(ctx context.Context, id string) ([]coderd.Organization, error) {
func (c *Client) OrganizationsByUser(ctx context.Context, id string) ([]Organization, error) {
if id == "" {
id = "me"
}
@ -134,62 +192,62 @@ func (c *Client) OrganizationsByUser(ctx context.Context, id string) ([]coderd.O
if res.StatusCode > http.StatusOK {
return nil, readBodyAsError(res)
}
var orgs []coderd.Organization
var orgs []Organization
return orgs, json.NewDecoder(res.Body).Decode(&orgs)
}
func (c *Client) OrganizationByName(ctx context.Context, user, name string) (coderd.Organization, error) {
func (c *Client) OrganizationByName(ctx context.Context, user, name string) (Organization, error) {
if user == "" {
user = "me"
}
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/organizations/%s", user, name), nil)
if err != nil {
return coderd.Organization{}, err
return Organization{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.Organization{}, readBodyAsError(res)
return Organization{}, readBodyAsError(res)
}
var org coderd.Organization
var org Organization
return org, json.NewDecoder(res.Body).Decode(&org)
}
// CreateOrganization creates an organization and adds the provided user as an admin.
func (c *Client) CreateOrganization(ctx context.Context, user string, req coderd.CreateOrganizationRequest) (coderd.Organization, error) {
func (c *Client) CreateOrganization(ctx context.Context, user string, req CreateOrganizationRequest) (Organization, error) {
if user == "" {
user = "me"
}
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/organizations", user), req)
if err != nil {
return coderd.Organization{}, err
return Organization{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.Organization{}, readBodyAsError(res)
return Organization{}, readBodyAsError(res)
}
var org coderd.Organization
var org Organization
return org, json.NewDecoder(res.Body).Decode(&org)
}
// CreateWorkspace creates a new workspace for the project specified.
func (c *Client) CreateWorkspace(ctx context.Context, user string, request coderd.CreateWorkspaceRequest) (coderd.Workspace, error) {
func (c *Client) CreateWorkspace(ctx context.Context, user string, request CreateWorkspaceRequest) (Workspace, error) {
if user == "" {
user = "me"
}
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/workspaces", user), request)
if err != nil {
return coderd.Workspace{}, err
return Workspace{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.Workspace{}, readBodyAsError(res)
return Workspace{}, readBodyAsError(res)
}
var workspace coderd.Workspace
var workspace Workspace
return workspace, json.NewDecoder(res.Body).Decode(&workspace)
}
// WorkspacesByUser returns all workspaces the specified user has access to.
func (c *Client) WorkspacesByUser(ctx context.Context, user string) ([]coderd.Workspace, error) {
func (c *Client) WorkspacesByUser(ctx context.Context, user string) ([]Workspace, error) {
if user == "" {
user = "me"
}
@ -201,22 +259,22 @@ func (c *Client) WorkspacesByUser(ctx context.Context, user string) ([]coderd.Wo
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var workspaces []coderd.Workspace
var workspaces []Workspace
return workspaces, json.NewDecoder(res.Body).Decode(&workspaces)
}
func (c *Client) WorkspaceByName(ctx context.Context, user, name string) (coderd.Workspace, error) {
func (c *Client) WorkspaceByName(ctx context.Context, user, name string) (Workspace, error) {
if user == "" {
user = "me"
}
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/workspaces/%s", user, name), nil)
if err != nil {
return coderd.Workspace{}, err
return Workspace{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.Workspace{}, readBodyAsError(res)
return Workspace{}, readBodyAsError(res)
}
var workspace coderd.Workspace
var workspace Workspace
return workspace, json.NewDecoder(res.Body).Decode(&workspace)
}

View File

@ -9,26 +9,55 @@ import (
"github.com/google/uuid"
"github.com/coder/coder/coderd"
"github.com/coder/coder/database"
)
// WorkspaceBuild is an at-point representation of a workspace state.
// Iterate on before/after to determine a chronological history.
type WorkspaceBuild struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
WorkspaceID uuid.UUID `json:"workspace_id"`
ProjectVersionID uuid.UUID `json:"project_version_id"`
BeforeID uuid.UUID `json:"before_id"`
AfterID uuid.UUID `json:"after_id"`
Name string `json:"name"`
Transition database.WorkspaceTransition `json:"transition"`
Initiator string `json:"initiator"`
Job ProvisionerJob `json:"job"`
}
// WorkspaceBuild returns a single workspace build for a workspace.
// If history is "", the latest version is returned.
func (c *Client) WorkspaceBuild(ctx context.Context, id uuid.UUID) (coderd.WorkspaceBuild, error) {
func (c *Client) WorkspaceBuild(ctx context.Context, id uuid.UUID) (WorkspaceBuild, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspacebuilds/%s", id), nil)
if err != nil {
return coderd.WorkspaceBuild{}, err
return WorkspaceBuild{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.WorkspaceBuild{}, readBodyAsError(res)
return WorkspaceBuild{}, readBodyAsError(res)
}
var workspaceBuild coderd.WorkspaceBuild
var workspaceBuild WorkspaceBuild
return workspaceBuild, json.NewDecoder(res.Body).Decode(&workspaceBuild)
}
// CancelWorkspaceBuild marks a workspace build job as canceled.
func (c *Client) CancelWorkspaceBuild(ctx context.Context, id uuid.UUID) error {
res, err := c.request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/workspacebuilds/%s/cancel", id), nil)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return readBodyAsError(res)
}
return nil
}
// WorkspaceResourcesByBuild returns resources for a workspace build.
func (c *Client) WorkspaceResourcesByBuild(ctx context.Context, build uuid.UUID) ([]coderd.WorkspaceResource, error) {
func (c *Client) WorkspaceResourcesByBuild(ctx context.Context, build uuid.UUID) ([]WorkspaceResource, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspacebuilds/%s/resources", build), nil)
if err != nil {
return nil, err
@ -37,16 +66,16 @@ func (c *Client) WorkspaceResourcesByBuild(ctx context.Context, build uuid.UUID)
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var resources []coderd.WorkspaceResource
var resources []WorkspaceResource
return resources, json.NewDecoder(res.Body).Decode(&resources)
}
// WorkspaceBuildLogsBefore returns logs that occurred before a specific time.
func (c *Client) WorkspaceBuildLogsBefore(ctx context.Context, version uuid.UUID, before time.Time) ([]coderd.ProvisionerJobLog, error) {
return c.provisionerJobLogsBefore(ctx, fmt.Sprintf("/api/v2/workspacebuilds/%s/logs", version), before)
func (c *Client) WorkspaceBuildLogsBefore(ctx context.Context, build uuid.UUID, before time.Time) ([]ProvisionerJobLog, error) {
return c.provisionerJobLogsBefore(ctx, fmt.Sprintf("/api/v2/workspacebuilds/%s/logs", build), before)
}
// WorkspaceBuildLogsAfter streams logs for a workspace build that occurred after a specific time.
func (c *Client) WorkspaceBuildLogsAfter(ctx context.Context, version uuid.UUID, after time.Time) (<-chan coderd.ProvisionerJobLog, error) {
return c.provisionerJobLogsAfter(ctx, fmt.Sprintf("/api/v2/workspacebuilds/%s/logs", version), after)
func (c *Client) WorkspaceBuildLogsAfter(ctx context.Context, build uuid.UUID, after time.Time) (<-chan ProvisionerJobLog, error) {
return c.provisionerJobLogsAfter(ctx, fmt.Sprintf("/api/v2/workspacebuilds/%s/logs", build), after)
}

View File

@ -8,15 +8,23 @@ import (
"cloud.google.com/go/compute/metadata"
"golang.org/x/xerrors"
"github.com/coder/coder/coderd"
)
type GoogleInstanceIdentityToken struct {
JSONWebToken string `json:"json_web_token" validate:"required"`
}
// WorkspaceAgentAuthenticateResponse is returned when an instance ID
// has been exchanged for a session token.
type WorkspaceAgentAuthenticateResponse struct {
SessionToken string `json:"session_token"`
}
// AuthWorkspaceGoogleInstanceIdentity uses the Google Compute Engine Metadata API to
// fetch a signed JWT, and exchange it for a session token for a workspace agent.
//
// The requesting instance must be registered as a resource in the latest history for a workspace.
func (c *Client) AuthWorkspaceGoogleInstanceIdentity(ctx context.Context, serviceAccount string, gcpClient *metadata.Client) (coderd.WorkspaceAgentAuthenticateResponse, error) {
func (c *Client) AuthWorkspaceGoogleInstanceIdentity(ctx context.Context, serviceAccount string, gcpClient *metadata.Client) (WorkspaceAgentAuthenticateResponse, error) {
if serviceAccount == "" {
// This is the default name specified by Google.
serviceAccount = "default"
@ -27,18 +35,18 @@ func (c *Client) AuthWorkspaceGoogleInstanceIdentity(ctx context.Context, servic
// "format=full" is required, otherwise the responding payload will be missing "instance_id".
jwt, err := gcpClient.Get(fmt.Sprintf("instance/service-accounts/%s/identity?audience=coder&format=full", serviceAccount))
if err != nil {
return coderd.WorkspaceAgentAuthenticateResponse{}, xerrors.Errorf("get metadata identity: %w", err)
return WorkspaceAgentAuthenticateResponse{}, xerrors.Errorf("get metadata identity: %w", err)
}
res, err := c.request(ctx, http.MethodPost, "/api/v2/workspaceresources/auth/google-instance-identity", coderd.GoogleInstanceIdentityToken{
res, err := c.request(ctx, http.MethodPost, "/api/v2/workspaceresources/auth/google-instance-identity", GoogleInstanceIdentityToken{
JSONWebToken: jwt,
})
if err != nil {
return coderd.WorkspaceAgentAuthenticateResponse{}, err
return WorkspaceAgentAuthenticateResponse{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.WorkspaceAgentAuthenticateResponse{}, readBodyAsError(res)
return WorkspaceAgentAuthenticateResponse{}, readBodyAsError(res)
}
var resp coderd.WorkspaceAgentAuthenticateResponse
var resp WorkspaceAgentAuthenticateResponse
return resp, json.NewDecoder(res.Body).Decode(&resp)
}

View File

@ -7,13 +7,15 @@ import (
"io"
"net/http"
"net/http/cookiejar"
"time"
"github.com/google/uuid"
"github.com/hashicorp/yamux"
"github.com/pion/webrtc/v3"
"golang.org/x/xerrors"
"nhooyr.io/websocket"
"github.com/coder/coder/coderd"
"github.com/coder/coder/database"
"github.com/coder/coder/httpmw"
"github.com/coder/coder/peer"
"github.com/coder/coder/peerbroker"
@ -21,16 +23,69 @@ import (
"github.com/coder/coder/provisionersdk"
)
func (c *Client) WorkspaceResource(ctx context.Context, id uuid.UUID) (coderd.WorkspaceResource, error) {
type WorkspaceAgentStatus string
const (
WorkspaceAgentWaiting WorkspaceAgentStatus = "waiting"
WorkspaceAgentConnected WorkspaceAgentStatus = "connected"
WorkspaceAgentDisconnected WorkspaceAgentStatus = "disconnected"
)
type WorkspaceResource struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
JobID uuid.UUID `json:"job_id"`
Transition database.WorkspaceTransition `json:"workspace_transition"`
Address string `json:"address"`
Type string `json:"type"`
Name string `json:"name"`
Agent *WorkspaceAgent `json:"agent,omitempty"`
}
type WorkspaceAgent struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
FirstConnectedAt *time.Time `json:"first_connected_at,omitempty"`
LastConnectedAt *time.Time `json:"last_connected_at,omitempty"`
DisconnectedAt *time.Time `json:"disconnected_at,omitempty"`
Status WorkspaceAgentStatus `json:"status"`
ResourceID uuid.UUID `json:"resource_id"`
InstanceID string `json:"instance_id,omitempty"`
EnvironmentVariables map[string]string `json:"environment_variables"`
StartupScript string `json:"startup_script,omitempty"`
}
type WorkspaceAgentResourceMetadata struct {
MemoryTotal uint64 `json:"memory_total"`
DiskTotal uint64 `json:"disk_total"`
CPUCores uint64 `json:"cpu_cores"`
CPUModel string `json:"cpu_model"`
CPUMhz float64 `json:"cpu_mhz"`
}
type WorkspaceAgentInstanceMetadata struct {
JailOrchestrator string `json:"jail_orchestrator"`
OperatingSystem string `json:"operating_system"`
Platform string `json:"platform"`
PlatformFamily string `json:"platform_family"`
KernelVersion string `json:"kernel_version"`
KernelArchitecture string `json:"kernel_architecture"`
Cloud string `json:"cloud"`
Jail string `json:"jail"`
VNC bool `json:"vnc"`
}
func (c *Client) WorkspaceResource(ctx context.Context, id uuid.UUID) (WorkspaceResource, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceresources/%s", id), nil)
if err != nil {
return coderd.WorkspaceResource{}, err
return WorkspaceResource{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.WorkspaceResource{}, readBodyAsError(res)
return WorkspaceResource{}, readBodyAsError(res)
}
var resource coderd.WorkspaceResource
var resource WorkspaceResource
return resource, json.NewDecoder(res.Body).Decode(&resource)
}
@ -106,5 +161,9 @@ func (c *Client) ListenWorkspaceAgent(ctx context.Context, opts *peer.ConnOption
if err != nil {
return nil, xerrors.Errorf("multiplex client: %w", err)
}
return peerbroker.Listen(session, nil, opts)
return peerbroker.Listen(session, func(ctx context.Context) ([]webrtc.ICEServer, error) {
return []webrtc.ICEServer{{
URLs: []string{"stun:stun.l.google.com:19302"},
}}, nil
}, opts)
}

View File

@ -5,27 +5,49 @@ import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/google/uuid"
"github.com/coder/coder/coderd"
"github.com/coder/coder/database"
)
// Workspace is a per-user deployment of a project. It tracks
// project versions, and can be updated.
type Workspace struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
OwnerID string `json:"owner_id"`
ProjectID uuid.UUID `json:"project_id"`
ProjectName string `json:"project_name"`
LatestBuild WorkspaceBuild `json:"latest_build"`
Outdated bool `json:"outdated"`
Name string `json:"name"`
}
// CreateWorkspaceBuildRequest provides options to update the latest workspace build.
type CreateWorkspaceBuildRequest struct {
ProjectVersionID uuid.UUID `json:"project_version_id"`
Transition database.WorkspaceTransition `json:"transition" validate:"oneof=create start stop delete,required"`
DryRun bool `json:"dry_run"`
}
// Workspace returns a single workspace.
func (c *Client) Workspace(ctx context.Context, id uuid.UUID) (coderd.Workspace, error) {
func (c *Client) Workspace(ctx context.Context, id uuid.UUID) (Workspace, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s", id), nil)
if err != nil {
return coderd.Workspace{}, err
return Workspace{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.Workspace{}, readBodyAsError(res)
return Workspace{}, readBodyAsError(res)
}
var workspace coderd.Workspace
var workspace Workspace
return workspace, json.NewDecoder(res.Body).Decode(&workspace)
}
func (c *Client) WorkspaceBuilds(ctx context.Context, workspace uuid.UUID) ([]coderd.WorkspaceBuild, error) {
func (c *Client) WorkspaceBuilds(ctx context.Context, workspace uuid.UUID) ([]WorkspaceBuild, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s/builds", workspace), nil)
if err != nil {
return nil, err
@ -34,46 +56,33 @@ func (c *Client) WorkspaceBuilds(ctx context.Context, workspace uuid.UUID) ([]co
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var workspaceBuild []coderd.WorkspaceBuild
var workspaceBuild []WorkspaceBuild
return workspaceBuild, json.NewDecoder(res.Body).Decode(&workspaceBuild)
}
// CreateWorkspaceBuild queues a new build to occur for a workspace.
func (c *Client) CreateWorkspaceBuild(ctx context.Context, workspace uuid.UUID, request coderd.CreateWorkspaceBuildRequest) (coderd.WorkspaceBuild, error) {
func (c *Client) CreateWorkspaceBuild(ctx context.Context, workspace uuid.UUID, request CreateWorkspaceBuildRequest) (WorkspaceBuild, error) {
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/workspaces/%s/builds", workspace), request)
if err != nil {
return coderd.WorkspaceBuild{}, err
return WorkspaceBuild{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.WorkspaceBuild{}, readBodyAsError(res)
return WorkspaceBuild{}, readBodyAsError(res)
}
var workspaceBuild coderd.WorkspaceBuild
var workspaceBuild WorkspaceBuild
return workspaceBuild, json.NewDecoder(res.Body).Decode(&workspaceBuild)
}
func (c *Client) WorkspaceBuildByName(ctx context.Context, workspace uuid.UUID, name string) (coderd.WorkspaceBuild, error) {
func (c *Client) WorkspaceBuildByName(ctx context.Context, workspace uuid.UUID, name string) (WorkspaceBuild, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s/builds/%s", workspace, name), nil)
if err != nil {
return coderd.WorkspaceBuild{}, err
return WorkspaceBuild{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.WorkspaceBuild{}, readBodyAsError(res)
return WorkspaceBuild{}, readBodyAsError(res)
}
var workspaceBuild coderd.WorkspaceBuild
return workspaceBuild, json.NewDecoder(res.Body).Decode(&workspaceBuild)
}
func (c *Client) WorkspaceBuildLatest(ctx context.Context, workspace uuid.UUID) (coderd.WorkspaceBuild, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s/builds/latest", workspace), nil)
if err != nil {
return coderd.WorkspaceBuild{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.WorkspaceBuild{}, readBodyAsError(res)
}
var workspaceBuild coderd.WorkspaceBuild
var workspaceBuild WorkspaceBuild
return workspaceBuild, json.NewDecoder(res.Body).Decode(&workspaceBuild)
}