-
Notifications
You must be signed in to change notification settings - Fork 957
feat: coder-attach: add support for external workspaces #19178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…ils, extend CodeExample to redact parts of the code
…attach # Conflicts: # cli/create.go # coderd/database/queries.sql.go # docs/admin/security/audit-logs.md # provisionersdk/proto/provisioner.pb.go
…attach # Conflicts: # codersdk/workspaces.go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would highly recommend breaking this PR down by domain. Right now it's extremely large.
Suggested breakpoints:
- Database
- API/protobuf
- CLI
- UI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When adding a new field to a protobuf, we increment the minor version in provisionerd/proto/version.go
.
actions: { | ||
display: "flex", | ||
alignItems: "center", | ||
gap: 4, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pls use tailwind for new styles
@@ -19,7 +30,31 @@ export const CodeExample: FC<CodeExampleProps> = ({ | |||
// Defaulting to true to be on the safe side; you should have to opt out of | |||
// the secure option, not remember to opt in | |||
secret = true, | |||
|
|||
// Redact parts of the code if the user doesn't want to obfuscate the whole code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know there was already a bad pattern here, but these comments will be much more helpful on the type definition in JSDoc format so that the language server will actually recognize them
redactPattern, | ||
redactReplacement = "********", | ||
|
||
// Show a button to show the redacted parts of the code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
redactReplacement = "********", | ||
|
||
// Show a button to show the redacted parts of the code | ||
redactShowButton = false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a really confusing name. it kind of sounds like by setting it to true you're "redacting the show button" which doesn't really make sense. just showButton
would also be confusing. what button are we showing and why?
I think these are usually called "reveal" buttons. maybe showRevealButton
or enableRevealButton
?
redactShowButton = false, | |
redactShowButton, |
also you don't need the default. undefined
is treated like false
when used as a boolean.
const styles = { | ||
externalAgentSection: (theme) => ({ | ||
fontSize: 16, | ||
color: theme.palette.text.secondary, | ||
paddingBottom: 8, | ||
lineHeight: 1.4, | ||
}), | ||
} satisfies Record<string, Interpolation<Theme>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pls use tailwind for new styles
…attach # Conflicts: # coderd/database/queries/templateversions.sql
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BE review 👍
cli/external_workspaces.go
Outdated
cliui.ChangeFormatterData(cliui.TextFormat(), func(data any) (any, error) { | ||
agent, ok := data.(externalAgent) | ||
if !ok { | ||
return "", xerrors.Errorf("expected externalAgent, got %T", data) | ||
} | ||
|
||
var output strings.Builder | ||
_, _ = output.WriteString(fmt.Sprintf("Please run the following commands to attach agent %s:\n", cliui.Keyword(agent.AgentName))) | ||
_, _ = output.WriteString(fmt.Sprintf("%s\n", pretty.Sprint(cliui.DefaultStyles.Code, fmt.Sprintf("export CODER_AGENT_TOKEN=%s", agent.AuthToken)))) | ||
_, _ = output.WriteString(pretty.Sprint(cliui.DefaultStyles.Code, fmt.Sprintf("curl -fsSL %s | sh", agent.InitScript))) | ||
|
||
return output.String(), nil | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This chunk of code seems to be used multiple times, can you not reuse printExternalAgents()
?
cli/external_workspaces.go
Outdated
template, err := client.TemplateByName(inv.Context(), organization.ID, templateName.Value.String()) | ||
if err != nil { | ||
return xerrors.Errorf("get template by name: %w", err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of these queries are going to be repeated by the original handler, could we change the createHandler
function into a model where the queries get run once, then a "validate" function that gets swapped for coder create
or coder external-workspaces create
gets executed (and all the queried objects get passed in), then the workspace gets created.
workspace, workspaceBuild, err := createHandler(ctx, inv, createOptions{
beforeCreate: func(ctx context.Context, template codersdk.Template, ...) error {},
})
cli/root.go
Outdated
@@ -127,6 +127,9 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command { | |||
r.update(), | |||
r.whoami(), | |||
|
|||
// External Workspace Commands |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just group this with workspaces tbh
cli/external_workspaces.go
Outdated
|
||
cmd := &serpent.Command{ | ||
Use: "external-workspaces [subcommand]", | ||
Short: "External workspace related commands", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Short: "External workspace related commands", | |
Short: "Create or manage external workspaces", |
cli/external_workspaces.go
Outdated
) | ||
|
||
cmd := &serpent.Command{ | ||
Use: "agent-instructions [workspace name] [agent name]", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other commands use [user/]workspace[.agent]
syntax for selecting an agent, defaulting to the only agent in the workspace if the agent is unspecified and there's only one. I believe there's a helper function in cli
to resolve these for you.
coderd/init_script.go
Outdated
return | ||
} | ||
script = strings.ReplaceAll(script, "${ACCESS_URL}", api.AccessURL.String()+"/") | ||
script = strings.ReplaceAll(script, "${AUTH_TYPE}", "token") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't auth type be configurable e.g. with an query parameter var? Defaulting the value to "token" is fine, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Phase 1 assumes token-based auth, so I think it can stay as it is. In the future, we can extend this to include other types of auth.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would that look like on the init script endpoint? A query parameter defaulting to token
perhaps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that's the plan 👍
@@ -1731,7 +1731,17 @@ func (s *server) completeTemplateImportJob(ctx context.Context, job database.Pro | |||
if err != nil { | |||
return xerrors.Errorf("update template version external auth providers: %w", err) | |||
} | |||
|
|||
err = db.UpdateTemplateVersionExternalAgentByJobID(ctx, database.UpdateTemplateVersionExternalAgentByJobIDParams{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could probably be merged with the above query.
break | ||
} | ||
} | ||
err = db.UpdateWorkspaceBuildExternalAgentByID(ctx, database.UpdateWorkspaceBuildExternalAgentByIDParams{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
coderd/workspaceagents.go
Outdated
|
||
for _, agent := range agents { | ||
if agent.Name == agentName { | ||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ExternalAgentCredentials{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the external agent uses a different authentication strategy than "token" we should probably return a 404 or something else that the frontend can detect.
{workspace.name} workspace: | ||
</p> | ||
<CodeExample | ||
code={`CODER_AGENT_TOKEN="${externalAgentToken}" curl -fsSL "${initScriptURL}" | sh`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the generation of the command should be moved to the backend and returned from the API, so we can keep it all in one place. This will need to eventually vary by OS/arch (curl | sh
doesn't work on windows).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can extend the credentials endpoint and add another field for the ready-to-go command. Unless you have a different idea?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds good to me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
…ernalAgentByJobID into one query
…attach # Conflicts: # coderd/database/dump.sql # coderd/database/modelqueries.go # coderd/database/queries.sql.go # coderd/database/queries/templates.sql # coderd/searchquery/search.go # coderd/searchquery/search_test.go
…ecks for new endpoint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Backend looks good, would like to see a few quick changes then I'll stamp
@@ -234,3 +225,13 @@ FROM | |||
WHERE | |||
template_versions.id IN (archived_versions.id) | |||
RETURNING template_versions.id; | |||
|
|||
-- name: UpdateTemplateVersionAITaskAndExternalAgentByJobID :exec |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe UpdateTemplateVersionFlagsByJobID
? The name might get even crazier if we add more of these post-build flag columns.
@@ -253,3 +244,13 @@ WHERE | |||
AND pj.job_status = 'failed' | |||
ORDER BY | |||
tv.name ASC, wb.build_number DESC; | |||
|
|||
-- name: UpdateWorkspaceBuildAITaskAndExternalAgentByID :exec |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
coderd/init_script.go
Outdated
|
||
script, exists := provisionersdk.AgentScriptEnv()[fmt.Sprintf("CODER_AGENT_SCRIPT_%s_%s", os, arch)] | ||
if !exists { | ||
rw.WriteHeader(http.StatusBadRequest) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should probably write a message here too, using httpapi.Write(..., codersdk.Response{})
is probably fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unknown os/arch: %s/%s
coderd/init_script_test.go
Outdated
client := coderdtest.New(t, nil) | ||
script, err := client.InitScript(context.Background(), "windows", "amd64") | ||
require.NoError(t, err) | ||
require.NotEmpty(t, script) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should test the replacements as well, probably search for substrings like $env:CODER_AGENT_AUTH_TYPE="token"
(or whatever it's supposed to look like) etc.
coderd/workspaceagents_test.go
Outdated
client, db := coderdtest.NewWithDatabase(t, nil) | ||
user := coderdtest.CreateFirstUser(t, client) | ||
|
||
t.Run("Success", func(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should check the returned command matches what you expect for both non-windows and windows in separate tests.
codersdk/init_script.go
Outdated
@@ -0,0 +1,28 @@ | |||
package codersdk |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: This file should probably be renamed just initscript.go
to match other files in the dir like provisionerdaemons.go
. Files ending with _xyz.go
are usually for showing stuff similar build tags in the filename
cli/external_workspaces.go
Outdated
@@ -1,6 +1,7 @@ | |||
package cli |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Maybe this should be externalworkspaces.go
too
Co-authored-by: Dean Sheather <dean@deansheather.com>
quoting @johnstcn, I think this still needs to be addressed. I did a cursory review but it's really hard to actually feel comfortable approving a diff this large. I think this should probably be broken into at least like three chunks before it gets merged. |
Depends on: coder/terraform-provider-coder#424
This pull request introduces support for external workspace management, allowing users to register and manage workspaces that are provisioned and managed outside of the Coder.
CLI changes
coder external-workspaces create
- Creates a new external workspace (this command extendscoder create
)coder external-workspaces create ext-workspace --template=externally-managed-workspace -y
coder external-workspaces list
- Lists all external workspacescoder external-workspaces agent-instructions <workspace name> <agent name>
- Retrieves agent connection instructioncoder external-workspaces agent-instructions ext-workspace main --output=json
coderd changes
GET /api/v2/init-script
- Gets the agent initialization scriptos
andarch
) you can get the init script for different platformsGET /api/v2/workspaces/{workspace}/external-agent/{agent}/credentials
- Gets credentials for an external agentDatabase changes
has_external_agent
field to workspace builds and template versionshas_external_agent
fieldprovisioner changes
coder_external_agent
terraform resourceFrontend changes
CodeExample
so you can now hide specific parts of the code instead of the full line