From 84a4c7158b4388fab4b321080193334b8e6528d9 Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Mon, 2 Nov 2020 18:05:40 -0600 Subject: [PATCH] Add more coder-sdk update, create, and delete operations --- coder-sdk/env.go | 9 +++-- coder-sdk/error.go | 9 +++-- coder-sdk/image.go | 6 ++-- coder-sdk/org.go | 59 +++++++++++++++++++++++++------ coder-sdk/users.go | 67 ++++++++++++++++++++++++++++++++---- coder-sdk/util.go | 35 +++++++++++++++++++ internal/x/xjson/duration.go | 31 ----------------- 7 files changed, 157 insertions(+), 59 deletions(-) create mode 100644 coder-sdk/util.go delete mode 100644 internal/x/xjson/duration.go diff --git a/coder-sdk/env.go b/coder-sdk/env.go index a15f1caa..42bb6467 100644 --- a/coder-sdk/env.go +++ b/coder-sdk/env.go @@ -5,7 +5,6 @@ import ( "net/http" "time" - "cdr.dev/coder-cli/internal/x/xjson" "golang.org/x/xerrors" "nhooyr.io/websocket" "nhooyr.io/websocket/wsjson" @@ -31,14 +30,14 @@ type Environment struct { UpdatedAt time.Time `json:"updated_at" table:"-"` LastOpenedAt time.Time `json:"last_opened_at" table:"-"` LastConnectionAt time.Time `json:"last_connection_at" table:"-"` - AutoOffThreshold xjson.MSDuration `json:"auto_off_threshold" table:"-"` + AutoOffThreshold Duration `json:"auto_off_threshold" table:"-"` } // RebuildMessage defines the message shown when an Environment requires a rebuild for it can be accessed. type RebuildMessage struct { - Text string `json:"text"` - Required bool `json:"required"` - AutoOffThreshold xjson.MSDuration `json:"auto_off_threshold" table:"-"` + Text string `json:"text"` + Required bool `json:"required"` + AutoOffThreshold Duration `json:"auto_off_threshold"` } // EnvironmentStat represents the state of an environment diff --git a/coder-sdk/error.go b/coder-sdk/error.go index 91ebfa30..2b312378 100644 --- a/coder-sdk/error.go +++ b/coder-sdk/error.go @@ -14,9 +14,12 @@ var ErrNotFound = xerrors.Errorf("resource not found") // APIError is the expected payload format for our errors. type APIError struct { - Err struct { - Msg string `json:"msg"` - } `json:"error"` + Err APIErrorMsg `json:"error"` +} + +// APIErrorMsg contains the rich error information returned by API errors. +type APIErrorMsg struct { + Msg string `json:"msg"` } // HTTPError represents an error from the Coder API. diff --git a/coder-sdk/image.go b/coder-sdk/image.go index c9122cc9..edeecabf 100644 --- a/coder-sdk/image.go +++ b/coder-sdk/image.go @@ -26,8 +26,8 @@ type NewRegistryRequest struct { Password string `json:"password"` } -// ImportImageRequest is used to import new images and registries into Coder -type ImportImageRequest struct { +// ImportImageReq is used to import new images and registries into Coder +type ImportImageReq struct { RegistryID *string `json:"registry_id"` // Used to import images to existing registries. NewRegistry *NewRegistryRequest `json:"new_registry"` // Used when adding a new registry. Repository string `json:"repository"` // Refers to the image. Ex: "codercom/ubuntu". @@ -40,7 +40,7 @@ type ImportImageRequest struct { } // ImportImage creates a new image and optionally a new registry -func (c Client) ImportImage(ctx context.Context, orgID string, req ImportImageRequest) (*Image, error) { +func (c Client) ImportImage(ctx context.Context, orgID string, req ImportImageReq) (*Image, error) { var img Image if err := c.requestBody(ctx, http.MethodPost, "/api/orgs/"+orgID+"/images", req, &img); err != nil { return nil, err diff --git a/coder-sdk/org.go b/coder-sdk/org.go index e3fe4583..0caa9d18 100644 --- a/coder-sdk/org.go +++ b/coder-sdk/org.go @@ -16,18 +16,15 @@ type Organization struct { // OrganizationUser user wraps the basic User type and adds data specific to the user's membership of an organization type OrganizationUser struct { User - OrganizationRoles []OrganizationRole `json:"organization_roles"` - RolesUpdatedAt time.Time `json:"roles_updated_at"` + OrganizationRoles []Role `json:"organization_roles"` + RolesUpdatedAt time.Time `json:"roles_updated_at"` } -// OrganizationRole defines an organization OrganizationRole -type OrganizationRole string - -// The OrganizationRole enum values +// Organization Roles const ( - RoleOrgMember OrganizationRole = "organization-member" - RoleOrgAdmin OrganizationRole = "organization-admin" - RoleOrgManager OrganizationRole = "organization-manager" + RoleOrgMember Role = "organization-member" + RoleOrgAdmin Role = "organization-admin" + RoleOrgManager Role = "organization-manager" ) // Organizations gets all Organizations @@ -39,11 +36,51 @@ func (c Client) Organizations(ctx context.Context) ([]Organization, error) { return orgs, nil } -// OrgMembers get all members of the given organization -func (c Client) OrgMembers(ctx context.Context, orgID string) ([]OrganizationUser, error) { +func (c Client) OrganizationByID(ctx context.Context, orgID string) (*Organization, error) { + var org Organization + err := c.requestBody(ctx, http.MethodGet, "/api/orgs/"+orgID, nil, &org) + if err != nil { + return nil, err + } + return &org, nil +} + +// OrganizationMembers get all members of the given organization +func (c Client) OrganizationMembers(ctx context.Context, orgID string) ([]OrganizationUser, error) { var members []OrganizationUser if err := c.requestBody(ctx, http.MethodGet, "/api/orgs/"+orgID+"/members", nil, &members); err != nil { return nil, err } return members, nil } + +type UpdateOrganizationReq struct { + Name *string `json:"name"` + Description *string `json:"description"` + Default *bool `json:"default"` + AutoOffThreshold *Duration `json:"auto_off_threshold"` + CPUProvisioningRate *float32 `json:"cpu_provisioning_rate"` + MemoryProvisioningRate *float32 `json:"memory_provisioning_rate"` +} + +func (c Client) UpdateOrganization(ctx context.Context, orgID string, req UpdateOrganizationReq) error { + return c.requestBody(ctx, http.MethodPatch, "/api/orgs/"+orgID, req, nil) +} + +type CreateOrganizationReq struct { + Name string `json:"name"` + Description string `json:"description"` + Default bool `json:"default"` + ResourceNamespace string `json:"resource_namespace"` + AutoOffThreshold Duration `json:"auto_off_threshold"` + CPUProvisioningRate float32 `json:"cpu_provisioning_rate"` + MemoryProvisioningRate float32 `json:"memory_provisioning_rate"` +} + +func (c Client) CreateOrganization(ctx context.Context, req CreateOrganizationReq) error { + return c.requestBody(ctx, http.MethodPost, "/api/orgs", req, nil) +} + +func (c Client) DeleteOrganization(ctx context.Context, orgID string) error { + return c.requestBody(ctx, http.MethodDelete, "/api/orgs/"+orgID, nil, nil) +} diff --git a/coder-sdk/users.go b/coder-sdk/users.go index e988a697..6117bace 100644 --- a/coder-sdk/users.go +++ b/coder-sdk/users.go @@ -8,14 +8,38 @@ import ( // User describes a Coder user account. type User struct { - ID string `json:"id" table:"-"` - Email string `json:"email" table:"Email"` - Username string `json:"username" table:"Username"` - Name string `json:"name" table:"Name"` - CreatedAt time.Time `json:"created_at" table:"CreatedAt"` - UpdatedAt time.Time `json:"updated_at" table:"-"` + ID string `json:"id" table:"-"` + Email string `json:"email" table:"Email"` + Username string `json:"username" table:"Username"` + Name string `json:"name" table:"Name"` + Roles []Role `json:"roles" table:"-"` + TemporaryPassword bool `json:"temporary_password" table:"-"` + LoginType string `json:"login_type" table:"-"` + KeyRegeneratedAt time.Time `json:"key_regenerated_at" table:"-"` + CreatedAt time.Time `json:"created_at" table:"CreatedAt"` + UpdatedAt time.Time `json:"updated_at" table:"-"` } +type Role string + +type Roles []Role + +// Site Roles +const ( + SiteAdmin Role = "site-admin" + SiteAuditor Role = "site-auditor" + SiteManager Role = "site-manager" + SiteMember Role = "site-member" +) + +type LoginType string + +const ( + LoginTypeBuiltIn LoginType = "built-in" + LoginTypeSAML LoginType = "saml" + LoginTypeOIDC LoginType = "oidc" +) + // Me gets the details of the authenticated user. func (c Client) Me(ctx context.Context) (*User, error) { return c.UserByID(ctx, Me) @@ -70,3 +94,34 @@ func (c Client) UserByEmail(ctx context.Context, email string) (*User, error) { } return nil, ErrNotFound } + +// UpdateUserReq defines a modification to the user, updating the +// value of all non-nil values. +type UpdateUserReq struct { + // TODO(@cmoog) add update password option + Revoked *bool `json:"revoked,omitempty"` + Roles *Roles `json:"roles,omitempty"` + LoginType *LoginType `json:"login_type,omitempty"` + Name *string `json:"name,omitempty"` + Username *string `json:"username,omitempty"` + Email *string `json:"email,omitempty"` + DotfilesGitURL *string `json:"dotfiles_git_uri,omitempty"` +} + +func (c Client) UpdateUser(ctx context.Context, userID string, req UpdateUserReq) error { + return c.requestBody(ctx, http.MethodPatch, "/api/users/"+userID, req, nil) +} + +type CreateUserReq struct { + Name string `json:"name"` + Username string `json:"username"` + Email string `json:"email"` + Password string `json:"password"` + TemporaryPassword bool `json:"temporary_password"` + LoginType LoginType `json:"login_type"` + OrganizationsIDs []string `json:"organizations"` +} + +func (c Client) CreateUser(ctx context.Context, req CreateUserReq) error { + return c.requestBody(ctx, http.MethodPost, "/api/users", req, nil) +} diff --git a/coder-sdk/util.go b/coder-sdk/util.go new file mode 100644 index 00000000..882b3ced --- /dev/null +++ b/coder-sdk/util.go @@ -0,0 +1,35 @@ +package coder + +import ( + "encoding/json" + "strconv" + "time" +) + +func String(s string) *string { + return &s +} + +// Duration is a time.Duration wrapper that marshals to millisecond precision. +// While it looses precision, most javascript applications expect durations to be in milliseconds. +type Duration time.Duration + +// MarshalJSON marshals the duration to millisecond precision. +func (d Duration) MarshalJSON() ([]byte, error) { + du := time.Duration(d) + return json.Marshal(du.Milliseconds()) +} + +// UnmarshalJSON unmarshals a millisecond-precision integer to +// a time.Duration. +func (d *Duration) UnmarshalJSON(b []byte) error { + i, err := strconv.ParseInt(string(b), 10, 64) + if err != nil { + return err + } + + *d = Duration(time.Duration(i) * time.Millisecond) + return nil +} + +func (d Duration) String() string { return time.Duration(d).String() } diff --git a/internal/x/xjson/duration.go b/internal/x/xjson/duration.go deleted file mode 100644 index 04ec1f17..00000000 --- a/internal/x/xjson/duration.go +++ /dev/null @@ -1,31 +0,0 @@ -package xjson - -import ( - "encoding/json" - "strconv" - "time" -) - -// MSDuration is a time.MSDuration that marshals to millisecond precision. -// While is looses precision, most javascript applications expect durations to be in milliseconds. -type MSDuration time.Duration - -// MarshalJSON marshals the duration to millisecond precision. -func (d MSDuration) MarshalJSON() ([]byte, error) { - du := time.Duration(d) - return json.Marshal(du.Milliseconds()) -} - -// UnmarshalJSON unmarshals a millisecond-precision integer to -// a time.Duration. -func (d *MSDuration) UnmarshalJSON(b []byte) error { - i, err := strconv.ParseInt(string(b), 10, 64) - if err != nil { - return err - } - - *d = MSDuration(time.Duration(i) * time.Millisecond) - return nil -} - -func (d MSDuration) String() string { return time.Duration(d).String() }