feat: Refactor API routes to use UUIDs instead of friendly names (#401)

* Add client for agent

* Cleanup code

* Fix linting error

* Rename routes to be simpler

* Rename workspace history to workspace build

* Refactor HTTP middlewares to use UUIDs

* Cleanup routes

* Compiles!

* Fix files and organizations

* Fix querying

* Fix agent lock

* Cleanup database abstraction

* Add parameters

* Fix linting errors

* Fix log race

* Lock on close wait

* Fix log cleanup

* Fix e2e tests

* Fix upstream version of opencensus-go

* Update coderdtest.go

* Fix coverpkg

* Fix codecov ignore
This commit is contained in:
Kyle Carberry
2022-03-07 11:40:54 -06:00
committed by GitHub
parent 330686f60a
commit bf0ae8f573
115 changed files with 5853 additions and 4657 deletions

View File

@ -59,7 +59,7 @@ func login() *cobra.Command {
}
client := codersdk.New(serverURL)
hasInitialUser, err := client.HasInitialUser(cmd.Context())
hasInitialUser, err := client.HasFirstUser(cmd.Context())
if err != nil {
return xerrors.Errorf("has initial user: %w", err)
}
@ -119,7 +119,7 @@ func login() *cobra.Command {
return xerrors.Errorf("specify password prompt: %w", err)
}
_, err = client.CreateInitialUser(cmd.Context(), coderd.CreateInitialUserRequest{
_, err = client.CreateFirstUser(cmd.Context(), coderd.CreateFirstUserRequest{
Email: email,
Username: username,
Password: password,

View File

@ -1,13 +1,11 @@
package cli_test
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/pty/ptytest"
)
@ -56,18 +54,7 @@ func TestLogin(t *testing.T) {
t.Run("ExistingUserValidTokenTTY", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
_, err := client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{
Username: "test-user",
Email: "test-user@coder.com",
Organization: "acme-corp",
Password: "password",
})
require.NoError(t, err)
token, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
Email: "test-user@coder.com",
Password: "password",
})
require.NoError(t, err)
coderdtest.CreateFirstUser(t, client)
root, _ := clitest.New(t, "login", client.URL.String(), "--force-tty", "--no-open")
pty := ptytest.New(t)
@ -79,20 +66,14 @@ func TestLogin(t *testing.T) {
}()
pty.ExpectMatch("Paste your token here:")
pty.WriteLine(token.SessionToken)
pty.WriteLine(client.SessionToken)
pty.ExpectMatch("Welcome to Coder")
})
t.Run("ExistingUserInvalidTokenTTY", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
_, err := client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{
Username: "test-user",
Email: "test-user@coder.com",
Organization: "acme-corp",
Password: "password",
})
require.NoError(t, err)
coderdtest.CreateFirstUser(t, client)
root, _ := clitest.New(t, "login", client.URL.String(), "--force-tty", "--no-open")
pty := ptytest.New(t)

View File

@ -56,7 +56,7 @@ func projectCreate() *cobra.Command {
Default: filepath.Base(directory),
Label: "What's your project's name?",
Validate: func(s string) error {
project, _ := client.Project(cmd.Context(), organization.Name, s)
project, _ := client.ProjectByName(cmd.Context(), organization.ID, s)
if project.ID.String() != uuid.Nil.String() {
return xerrors.New("A project already exists with that name!")
}
@ -71,9 +71,9 @@ func projectCreate() *cobra.Command {
if err != nil {
return err
}
project, err := client.CreateProject(cmd.Context(), organization.Name, coderd.CreateProjectRequest{
Name: name,
VersionImportJobID: job.ID,
project, err := client.CreateProject(cmd.Context(), organization.ID, coderd.CreateProjectRequest{
Name: name,
VersionID: job.ID,
})
if err != nil {
return err
@ -118,7 +118,7 @@ func projectCreate() *cobra.Command {
return cmd
}
func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, organization coderd.Organization, provisioner database.ProvisionerType, directory string, parameters ...coderd.CreateParameterValueRequest) (*coderd.ProvisionerJob, error) {
func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, organization coderd.Organization, provisioner database.ProvisionerType, directory string, parameters ...coderd.CreateParameterRequest) (*coderd.ProjectVersion, error) {
spin := spinner.New(spinner.CharSets[5], 100*time.Millisecond)
spin.Writer = cmd.OutOrStdout()
spin.Suffix = " Uploading current directory..."
@ -133,13 +133,13 @@ func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, o
if err != nil {
return nil, err
}
resp, err := client.UploadFile(cmd.Context(), codersdk.ContentTypeTar, tarData)
resp, err := client.Upload(cmd.Context(), codersdk.ContentTypeTar, tarData)
if err != nil {
return nil, err
}
before := time.Now()
job, err := client.CreateProjectImportJob(cmd.Context(), organization.Name, coderd.CreateProjectImportJobRequest{
version, err := client.CreateProjectVersion(cmd.Context(), organization.ID, coderd.CreateProjectVersionRequest{
StorageMethod: database.ProvisionerStorageMethodFile,
StorageSource: resp.Hash,
Provisioner: provisioner,
@ -149,7 +149,7 @@ func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, o
return nil, err
}
spin.Suffix = " Waiting for the import to complete..."
logs, err := client.ProjectImportJobLogsAfter(cmd.Context(), organization.Name, job.ID, before)
logs, err := client.ProjectVersionLogsAfter(cmd.Context(), version.ID, before)
if err != nil {
return nil, err
}
@ -162,22 +162,22 @@ func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, o
logBuffer = append(logBuffer, log)
}
job, err = client.ProjectImportJob(cmd.Context(), organization.Name, job.ID)
version, err = client.ProjectVersion(cmd.Context(), version.ID)
if err != nil {
return nil, err
}
parameterSchemas, err := client.ProjectImportJobSchemas(cmd.Context(), organization.Name, job.ID)
parameterSchemas, err := client.ProjectVersionSchema(cmd.Context(), version.ID)
if err != nil {
return nil, err
}
parameterValues, err := client.ProjectImportJobParameters(cmd.Context(), organization.Name, job.ID)
parameterValues, err := client.ProjectVersionParameters(cmd.Context(), version.ID)
if err != nil {
return nil, err
}
spin.Stop()
if provisionerd.IsMissingParameterError(job.Error) {
valuesBySchemaID := map[string]coderd.ComputedParameterValue{}
if provisionerd.IsMissingParameterError(version.Job.Error) {
valuesBySchemaID := map[string]coderd.ProjectVersionParameter{}
for _, parameterValue := range parameterValues {
valuesBySchemaID[parameterValue.SchemaID.String()] = parameterValue
}
@ -192,7 +192,7 @@ func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, o
if err != nil {
return nil, err
}
parameters = append(parameters, coderd.CreateParameterValueRequest{
parameters = append(parameters, coderd.CreateParameterRequest{
Name: parameterSchema.Name,
SourceValue: value,
SourceScheme: database.ParameterSourceSchemeData,
@ -202,21 +202,20 @@ func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, o
return validateProjectVersionSource(cmd, client, organization, provisioner, directory, parameters...)
}
if job.Status != coderd.ProvisionerJobStatusSucceeded {
if version.Job.Status != coderd.ProvisionerJobSucceeded {
for _, log := range logBuffer {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", color.HiGreenString("[tf]"), log.Output)
}
return nil, xerrors.New(job.Error)
return nil, xerrors.New(version.Job.Error)
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s Successfully imported project source!\n", color.HiGreenString("✓"))
resources, err := client.ProjectImportJobResources(cmd.Context(), organization.Name, job.ID)
resources, err := client.ProjectVersionResources(cmd.Context(), version.ID)
if err != nil {
return nil, err
}
return &job, displayProjectImportInfo(cmd, parameterSchemas, parameterValues, resources)
return &version, displayProjectImportInfo(cmd, parameterSchemas, parameterValues, resources)
}
func tarDirectory(directory string) ([]byte, error) {

View File

@ -18,7 +18,7 @@ func TestProjectCreate(t *testing.T) {
t.Run("NoParameters", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateInitialUser(t, client)
coderdtest.CreateFirstUser(t, client)
source := clitest.CreateProjectVersionSource(t, &echo.Responses{
Parse: echo.ParseComplete,
Provision: echo.ProvisionComplete,
@ -54,7 +54,7 @@ func TestProjectCreate(t *testing.T) {
t.Run("Parameter", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateInitialUser(t, client)
coderdtest.CreateFirstUser(t, client)
source := clitest.CreateProjectVersionSource(t, &echo.Responses{
Parse: []*proto.Parse_Response{{
Type: &proto.Parse_Response_Complete{

View File

@ -23,7 +23,7 @@ func projectList() *cobra.Command {
if err != nil {
return err
}
projects, err := client.Projects(cmd.Context(), organization.Name)
projects, err := client.ProjectsByOrganization(cmd.Context(), organization.ID)
if err != nil {
return err
}

View File

@ -15,7 +15,7 @@ func TestProjectList(t *testing.T) {
t.Run("None", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateInitialUser(t, client)
coderdtest.CreateFirstUser(t, client)
cmd, root := clitest.New(t, "projects", "list")
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
@ -33,12 +33,12 @@ func TestProjectList(t *testing.T) {
t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
user := coderdtest.CreateFirstUser(t, client)
daemon := coderdtest.NewProvisionerDaemon(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
_ = daemon.Close()
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
cmd, root := clitest.New(t, "projects", "list")
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)

View File

@ -40,8 +40,8 @@ func projects() *cobra.Command {
return cmd
}
func displayProjectImportInfo(cmd *cobra.Command, parameterSchemas []coderd.ParameterSchema, parameterValues []coderd.ComputedParameterValue, resources []coderd.ProvisionerJobResource) error {
schemaByID := map[string]coderd.ParameterSchema{}
func displayProjectImportInfo(cmd *cobra.Command, parameterSchemas []coderd.ProjectVersionParameterSchema, parameterValues []coderd.ProjectVersionParameter, resources []coderd.WorkspaceResource) error {
schemaByID := map[string]coderd.ProjectVersionParameterSchema{}
for _, schema := range parameterSchemas {
schemaByID[schema.ID.String()] = schema
}

View File

@ -108,7 +108,7 @@ func createClient(cmd *cobra.Command) (*codersdk.Client, error) {
// currentOrganization returns the currently active organization for the authenticated user.
func currentOrganization(cmd *cobra.Command, client *codersdk.Client) (coderd.Organization, error) {
orgs, err := client.UserOrganizations(cmd.Context(), "me")
orgs, err := client.OrganizationsByUser(cmd.Context(), "me")
if err != nil {
return coderd.Organization{}, nil
}

57
cli/workspaceagent.go Normal file
View File

@ -0,0 +1,57 @@
package cli
import (
"net/url"
"os"
"github.com/powersj/whatsthis/pkg/cloud"
"github.com/spf13/cobra"
"golang.org/x/xerrors"
"github.com/coder/coder/agent"
"github.com/coder/coder/codersdk"
)
func workspaceAgent() *cobra.Command {
return &cobra.Command{
Use: "agent",
// This command isn't useful for users, and seems
// more likely to confuse.
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
coderURLRaw, exists := os.LookupEnv("CODER_URL")
if !exists {
return xerrors.New("CODER_URL must be set")
}
coderURL, err := url.Parse(coderURLRaw)
if err != nil {
return xerrors.Errorf("parse %q: %w", coderURLRaw, err)
}
client := codersdk.New(coderURL)
sessionToken, exists := os.LookupEnv("CODER_TOKEN")
if !exists {
probe, err := cloud.New()
if err != nil {
return xerrors.Errorf("probe cloud: %w", err)
}
if !probe.Detected {
return xerrors.Errorf("no valid authentication method found; set \"CODER_TOKEN\"")
}
switch {
case probe.GCP():
response, err := client.AuthWorkspaceGoogleInstanceIdentity(cmd.Context(), "", nil)
if err != nil {
return xerrors.Errorf("authenticate workspace with gcp: %w", err)
}
sessionToken = response.SessionToken
default:
return xerrors.Errorf("%q authentication not supported; set \"CODER_TOKEN\" instead", probe.Name)
}
}
client.SessionToken = sessionToken
closer := agent.New(client.ListenWorkspaceAgent, nil)
<-cmd.Context().Done()
return closer.Close()
},
}
}

View File

@ -39,7 +39,7 @@ func workspaceCreate() *cobra.Command {
if s == "" {
return xerrors.Errorf("You must provide a name!")
}
workspace, _ := client.Workspace(cmd.Context(), "", s)
workspace, _ := client.WorkspaceByName(cmd.Context(), "", s)
if workspace.ID.String() != uuid.Nil.String() {
return xerrors.New("A workspace already exists with that name!")
}
@ -56,23 +56,23 @@ func workspaceCreate() *cobra.Command {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s Previewing project create...\n", caret)
project, err := client.Project(cmd.Context(), organization.Name, args[0])
project, err := client.ProjectByName(cmd.Context(), organization.ID, args[0])
if err != nil {
return err
}
projectVersion, err := client.ProjectVersion(cmd.Context(), organization.Name, project.Name, project.ActiveVersionID.String())
projectVersion, err := client.ProjectVersion(cmd.Context(), project.ActiveVersionID)
if err != nil {
return err
}
parameterSchemas, err := client.ProjectImportJobSchemas(cmd.Context(), organization.Name, projectVersion.ImportJobID)
parameterSchemas, err := client.ProjectVersionSchema(cmd.Context(), projectVersion.ID)
if err != nil {
return err
}
parameterValues, err := client.ProjectImportJobParameters(cmd.Context(), organization.Name, projectVersion.ImportJobID)
parameterValues, err := client.ProjectVersionParameters(cmd.Context(), projectVersion.ID)
if err != nil {
return err
}
resources, err := client.ProjectImportJobResources(cmd.Context(), organization.Name, projectVersion.ImportJobID)
resources, err := client.ProjectVersionResources(cmd.Context(), projectVersion.ID)
if err != nil {
return err
}
@ -100,7 +100,7 @@ func workspaceCreate() *cobra.Command {
if err != nil {
return err
}
history, err := client.CreateWorkspaceHistory(cmd.Context(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
version, err := client.CreateWorkspaceBuild(cmd.Context(), workspace.ID, coderd.CreateWorkspaceBuildRequest{
ProjectVersionID: projectVersion.ID,
Transition: database.WorkspaceTransitionStart,
})
@ -108,7 +108,7 @@ func workspaceCreate() *cobra.Command {
return err
}
logs, err := client.WorkspaceProvisionJobLogsAfter(cmd.Context(), organization.Name, history.ProvisionJobID, time.Time{})
logs, err := client.WorkspaceBuildLogsAfter(cmd.Context(), version.ID, time.Time{})
if err != nil {
return err
}

View File

@ -17,9 +17,9 @@ func TestWorkspaceCreate(t *testing.T) {
t.Run("Create", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdtest.NewProvisionerDaemon(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
@ -32,8 +32,8 @@ func TestWorkspaceCreate(t *testing.T) {
},
}},
})
coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
cmd, root := clitest.New(t, "workspaces", "create", project.Name)
clitest.SetupConfig(t, client, root)

View File

@ -6,6 +6,7 @@ func workspaces() *cobra.Command {
cmd := &cobra.Command{
Use: "workspaces",
}
cmd.AddCommand(workspaceAgent())
cmd.AddCommand(workspaceCreate())
return cmd