From fb4c1d7cd10d75447e2162c5886e70abd49a3160 Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Tue, 3 Nov 2020 18:50:52 -0600 Subject: [PATCH 1/6] Enable more linting rules --- .golangci.yml | 27 +++++++++++++++++++++++++++ coder-sdk/error.go | 6 +++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index daa86752..f97bc7e5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -17,6 +17,7 @@ linters-settings: - (cdr.dev/coder-cli/pkg/clog).Causef linters: disable-all: true + exclude-use-default: false enable: - megacheck - govet @@ -44,3 +45,29 @@ linters: - rowserrcheck - scopelint - goprintffuncname + - gofmt + - godot + - ineffassign + - gocritic + +issues: + exclude-use-default: false + exclude: + # errcheck: Almost all programs ignore errors on these functions and in most cases it's ok + - Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked + # golint: False positive when tests are defined in package 'test' + - func name will be used as test\.Test.* by other packages, and that stutters; consider calling this + # govet: Common false positives + - (possible misuse of unsafe.Pointer|should have signature) + # staticcheck: Developers tend to write in C-style with an explicit 'break' in a 'switch', so it's ok to ignore + - ineffective break statement. Did you mean to break out of the outer loop + # gosec: Too many false-positives on 'unsafe' usage + - Use of unsafe calls should be audited + # gosec: Too many false-positives for parametrized shell calls + - Subprocess launch(ed with variable|ing should be audited) + # gosec: Duplicated errcheck checks + - G104 + # gosec: Too many issues in popular repos + - (Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less) + # gosec: False positive is triggered by 'src, err := ioutil.ReadFile(filename)' + - Potential file inclusion via variable \ No newline at end of file diff --git a/coder-sdk/error.go b/coder-sdk/error.go index 2b312378..75667ef1 100644 --- a/coder-sdk/error.go +++ b/coder-sdk/error.go @@ -10,7 +10,11 @@ import ( ) // ErrNotFound describes an error case in which the requested resource could not be found -var ErrNotFound = xerrors.Errorf("resource not found") +var ErrNotFound = xerrors.New("resource not found") + +var ErrPermissions = xerrors.New("insufficient permissions") + +var ErrAuthentication = xerrors.New("invalid authentication") // APIError is the expected payload format for our errors. type APIError struct { From febead108ac2871004fc60996532400e4b9987ef Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Tue, 3 Nov 2020 20:04:45 -0600 Subject: [PATCH 2/6] fixup! Enable more linting rules --- .golangci.yml | 1 - coder-sdk/client.go | 4 ++-- coder-sdk/config.go | 18 ++++++++++++++ coder-sdk/env.go | 14 +++++------ coder-sdk/error.go | 4 +++- coder-sdk/image.go | 8 +++---- coder-sdk/org.go | 16 +++++++++---- coder-sdk/secrets.go | 10 ++++---- coder-sdk/tokens.go | 7 ++++++ coder-sdk/users.go | 13 ++++++---- coder-sdk/util.go | 1 + pkg/tcli/tcli.go | 56 +++++++++++++++++++++++--------------------- 12 files changed, 96 insertions(+), 56 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index f97bc7e5..bfe48ebf 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,7 +7,6 @@ linters-settings: min-complexity: 46 nestif: min-complexity: 10 - golint: govet: settings: printf: diff --git a/coder-sdk/client.go b/coder-sdk/client.go index 959c7611..12a7c476 100644 --- a/coder-sdk/client.go +++ b/coder-sdk/client.go @@ -6,10 +6,10 @@ import ( "net/url" ) -// Me is the route param to access resources of the authenticated user +// Me is the route param to access resources of the authenticated user. const Me = "me" -// Client wraps the Coder HTTP API +// Client wraps the Coder HTTP API. type Client struct { BaseURL *url.URL Token string diff --git a/coder-sdk/config.go b/coder-sdk/config.go index e4a741ad..7912e78a 100644 --- a/coder-sdk/config.go +++ b/coder-sdk/config.go @@ -5,6 +5,7 @@ import ( "net/http" ) +// AuthProviderType is an enum of each valid auth provider. type AuthProviderType string // AuthProviderType enum. @@ -14,18 +15,21 @@ const ( AuthProviderOIDC AuthProviderType = "oidc" ) +// ConfigAuth describes the authentication configuration for a Coder Enterprise deployment. type ConfigAuth struct { ProviderType *AuthProviderType `json:"provider_type"` OIDC *ConfigOIDC `json:"oidc"` SAML *ConfigSAML `json:"saml"` } +// ConfigOIDC describes the OIDC configuration for single-signon support in Coder Enterprise. type ConfigOIDC struct { ClientID *string `json:"client_id"` ClientSecret *string `json:"client_secret"` Issuer *string `json:"issuer"` } +// ConfigSAML describes the SAML configuration values. type ConfigSAML struct { IdentityProviderMetadataURL *string `json:"idp_metadata_url"` SignatureAlgorithm *string `json:"signature_algorithm"` @@ -34,28 +38,33 @@ type ConfigSAML struct { PublicKeyCertificate *string `json:"public_key_certificate"` } +// ConfigOAuthBitbucketServer describes the Bitbucket integration configuration for a Coder Enterprise deployment. type ConfigOAuthBitbucketServer struct { BaseURL string `json:"base_url" diff:"oauth.bitbucket_server.base_url"` } +// ConfigOAuthGitHub describes the Github integration configuration for a Coder Enterprise deployment. type ConfigOAuthGitHub struct { BaseURL string `json:"base_url"` ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` } +// ConfigOAuthGitLab describes the GitLab integration configuration for a Coder Enterprise deployment. type ConfigOAuthGitLab struct { BaseURL string `json:"base_url"` ClientID string `json:"client_id" ` ClientSecret string `json:"client_secret"` } +// ConfigOAuth describes the aggregate git integration configuration for a Coder Enterprise deployment. type ConfigOAuth struct { BitbucketServer ConfigOAuthBitbucketServer `json:"bitbucket_server"` GitHub ConfigOAuthGitHub `json:"github"` GitLab ConfigOAuthGitLab `json:"gitlab"` } +// SiteConfigAuth fetches the sitewide authentication configuration. func (c Client) SiteConfigAuth(ctx context.Context) (*ConfigAuth, error) { var conf ConfigAuth if err := c.requestBody(ctx, http.MethodGet, "/api/auth/config", nil, &conf); err != nil { @@ -64,10 +73,12 @@ func (c Client) SiteConfigAuth(ctx context.Context) (*ConfigAuth, error) { return &conf, nil } +// PutSiteConfigAuth sets the sitewide authentication configuration. func (c Client) PutSiteConfigAuth(ctx context.Context, req ConfigAuth) error { return c.requestBody(ctx, http.MethodPut, "/api/auth/config", req, nil) } +// SiteConfigOAuth fetches the sitewide git provider OAuth configuration. func (c Client) SiteConfigOAuth(ctx context.Context) (*ConfigOAuth, error) { var conf ConfigOAuth if err := c.requestBody(ctx, http.MethodGet, "/api/oauth/config", nil, &conf); err != nil { @@ -76,6 +87,7 @@ func (c Client) SiteConfigOAuth(ctx context.Context) (*ConfigOAuth, error) { return &conf, nil } +// PutSiteConfigOAuth sets the sitewide git provider OAuth configuration. func (c Client) PutSiteConfigOAuth(ctx context.Context, req ConfigOAuth) error { return c.requestBody(ctx, http.MethodPut, "/api/oauth/config", req, nil) } @@ -84,6 +96,7 @@ type configSetupMode struct { SetupMode bool `json:"setup_mode"` } +// SiteSetupModeEnabled fetches the current setup_mode state of a Coder Enterprise deployment. func (c Client) SiteSetupModeEnabled(ctx context.Context) (bool, error) { var conf configSetupMode if err := c.requestBody(ctx, http.MethodGet, "/api/config/setup-mode", nil, &conf); err != nil { @@ -92,6 +105,7 @@ func (c Client) SiteSetupModeEnabled(ctx context.Context) (bool, error) { return conf.SetupMode, nil } +// ExtensionMarketplaceType is an enum of the valid extension marketplace configurations. type ExtensionMarketplaceType string // ExtensionMarketplaceType enum. @@ -101,13 +115,16 @@ const ( ExtensionMarketplacePublic ExtensionMarketplaceType = "public" ) +// MarketplaceExtensionPublicURL is the URL of the coder.com public marketplace that serves open source Code OSS extensions. const MarketplaceExtensionPublicURL = "https://extensions.coder.com/api" +// ConfigExtensionMarketplace describes the sitewide extension marketplace configuration. type ConfigExtensionMarketplace struct { URL string `json:"url"` Type ExtensionMarketplaceType `json:"type"` } +// SiteConfigExtensionMarketplace fetches the extension marketplace configuration. func (c Client) SiteConfigExtensionMarketplace(ctx context.Context) (*ConfigExtensionMarketplace, error) { var conf ConfigExtensionMarketplace if err := c.requestBody(ctx, http.MethodGet, "/api/extensions/config", nil, &conf); err != nil { @@ -116,6 +133,7 @@ func (c Client) SiteConfigExtensionMarketplace(ctx context.Context) (*ConfigExte return &conf, nil } +// PutSiteConfigExtensionMarketplace sets the extension marketplace configuration. func (c Client) PutSiteConfigExtensionMarketplace(ctx context.Context, req ConfigExtensionMarketplace) error { return c.requestBody(ctx, http.MethodPut, "/api/extensions/config", req, nil) } diff --git a/coder-sdk/env.go b/coder-sdk/env.go index 42bb6467..cf4cfcc9 100644 --- a/coder-sdk/env.go +++ b/coder-sdk/env.go @@ -10,7 +10,7 @@ import ( "nhooyr.io/websocket/wsjson" ) -// Environment describes a Coder environment +// Environment describes a Coder environment. type Environment struct { ID string `json:"id" table:"-"` Name string `json:"name" table:"Name"` @@ -40,7 +40,7 @@ type RebuildMessage struct { AutoOffThreshold Duration `json:"auto_off_threshold"` } -// EnvironmentStat represents the state of an environment +// EnvironmentStat represents the state of an environment. type EnvironmentStat struct { Time time.Time `json:"time"` LastOnline time.Time `json:"last_online"` @@ -58,7 +58,7 @@ func (e EnvironmentStat) String() string { return string(e.ContainerStatus) } // EnvironmentStatus refers to the states of an environment. type EnvironmentStatus string -// The following represent the possible environment container states +// The following represent the possible environment container states. const ( EnvironmentCreating EnvironmentStatus = "CREATING" EnvironmentOff EnvironmentStatus = "OFF" @@ -89,7 +89,7 @@ func (c Client) CreateEnvironment(ctx context.Context, orgID string, req CreateE } // Environments lists environments returned by the given filter. -// TODO: add the filter options, explore performance issues +// TODO: add the filter options, explore performance issue. func (c Client) Environments(ctx context.Context) ([]Environment, error) { var envs []Environment if err := c.requestBody(ctx, http.MethodGet, "/api/environments", nil, &envs); err != nil { @@ -146,7 +146,7 @@ func (c Client) DialWsep(ctx context.Context, env *Environment) (*websocket.Conn return c.dialWebsocket(ctx, "/proxy/environments/"+env.ID+"/wsep") } -// DialIDEStatus opens a websocket connection for cpu load metrics on the environment +// DialIDEStatus opens a websocket connection for cpu load metrics on the environment. func (c Client) DialIDEStatus(ctx context.Context, envID string) (*websocket.Conn, error) { return c.dialWebsocket(ctx, "/proxy/environments/"+envID+"/ide/api/status") } @@ -204,7 +204,7 @@ func (c Client) DialEnvironmentStats(ctx context.Context, envID string) (*websoc return c.dialWebsocket(ctx, "/api/environments/"+envID+"/watch-stats") } -// DialResourceLoad opens a websocket connection for cpu load metrics on the environment +// DialResourceLoad opens a websocket connection for cpu load metrics on the environment. func (c Client) DialResourceLoad(ctx context.Context, envID string) (*websocket.Conn, error) { return c.dialWebsocket(ctx, "/api/environments/"+envID+"/watch-resource-load") } @@ -233,7 +233,7 @@ type buildLogMsg struct { Type BuildLogType `json:"type"` } -// WaitForEnvironmentReady will watch the build log and return when done +// WaitForEnvironmentReady will watch the build log and return when done. func (c Client) WaitForEnvironmentReady(ctx context.Context, env *Environment) error { conn, err := c.DialEnvironmentBuildLog(ctx, env.ID) if err != nil { diff --git a/coder-sdk/error.go b/coder-sdk/error.go index 75667ef1..e5d16eb2 100644 --- a/coder-sdk/error.go +++ b/coder-sdk/error.go @@ -9,11 +9,13 @@ import ( "golang.org/x/xerrors" ) -// ErrNotFound describes an error case in which the requested resource could not be found +// ErrNotFound describes an error case in which the requested resource could not be found. var ErrNotFound = xerrors.New("resource not found") +// ErrPermissions describes an error case in which the requester has insufficient permissions to access the requested resource. var ErrPermissions = xerrors.New("insufficient permissions") +// ErrAuthentication describes the error case in which the requester has invalid authentication. var ErrAuthentication = xerrors.New("invalid authentication") // APIError is the expected payload format for our errors. diff --git a/coder-sdk/image.go b/coder-sdk/image.go index edeecabf..f87e9384 100644 --- a/coder-sdk/image.go +++ b/coder-sdk/image.go @@ -5,7 +5,7 @@ import ( "net/http" ) -// Image describes a Coder Image +// Image describes a Coder Image. type Image struct { ID string `json:"id"` OrganizationID string `json:"organization_id"` @@ -18,7 +18,7 @@ type Image struct { Deprecated bool `json:"deprecated"` } -// NewRegistryRequest describes a docker registry used in importing an image +// NewRegistryRequest describes a docker registry used in importing an image. type NewRegistryRequest struct { FriendlyName string `json:"friendly_name"` Registry string `json:"registry"` @@ -26,7 +26,7 @@ type NewRegistryRequest struct { Password string `json:"password"` } -// ImportImageReq is used to import new images and registries into Coder +// 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. @@ -39,7 +39,7 @@ type ImportImageReq struct { URL string `json:"url"` } -// ImportImage creates a new image and optionally a new registry +// ImportImage creates a new image and optionally a new registry. 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 { diff --git a/coder-sdk/org.go b/coder-sdk/org.go index 0caa9d18..d42235ae 100644 --- a/coder-sdk/org.go +++ b/coder-sdk/org.go @@ -6,28 +6,28 @@ import ( "time" ) -// Organization describes an Organization in Coder +// Organization describes an Organization in Coder. type Organization struct { ID string `json:"id"` Name string `json:"name"` Members []OrganizationUser `json:"members"` } -// OrganizationUser user wraps the basic User type and adds data specific to the user's membership of an organization +// OrganizationUser user wraps the basic User type and adds data specific to the user's membership of an organization. type OrganizationUser struct { User OrganizationRoles []Role `json:"organization_roles"` RolesUpdatedAt time.Time `json:"roles_updated_at"` } -// Organization Roles +// Organization Roles. const ( RoleOrgMember Role = "organization-member" RoleOrgAdmin Role = "organization-admin" RoleOrgManager Role = "organization-manager" ) -// Organizations gets all Organizations +// Organizations gets all Organizations. func (c Client) Organizations(ctx context.Context) ([]Organization, error) { var orgs []Organization if err := c.requestBody(ctx, http.MethodGet, "/api/orgs", nil, &orgs); err != nil { @@ -36,6 +36,7 @@ func (c Client) Organizations(ctx context.Context) ([]Organization, error) { return orgs, nil } +// OrganizationByID get the Organization by its ID. 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) @@ -45,7 +46,7 @@ func (c Client) OrganizationByID(ctx context.Context, orgID string) (*Organizati return &org, nil } -// OrganizationMembers get all members of the given organization +// 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 { @@ -54,6 +55,7 @@ func (c Client) OrganizationMembers(ctx context.Context, orgID string) ([]Organi return members, nil } +// UpdateOrganizationReq describes the patch request parameters to provide partial updates to an Organization resource. type UpdateOrganizationReq struct { Name *string `json:"name"` Description *string `json:"description"` @@ -63,10 +65,12 @@ type UpdateOrganizationReq struct { MemoryProvisioningRate *float32 `json:"memory_provisioning_rate"` } +// UpdateOrganization applys a partial update of an Organization resource. func (c Client) UpdateOrganization(ctx context.Context, orgID string, req UpdateOrganizationReq) error { return c.requestBody(ctx, http.MethodPatch, "/api/orgs/"+orgID, req, nil) } +// CreateOrganizationReq describes the request parameters to create a new Organization. type CreateOrganizationReq struct { Name string `json:"name"` Description string `json:"description"` @@ -77,10 +81,12 @@ type CreateOrganizationReq struct { MemoryProvisioningRate float32 `json:"memory_provisioning_rate"` } +// CreateOrganization creates a new Organization in Coder Enterprise. func (c Client) CreateOrganization(ctx context.Context, req CreateOrganizationReq) error { return c.requestBody(ctx, http.MethodPost, "/api/orgs", req, nil) } +// DeleteOrganization deletes an organization. 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/secrets.go b/coder-sdk/secrets.go index d4dfa1eb..9043091d 100644 --- a/coder-sdk/secrets.go +++ b/coder-sdk/secrets.go @@ -6,7 +6,7 @@ import ( "time" ) -// Secret describes a Coder secret +// Secret describes a Coder secret. type Secret struct { ID string `json:"id" table:"-"` Name string `json:"name" table:"Name"` @@ -16,7 +16,7 @@ type Secret struct { UpdatedAt time.Time `json:"updated_at" table:"-"` } -// Secrets gets all secrets for the given user +// Secrets gets all secrets for the given user. func (c *Client) Secrets(ctx context.Context, userID string) ([]Secret, error) { var secrets []Secret if err := c.requestBody(ctx, http.MethodGet, "/api/users/"+userID+"/secrets", nil, &secrets); err != nil { @@ -51,7 +51,7 @@ func (c *Client) SecretWithValueByID(ctx context.Context, id, userID string) (*S return &secret, nil } -// SecretByName gets a secret object by name +// SecretByName gets a secret object by name. func (c *Client) SecretByName(ctx context.Context, name, userID string) (*Secret, error) { secrets, err := c.Secrets(ctx, userID) if err != nil { @@ -72,12 +72,12 @@ type InsertSecretReq struct { Description string `json:"description"` } -// InsertSecret adds a new secret for the authed user +// InsertSecret adds a new secret for the authed user. func (c *Client) InsertSecret(ctx context.Context, user *User, req InsertSecretReq) error { return c.requestBody(ctx, http.MethodPost, "/api/users/"+user.ID+"/secrets", req, nil) } -// DeleteSecretByName deletes the authenticated users secret with the given name +// DeleteSecretByName deletes the authenticated users secret with the given name. func (c *Client) DeleteSecretByName(ctx context.Context, name, userID string) error { // Lookup the secret by name to get the ID. secret, err := c.SecretByName(ctx, name, userID) diff --git a/coder-sdk/tokens.go b/coder-sdk/tokens.go index 946979c0..adb021a1 100644 --- a/coder-sdk/tokens.go +++ b/coder-sdk/tokens.go @@ -6,6 +6,7 @@ import ( "time" ) +// APIToken describes a Coder Enterprise APIToken resource for use in API requests. type APIToken struct { ID string `json:"id"` Name string `json:"name"` @@ -14,6 +15,7 @@ type APIToken struct { LastUsed time.Time `json:"last_used"` } +// CreateAPITokenReq defines the paramemters for creating a new APIToken. type CreateAPITokenReq struct { Name string `json:"name"` } @@ -22,6 +24,7 @@ type createAPITokenResp struct { Key string `json:"key"` } +// CreateAPIToken creates a new APIToken for making authenticated requests to Coder Enterprise. func (c Client) CreateAPIToken(ctx context.Context, userID string, req CreateAPITokenReq) (token string, _ error) { var resp createAPITokenResp err := c.requestBody(ctx, http.MethodPost, "/api/api-keys/"+userID, req, &resp) @@ -31,6 +34,7 @@ func (c Client) CreateAPIToken(ctx context.Context, userID string, req CreateAPI return resp.Key, nil } +// APITokens fetches all APITokens owned by the given user. func (c Client) APITokens(ctx context.Context, userID string) ([]APIToken, error) { var tokens []APIToken if err := c.requestBody(ctx, http.MethodGet, "/api/api-keys/"+userID, nil, &tokens); err != nil { @@ -39,6 +43,7 @@ func (c Client) APITokens(ctx context.Context, userID string) ([]APIToken, error return tokens, nil } +// APITokenByID fetches the metadata for a given APIToken. func (c Client) APITokenByID(ctx context.Context, userID, tokenID string) (*APIToken, error) { var token APIToken if err := c.requestBody(ctx, http.MethodGet, "/api/api-keys/"+userID+"/"+tokenID, nil, &token); err != nil { @@ -47,10 +52,12 @@ func (c Client) APITokenByID(ctx context.Context, userID, tokenID string) (*APIT return &token, nil } +// DeleteAPIToken deletes an APIToken. func (c Client) DeleteAPIToken(ctx context.Context, userID, tokenID string) error { return c.requestBody(ctx, http.MethodDelete, "/api/api-keys/"+userID+"/"+tokenID, nil, nil) } +// RegenerateAPIToken regenerates the given APIToken and returns the new value. func (c Client) RegenerateAPIToken(ctx context.Context, userID, tokenID string) (token string, _ error) { var resp createAPITokenResp if err := c.requestBody(ctx, http.MethodPost, "/api/api-keys/"+userID+"/"+tokenID+"/regen", nil, &resp); err != nil { diff --git a/coder-sdk/users.go b/coder-sdk/users.go index 59c3a6f1..232e3a23 100644 --- a/coder-sdk/users.go +++ b/coder-sdk/users.go @@ -20,11 +20,10 @@ type User struct { UpdatedAt time.Time `json:"updated_at" table:"-"` } +// Role defines a Coder Enterprise permissions role group. type Role string -type Roles []Role - -// Site Roles +// Site Roles. const ( SiteAdmin Role = "site-admin" SiteAuditor Role = "site-auditor" @@ -32,8 +31,10 @@ const ( SiteMember Role = "site-member" ) +// LoginType defines the enum of valid user login types. type LoginType string +// LoginType enum options. const ( LoginTypeBuiltIn LoginType = "built-in" LoginTypeSAML LoginType = "saml" @@ -100,7 +101,7 @@ func (c Client) UserByEmail(ctx context.Context, email string) (*User, error) { type UpdateUserReq struct { // TODO(@cmoog) add update password option Revoked *bool `json:"revoked,omitempty"` - Roles *Roles `json:"roles,omitempty"` + Roles *[]Role `json:"roles,omitempty"` LoginType *LoginType `json:"login_type,omitempty"` Name *string `json:"name,omitempty"` Username *string `json:"username,omitempty"` @@ -108,10 +109,12 @@ type UpdateUserReq struct { DotfilesGitURL *string `json:"dotfiles_git_uri,omitempty"` } +// UpdateUser applyes the partial update to the given user. func (c Client) UpdateUser(ctx context.Context, userID string, req UpdateUserReq) error { return c.requestBody(ctx, http.MethodPatch, "/api/users/"+userID, req, nil) } +// CreateUserReq defines the request parameters for creating a new user resource. type CreateUserReq struct { Name string `json:"name"` Username string `json:"username"` @@ -122,10 +125,12 @@ type CreateUserReq struct { OrganizationsIDs []string `json:"organizations"` } +// CreateUser creates a new user account. func (c Client) CreateUser(ctx context.Context, req CreateUserReq) error { return c.requestBody(ctx, http.MethodPost, "/api/users", req, nil) } +// DeleteUser deletes a user account. func (c Client) DeleteUser(ctx context.Context, userID string) error { return c.requestBody(ctx, http.MethodDelete, "/api/users/"+userID, nil, nil) } diff --git a/coder-sdk/util.go b/coder-sdk/util.go index 882b3ced..0abba3c9 100644 --- a/coder-sdk/util.go +++ b/coder-sdk/util.go @@ -6,6 +6,7 @@ import ( "time" ) +// String gives a string pointer. func String(s string) *string { return &s } diff --git a/pkg/tcli/tcli.go b/pkg/tcli/tcli.go index 51ec6f55..596dda54 100644 --- a/pkg/tcli/tcli.go +++ b/pkg/tcli/tcli.go @@ -29,7 +29,7 @@ type runnable interface { io.Closer } -// ContainerConfig describes the ContainerRunner configuration schema for initializing a testing environment +// ContainerConfig describes the ContainerRunner configuration schema for initializing a testing environment. type ContainerConfig struct { Name string Image string @@ -51,13 +51,13 @@ func preflightChecks() error { return nil } -// ContainerRunner specifies a runtime container for performing command tests +// ContainerRunner specifies a runtime container for performing command tests. type ContainerRunner struct { name string ctx context.Context } -// NewContainerRunner starts a new docker container for executing command tests +// NewContainerRunner starts a new docker container for executing command tests. func NewContainerRunner(ctx context.Context, config *ContainerConfig) (*ContainerRunner, error) { if err := preflightChecks(); err != nil { return nil, err @@ -87,7 +87,7 @@ func NewContainerRunner(ctx context.Context, config *ContainerConfig) (*Containe }, nil } -// Close kills and removes the command execution testing container +// Close kills and removes the command execution testing container. func (r *ContainerRunner) Close() error { cmd := exec.CommandContext(r.ctx, "sh", "-c", strings.Join([]string{ @@ -118,7 +118,7 @@ func (r *ContainerRunner) Run(ctx context.Context, command string) *Assertable { } } -// RunCmd lifts the given *exec.Cmd into the runtime container +// RunCmd lifts the given *exec.Cmd into the runtime container. func (r *ContainerRunner) RunCmd(cmd *exec.Cmd) *Assertable { path, _ := exec.LookPath("docker") cmd.Path = path @@ -131,7 +131,7 @@ func (r *ContainerRunner) RunCmd(cmd *exec.Cmd) *Assertable { } } -// HostRunner executes command tests on the host, outside of a container +// HostRunner executes command tests on the host, outside of a container. type HostRunner struct{} // Run executes the given command on the host. @@ -145,7 +145,7 @@ func (r *HostRunner) Run(ctx context.Context, command string) *Assertable { } } -// RunCmd executes the given *exec.Cmd on the host +// RunCmd executes the given *exec.Cmd on the host. func (r *HostRunner) RunCmd(cmd *exec.Cmd) *Assertable { return &Assertable{ cmd: cmd, @@ -153,18 +153,18 @@ func (r *HostRunner) RunCmd(cmd *exec.Cmd) *Assertable { } } -// Close is a noop for HostRunner +// Close is a noop for HostRunner. func (r *HostRunner) Close() error { return nil } -// Assertable describes an initialized command ready to be run and asserted against +// Assertable describes an initialized command ready to be run and asserted against. type Assertable struct { cmd *exec.Cmd tname string } -// Assert runs the Assertable and +// Assert runs the Assertable and. func (a *Assertable) Assert(t *testing.T, option ...Assertion) { slog.Helper() var ( @@ -183,10 +183,12 @@ func (a *Assertable) Assert(t *testing.T, option ...Assertion) { err := a.cmd.Run() result.Duration = time.Since(start) - if exitErr, ok := err.(*exec.ExitError); ok { - result.ExitCode = exitErr.ExitCode() - } else if err != nil { - slogtest.Fatal(t, "command failed to run", slog.Error(err), slog.F("command", a.cmd)) + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + result.ExitCode = exitErr.ExitCode() + } else { + slogtest.Fatal(t, "command failed to run", slog.Error(err), slog.F("command", a.cmd)) + } } else { result.ExitCode = 0 } @@ -211,20 +213,20 @@ func (a *Assertable) Assert(t *testing.T, option ...Assertion) { // Pass custom Assertion functions to cover special cases. type Assertion func(t *testing.T, r *CommandResult) -// CommandResult contains the aggregated result of a command execution +// CommandResult contains the aggregated result of a command execution. type CommandResult struct { Stdout, Stderr []byte ExitCode int Duration time.Duration } -// Success asserts that the command exited with an exit code of 0 +// Success asserts that the command exited with an exit code of 0. func Success() Assertion { slog.Helper() return ExitCodeIs(0) } -// Error asserts that the command exited with a nonzero exit code +// Error asserts that the command exited with a nonzero exit code. func Error() Assertion { return func(t *testing.T, r *CommandResult) { slog.Helper() @@ -232,7 +234,7 @@ func Error() Assertion { } } -// ExitCodeIs asserts that the command exited with the given code +// ExitCodeIs asserts that the command exited with the given code. func ExitCodeIs(code int) Assertion { return func(t *testing.T, r *CommandResult) { slog.Helper() @@ -240,7 +242,7 @@ func ExitCodeIs(code int) Assertion { } } -// StdoutEmpty asserts that the command did not write any data to Stdout +// StdoutEmpty asserts that the command did not write any data to Stdout. func StdoutEmpty() Assertion { return func(t *testing.T, r *CommandResult) { slog.Helper() @@ -249,7 +251,7 @@ func StdoutEmpty() Assertion { } // GetResult offers an escape hatch from tcli -// The pointer passed as "result" will be assigned to the command's *CommandResult +// The pointer passed as "result" will be assigned to the command's *CommandResult. func GetResult(result **CommandResult) Assertion { return func(t *testing.T, r *CommandResult) { slog.Helper() @@ -257,7 +259,7 @@ func GetResult(result **CommandResult) Assertion { } } -// StderrEmpty asserts that the command did not write any data to Stderr +// StderrEmpty asserts that the command did not write any data to Stderr. func StderrEmpty() Assertion { return func(t *testing.T, r *CommandResult) { slog.Helper() @@ -265,7 +267,7 @@ func StderrEmpty() Assertion { } } -// StdoutMatches asserts that Stdout contains a substring which matches the given regexp +// StdoutMatches asserts that Stdout contains a substring which matches the given regexp. func StdoutMatches(pattern string) Assertion { return func(t *testing.T, r *CommandResult) { slog.Helper() @@ -273,7 +275,7 @@ func StdoutMatches(pattern string) Assertion { } } -// StderrMatches asserts that Stderr contains a substring which matches the given regexp +// StderrMatches asserts that Stderr contains a substring which matches the given regexp. func StderrMatches(pattern string) Assertion { return func(t *testing.T, r *CommandResult) { slog.Helper() @@ -305,7 +307,7 @@ func empty(t *testing.T, name string, a []byte) { } } -// DurationLessThan asserts that the command completed in less than the given duration +// DurationLessThan asserts that the command completed in less than the given duration. func DurationLessThan(dur time.Duration) Assertion { return func(t *testing.T, r *CommandResult) { slog.Helper() @@ -318,7 +320,7 @@ func DurationLessThan(dur time.Duration) Assertion { } } -// DurationGreaterThan asserts that the command completed in greater than the given duration +// DurationGreaterThan asserts that the command completed in greater than the given duration. func DurationGreaterThan(dur time.Duration) Assertion { return func(t *testing.T, r *CommandResult) { slog.Helper() @@ -331,7 +333,7 @@ func DurationGreaterThan(dur time.Duration) Assertion { } } -// StdoutJSONUnmarshal attempts to unmarshal stdout into the given target +// StdoutJSONUnmarshal attempts to unmarshal stdout into the given target. func StdoutJSONUnmarshal(target interface{}) Assertion { return func(t *testing.T, r *CommandResult) { slog.Helper() @@ -340,7 +342,7 @@ func StdoutJSONUnmarshal(target interface{}) Assertion { } } -// StderrJSONUnmarshal attempts to unmarshal stderr into the given target +// StderrJSONUnmarshal attempts to unmarshal stderr into the given target. func StderrJSONUnmarshal(target interface{}) Assertion { return func(t *testing.T, r *CommandResult) { slog.Helper() From 5ad33969b0eec804008009d039166837bfe8ee54 Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Tue, 3 Nov 2020 20:23:51 -0600 Subject: [PATCH 3/6] fixup! Enable more linting rules --- ci/integration/setup_test.go | 8 ++++---- cmd/coder/main.go | 8 +++++--- internal/activity/writer.go | 3 ++- internal/cmd/auth.go | 3 +-- internal/cmd/cmd.go | 5 +++-- internal/cmd/rebuild.go | 2 +- internal/cmd/resourcemanager.go | 4 ++-- internal/cmd/secrets.go | 7 ++++--- internal/cmd/shell.go | 4 ++-- internal/cmd/urls.go | 2 +- internal/loginsrv/input.go | 2 ++ internal/sync/eventcache.go | 3 +-- internal/sync/sync.go | 13 +++++++------ internal/version/version.go | 4 +++- internal/x/xterminal/terminal.go | 2 +- 15 files changed, 39 insertions(+), 31 deletions(-) diff --git a/ci/integration/setup_test.go b/ci/integration/setup_test.go index 1ccb8e0e..cce4b440 100644 --- a/ci/integration/setup_test.go +++ b/ci/integration/setup_test.go @@ -13,10 +13,10 @@ import ( "golang.org/x/xerrors" ) -// binpath is populated during package initialization with a path to the coder binary +// binpath is populated during package initialization with a path to the coder binary. var binpath string -// initialize integration tests by building the coder-cli binary +// initialize integration tests by building the coder-cli binary. func init() { cwd, err := os.Getwd() if err != nil { @@ -30,7 +30,7 @@ func init() { } } -// build the coder-cli binary and move to the integration testing bin directory +// build the coder-cli binary and move to the integration testing bin directory. func build(path string) error { tar := "coder-cli-linux-amd64.tar.gz" dir := filepath.Dir(path) @@ -48,7 +48,7 @@ func build(path string) error { return nil } -// write session tokens to the given container runner +// write session tokens to the given container runner. func headlessLogin(ctx context.Context, t *testing.T, runner *tcli.ContainerRunner) { creds := login(ctx, t) cmd := exec.CommandContext(ctx, "sh", "-c", "mkdir -p $HOME/.config/coder && cat > $HOME/.config/coder/session") diff --git a/cmd/coder/main.go b/cmd/coder/main.go index 73e1858d..b491dfa8 100644 --- a/cmd/coder/main.go +++ b/cmd/coder/main.go @@ -17,7 +17,6 @@ import ( func main() { ctx, cancel := context.WithCancel(context.Background()) - defer cancel() // If requested, spin up the pprof webserver. if os.Getenv("PPROF") != "" { @@ -31,16 +30,19 @@ func main() { clog.Log(clog.Fatal(fmt.Sprintf("set output to raw: %s", err))) os.Exit(1) } - defer func() { + restoreTerminal := func() { // Best effort. Would result in broken terminal on window but nothing we can do about it. _ = xterminal.Restore(os.Stdout.Fd(), stdoutState) - }() + } app := cmd.Make() app.Version = fmt.Sprintf("%s %s %s/%s", version.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH) if err := app.ExecuteContext(ctx); err != nil { clog.Log(err) + restoreTerminal() os.Exit(1) } + restoreTerminal() + cancel() } diff --git a/internal/activity/writer.go b/internal/activity/writer.go index 0daf6dc8..02d9d1b8 100644 --- a/internal/activity/writer.go +++ b/internal/activity/writer.go @@ -1,3 +1,4 @@ +// Package activity defines the logic for tracking usage activity metrics. package activity import ( @@ -17,7 +18,7 @@ func (w *writer) Write(buf []byte) (int, error) { return w.wr.Write(buf) } -// Writer wraps the given writer such that all writes trigger an activity push +// Writer wraps the given writer such that all writes trigger an activity push. func (p *Pusher) Writer(wr io.Writer) io.Writer { return &writer{p: p, wr: wr} } diff --git a/internal/cmd/auth.go b/internal/cmd/auth.go index 49fb4e0d..aa204d1e 100644 --- a/internal/cmd/auth.go +++ b/internal/cmd/auth.go @@ -56,8 +56,7 @@ func newClient(ctx context.Context) (*coder.Client, error) { if err != nil { var he *coder.HTTPError if xerrors.As(err, &he) { - switch he.StatusCode { - case http.StatusUnauthorized: + if he.StatusCode == http.StatusUnauthorized { return nil, xerrors.Errorf("not authenticated: try running \"coder login`\"") } } diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 462c7c3a..937c0558 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -1,3 +1,4 @@ +// Package cmd constructs all subcommands for coder-cli. package cmd import ( @@ -7,10 +8,10 @@ import ( "github.com/spf13/cobra/doc" ) -// verbose is a global flag for specifying that a command should give verbose output +// verbose is a global flag for specifying that a command should give verbose output. var verbose bool = false -// Make constructs the "coder" root command +// Make constructs the "coder" root command. func Make() *cobra.Command { app := &cobra.Command{ Use: "coder", diff --git a/internal/cmd/rebuild.go b/internal/cmd/rebuild.go index ebb1cc8a..ae2907d3 100644 --- a/internal/cmd/rebuild.go +++ b/internal/cmd/rebuild.go @@ -73,7 +73,7 @@ coder envs rebuild backend-env --force`, } // trailBuildLogs follows the build log for a given environment and prints the staged -// output with loaders and success/failure indicators for each stage +// output with loaders and success/failure indicators for each stage. func trailBuildLogs(ctx context.Context, client *coder.Client, envID string) error { const check = "✅" const failure = "❌" diff --git a/internal/cmd/resourcemanager.go b/internal/cmd/resourcemanager.go index 2e636d7e..df411e00 100644 --- a/internal/cmd/resourcemanager.go +++ b/internal/cmd/resourcemanager.go @@ -143,7 +143,7 @@ func aggregateByOrg(users []coder.User, orgs []coder.Organization, envs []coder. return groups, userLabeler{userIDMap} } -// groupable specifies a structure capable of being an aggregation group of environments (user, org, all) +// groupable specifies a structure capable of being an aggregation group of environments (user, org, all). type groupable interface { header() string environments() []coder.Environment @@ -334,7 +334,7 @@ func (a resources) memUtilPercentage() string { return fmt.Sprintf("%.1f%%", a.memUtilization/a.memAllocation*100) } -// truncate the given string and replace the removed chars with some replacement (ex: "...") +// truncate the given string and replace the removed chars with some replacement (ex: "..."). func truncate(str string, max int, replace string) string { if len(str) <= max { return str diff --git a/internal/cmd/secrets.go b/internal/cmd/secrets.go index 9fedb6a1..41e38443 100644 --- a/internal/cmd/secrets.go +++ b/internal/cmd/secrets.go @@ -98,15 +98,16 @@ coder secrets create aws-credentials --from-file ./credentials.json`, if err != nil { return err } - if fromLiteral != "" { + switch { + case fromLiteral != "": value = fromLiteral - } else if fromFile != "" { + case fromFile != "": contents, err := ioutil.ReadFile(fromFile) if err != nil { return xerrors.Errorf("read file: %w", err) } value = string(contents) - } else { + default: prompt := promptui.Prompt{ Label: "value", Mask: '*', diff --git a/internal/cmd/shell.go b/internal/cmd/shell.go index c4466870..acf733df 100644 --- a/internal/cmd/shell.go +++ b/internal/cmd/shell.go @@ -222,16 +222,16 @@ func networkErr(env *coder.Environment) error { func heartbeat(ctx context.Context, conn *websocket.Conn, interval time.Duration) { ticker := time.NewTicker(interval) - defer ticker.Stop() - for { select { case <-ctx.Done(): + ticker.Stop() return case <-ticker.C: if err := conn.Ping(ctx); err != nil { // don't try to do multi-line here because the raw mode makes things weird clog.Log(clog.Fatal("failed to ping websocket, exiting: " + err.Error())) + ticker.Stop() os.Exit(1) } } diff --git a/internal/cmd/urls.go b/internal/cmd/urls.go index a856832c..26e5d5bd 100644 --- a/internal/cmd/urls.go +++ b/internal/cmd/urls.go @@ -49,7 +49,7 @@ func urlCmd() *cobra.Command { return cmd } -// DevURL is the parsed json response record for a devURL from cemanager +// DevURL is the parsed json response record for a devURL from cemanager. type DevURL struct { ID string `json:"id" table:"-"` URL string `json:"url" table:"URL"` diff --git a/internal/loginsrv/input.go b/internal/loginsrv/input.go index 99de2015..cc0ae255 100644 --- a/internal/loginsrv/input.go +++ b/internal/loginsrv/input.go @@ -1,3 +1,5 @@ +// Package loginsrv defines the login server in use by coder-cli +// for performing the browser authentication flow. package loginsrv import ( diff --git a/internal/sync/eventcache.go b/internal/sync/eventcache.go index b4924da1..1073b123 100644 --- a/internal/sync/eventcache.go +++ b/internal/sync/eventcache.go @@ -17,9 +17,8 @@ type eventCache map[string]timedEvent func (cache eventCache) Add(ev timedEvent) { lastEvent, ok := cache[ev.Path()] if ok { - switch { // If the file was quickly created and then destroyed, pretend nothing ever happened. - case lastEvent.Event() == notify.Create && ev.Event() == notify.Remove: + if lastEvent.Event() == notify.Create && ev.Event() == notify.Remove { delete(cache, ev.Path()) return } diff --git a/internal/sync/sync.go b/internal/sync/sync.go index 225ec598..ee990c1b 100644 --- a/internal/sync/sync.go +++ b/internal/sync/sync.go @@ -1,3 +1,4 @@ +// Package sync contains logic for establishing a file sync between a local machine and a Coder Enterprise environment. package sync import ( @@ -74,13 +75,13 @@ func (s Sync) syncPaths(delete bool, local, remote string) error { if err := cmd.Run(); err != nil { if exitError, ok := err.(*exec.ExitError); ok { - if exitError.ExitCode() == rsyncExitCodeIncompat { + switch { + case exitError.ExitCode() == rsyncExitCodeIncompat: return xerrors.Errorf("no compatible rsync on remote machine: rsync: %w", err) - } else if exitError.ExitCode() == rsyncExitCodeDataStream { + case exitError.ExitCode() == rsyncExitCodeDataStream: return xerrors.Errorf("protocol datastream error or no remote rsync found: %w", err) - } else { - return xerrors.Errorf("rsync: %w", err) } + return xerrors.Errorf("rsync: %w", err) } return xerrors.Errorf("rsync: %w", err) } @@ -207,7 +208,7 @@ func (s Sync) work(ev timedEvent) { } } -// ErrRestartSync describes a known error case that can be solved by re-starting the command +// ErrRestartSync describes a known error case that can be solved by re-starting the command. var ErrRestartSync = errors.New("the sync exited because it was overloaded, restart it") // workEventGroup converges a group of events to prevent duplicate work. @@ -302,7 +303,7 @@ func (s Sync) Version() (string, error) { // Run starts the sync synchronously. // Use this command to debug what wasn't sync'd correctly: -// rsync -e "coder sh" -nicr ~/Projects/cdr/coder-cli/. ammar:/home/coder/coder-cli/ +// rsync -e "coder sh" -nicr ~/Projects/cdr/coder-cli/. ammar:/home/coder/coder-cli/. func (s Sync) Run() error { events := make(chan notify.EventInfo, maxInflightInotify) // Set up a recursive watch. diff --git a/internal/version/version.go b/internal/version/version.go index 88b0626c..ce1d5de9 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -1,3 +1,5 @@ +// Package version contains the compile-time injected version string and +// related utiliy methods. package version import ( @@ -7,7 +9,7 @@ import ( // Version is populated at compile-time with the current coder-cli version. var Version string = "unknown" -// VersionMatch compares the given APIVersion to the compile-time injected coder-cli version. +// VersionsMatch compares the given APIVersion to the compile-time injected coder-cli version. func VersionsMatch(apiVersion string) bool { withoutPatchRelease := strings.Split(Version, ".") if len(withoutPatchRelease) < 3 { diff --git a/internal/x/xterminal/terminal.go b/internal/x/xterminal/terminal.go index 374869d8..dd2543e8 100644 --- a/internal/x/xterminal/terminal.go +++ b/internal/x/xterminal/terminal.go @@ -42,7 +42,7 @@ func ColorEnabled(fd uintptr) (bool, error) { return terminal.IsTerminal(int(fd)), nil } -// ResizeEvent describes the new terminal dimensions following a resize +// ResizeEvent describes the new terminal dimensions following a resize. type ResizeEvent struct { Height uint16 Width uint16 From f499d6c34a8a92ce96821c960b96cd4ff07d6cea Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Tue, 3 Nov 2020 20:26:17 -0600 Subject: [PATCH 4/6] fixup! Enable more linting rules --- internal/cmd/ceapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/ceapi.go b/internal/cmd/ceapi.go index 3d91c176..1e7e2480 100644 --- a/internal/cmd/ceapi.go +++ b/internal/cmd/ceapi.go @@ -58,7 +58,7 @@ func getEnvs(ctx context.Context, client *coder.Client, email string) ([]coder.E return allEnvs, nil } -// findEnv returns a single environment by name (if it exists.) +// findEnv returns a single environment by name (if it exists.). func findEnv(ctx context.Context, client *coder.Client, envName, userEmail string) (*coder.Environment, error) { envs, err := getEnvs(ctx, client, userEmail) if err != nil { From 40006ff4bb82d070b25d01724ca53efefe8fbaaf Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Tue, 3 Nov 2020 20:29:37 -0600 Subject: [PATCH 5/6] fixup! Enable more linting rules --- cmd/coder/main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/coder/main.go b/cmd/coder/main.go index b491dfa8..7a7f2716 100644 --- a/cmd/coder/main.go +++ b/cmd/coder/main.go @@ -28,6 +28,7 @@ func main() { stdoutState, err := xterminal.MakeOutputRaw(os.Stdout.Fd()) if err != nil { clog.Log(clog.Fatal(fmt.Sprintf("set output to raw: %s", err))) + cancel() os.Exit(1) } restoreTerminal := func() { @@ -40,6 +41,7 @@ func main() { if err := app.ExecuteContext(ctx); err != nil { clog.Log(err) + cancel() restoreTerminal() os.Exit(1) } From eed820b036cf2cf62429a7a12296b165b97bca78 Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Tue, 3 Nov 2020 20:29:54 -0600 Subject: [PATCH 6/6] fixup! Enable more linting rules --- cmd/coder/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/coder/main.go b/cmd/coder/main.go index 7a7f2716..51ce8614 100644 --- a/cmd/coder/main.go +++ b/cmd/coder/main.go @@ -45,6 +45,6 @@ func main() { restoreTerminal() os.Exit(1) } - restoreTerminal() cancel() + restoreTerminal() }