Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed
kacpersaw wants to merge34 commits intomainfromkacpersaw/feat-coder-attach
Closed
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
34 commits
Select commitHold shift + click to select a range
3ea541e
Add attach command and API endpoints for init-script and external age…
kacpersawJul 23, 2025
4818df1
add has_external_agents column to template_versions table
kacpersawJul 24, 2025
4bfdb83
add external workspace creation and agent instruction commands to cli
kacpersawJul 28, 2025
1044051
add has_external_agent to workspace builds
kacpersawJul 29, 2025
fd2458b
add list command for external workspaces
kacpersawJul 30, 2025
0c39f50
add AgentExternal component to display external agent connection deta…
kacpersawJul 30, 2025
23e555a
Merge remote-tracking branch 'origin/main' into kacpersaw/feat-coder-…
kacpersawJul 31, 2025
f9f5be1
add tests
kacpersawJul 31, 2025
e281f0e
Delete coder attach golden
kacpersawAug 5, 2025
d77522d
Hide agent apps when connecting & is external agent
kacpersawAug 5, 2025
451c806
Merge remote-tracking branch 'origin/main' into kacpersaw/feat-coder-…
kacpersawAug 5, 2025
f9274fe
Reformat code
kacpersawAug 5, 2025
00b6f26
Merge remote-tracking branch 'origin/main' into kacpersaw/feat-coder-…
kacpersawAug 6, 2025
c019a31
bump provisionerd proto version to v1.9
kacpersawAug 6, 2025
7d07857
Add beforeCreate and afterCreate to create handler, apply review sugg…
kacpersawAug 6, 2025
c462a69
Refactor init-script endpoint to use path params instead of query params
kacpersawAug 6, 2025
2d2dfec
Refactor init-script endpoint, apply review suggestions for db
kacpersawAug 6, 2025
387fc04
Apply FE review suggestions
kacpersawAug 6, 2025
c2588ea
Return 404 if workspace agent is authenticated through instance id
kacpersawAug 6, 2025
33dd778
Merge UpdateTemplateVersionAITaskByJobID and UpdateTemplateVersionExt…
kacpersawAug 7, 2025
c413479
update external agent credentials to include command in response
kacpersawAug 7, 2025
3c1d694
Bump terraform-provider-coder to v2.10.0
kacpersawAug 8, 2025
73acd0f
Merge remote-tracking branch 'origin/main' into kacpersaw/feat-coder-…
kacpersawAug 8, 2025
7cc6861
Regenerate sql
kacpersawAug 8, 2025
da68c20
Fix lint & tests
kacpersawAug 8, 2025
2e24741
Regenerate dump.sql
kacpersawAug 8, 2025
682ea60
Fix provision test
kacpersawAug 8, 2025
51967a5
update external agent credentials summary and adjust authorization ch…
kacpersawAug 8, 2025
f060324
merge UpdateWorkspaceBuildAITaskByID with UpdateWorkspaceBuildExterna…
kacpersawAug 8, 2025
ff6e8fa
Apply suggestions from code review
kacpersawAug 8, 2025
e2a7182
Apply review suggestions
kacpersawAug 8, 2025
141bc54
Merge branch 'main' into kacpersaw/feat-coder-attach
kacpersawAug 8, 2025
a75c1f4
make gen
kacpersawAug 8, 2025
22f2c00
Merge remote-tracking branch 'origin/main' into kacpersaw/feat-coder-…
kacpersawAug 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletioncli/create.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,12 @@ const PresetNone = "none"

var ErrNoPresetFound = xerrors.New("no preset found")

func (r *RootCmd) create() *serpent.Command {
type createOptions struct {
beforeCreate func(ctx context.Context, client *codersdk.Client, template codersdk.Template, templateVersionID uuid.UUID) error
afterCreate func(ctx context.Context, inv *serpent.Invocation, client *codersdk.Client, workspace codersdk.Workspace) error
}
Comment on lines +32 to +35
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

👍 nice abstraction!


func (r *RootCmd) create(opts createOptions) *serpent.Command {
var (
templateName string
templateVersion string
Expand DownExpand Up@@ -305,6 +310,13 @@ func (r *RootCmd) create() *serpent.Command {
_, _ = fmt.Fprintf(inv.Stdout, "%s", cliui.Bold("No preset applied."))
}

if opts.beforeCreate != nil {
err = opts.beforeCreate(inv.Context(), client, template, templateVersionID)
if err != nil {
return xerrors.Errorf("before create: %w", err)
}
}

richParameters, err := prepWorkspaceBuild(inv, client, prepWorkspaceBuildArgs{
Action: WorkspaceCreate,
TemplateVersionID: templateVersionID,
Expand DownExpand Up@@ -366,6 +378,14 @@ func (r *RootCmd) create() *serpent.Command {
cliui.Keyword(workspace.Name),
cliui.Timestamp(time.Now()),
)

if opts.afterCreate != nil {
err = opts.afterCreate(inv.Context(), inv, client, workspace)
if err != nil {
return err
}
}

return nil
},
}
Expand Down
271 changes: 271 additions & 0 deletionscli/externalworkspaces.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
package cli

import (
"context"
"fmt"
"strings"

"github.com/google/uuid"
"golang.org/x/xerrors"

"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/pretty"
"github.com/coder/serpent"
)

typeexternalAgentstruct {
WorkspaceNamestring`json:"-"`
AgentNamestring`json:"-"`
AuthTypestring`json:"auth_type"`
AuthTokenstring`json:"auth_token"`
InitScriptstring`json:"init_script"`
}

func (r*RootCmd)externalWorkspaces()*serpent.Command {
orgContext:=NewOrganizationContext()

cmd:=&serpent.Command{
Use:"external-workspaces [subcommand]",
Short:"Create or manage external workspaces",
Handler:func(inv*serpent.Invocation)error {
returninv.Command.HelpHandler(inv)
},
Children: []*serpent.Command{
r.externalWorkspaceCreate(),
r.externalWorkspaceAgentInstructions(),
r.externalWorkspaceList(),
},
}

orgContext.AttachOptions(cmd)
returncmd
}
Comment on lines +25 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Is this functionality available in OSS as well?


// externalWorkspaceCreate extends `coder create` to create an external workspace.
func (r*RootCmd)externalWorkspaceCreate()*serpent.Command {
opts:=createOptions{
beforeCreate:func(ctx context.Context,client*codersdk.Client,_ codersdk.Template,templateVersionID uuid.UUID)error {
resources,err:=client.TemplateVersionResources(ctx,templateVersionID)
iferr!=nil {
returnxerrors.Errorf("get template version resources: %w",err)
}
iflen(resources)==0 {
returnxerrors.Errorf("no resources found for template version %q",templateVersionID)
}

varhasExternalAgentbool
for_,resource:=rangeresources {
ifresource.Type=="coder_external_agent" {
hasExternalAgent=true
break
}
}

if!hasExternalAgent {
returnxerrors.Errorf("template version %q does not have an external agent. Only templates with external agents can be used for external workspace creation",templateVersionID)
}

returnnil
},
afterCreate:func(ctx context.Context,inv*serpent.Invocation,client*codersdk.Client,workspace codersdk.Workspace)error {
workspace,err:=client.WorkspaceByOwnerAndName(ctx,codersdk.Me,workspace.Name, codersdk.WorkspaceOptions{})
iferr!=nil {
returnxerrors.Errorf("get workspace by name: %w",err)
}

externalAgents,err:=fetchExternalAgents(inv,client,workspace,workspace.LatestBuild.Resources)
iferr!=nil {
returnxerrors.Errorf("fetch external agents: %w",err)
}

formatted:=formatExternalAgent(workspace.Name,externalAgents)
_,err=fmt.Fprintln(inv.Stdout,formatted)
returnerr
},
}

cmd:=r.create(opts)
cmd.Use="create [workspace]"
cmd.Short="Create a new external workspace"
cmd.Middleware=serpent.Chain(
cmd.Middleware,
serpent.RequireNArgs(1),
)

fori:=rangecmd.Options {
ifcmd.Options[i].Flag=="template" {
cmd.Options[i].Required=true
}
}

returncmd
}

// externalWorkspaceAgentInstructions prints the instructions for an external agent.
func (r*RootCmd)externalWorkspaceAgentInstructions()*serpent.Command {
client:=new(codersdk.Client)
formatter:=cliui.NewOutputFormatter(
cliui.ChangeFormatterData(cliui.TextFormat(),func(dataany) (any,error) {
agent,ok:=data.(externalAgent)
if!ok {
return"",xerrors.Errorf("expected externalAgent, got %T",data)
}

returnformatExternalAgent(agent.WorkspaceName, []externalAgent{agent}),nil
}),
cliui.JSONFormat(),
)

cmd:=&serpent.Command{
Use:"agent-instructions [user/]workspace[.agent]",
Short:"Get the instructions for an external agent",
Middleware:serpent.Chain(r.InitClient(client),serpent.RequireNArgs(1)),
Handler:func(inv*serpent.Invocation)error {
workspace,workspaceAgent,_,err:=getWorkspaceAndAgent(inv.Context(),inv,client,false,inv.Args[0])
iferr!=nil {
returnxerrors.Errorf("find workspace and agent: %w",err)
}

credentials,err:=client.WorkspaceExternalAgentCredentials(inv.Context(),workspace.ID,workspaceAgent.Name)
iferr!=nil {
returnxerrors.Errorf("get external agent token for agent %q: %w",workspaceAgent.Name,err)
}

agentInfo:=externalAgent{
WorkspaceName:workspace.Name,
AgentName:workspaceAgent.Name,
AuthType:"token",
AuthToken:credentials.AgentToken,
InitScript:credentials.Command,
}

out,err:=formatter.Format(inv.Context(),agentInfo)
iferr!=nil {
returnerr
}

_,err=fmt.Fprintln(inv.Stdout,out)
returnerr
},
}

formatter.AttachOptions(&cmd.Options)
returncmd
}

func (r*RootCmd)externalWorkspaceList()*serpent.Command {
var (
filter cliui.WorkspaceFilter
formatter=cliui.NewOutputFormatter(
cliui.TableFormat(
[]workspaceListRow{},
[]string{
"workspace",
"template",
"status",
"healthy",
"last built",
"current version",
"outdated",
},
),
cliui.JSONFormat(),
)
)
client:=new(codersdk.Client)
cmd:=&serpent.Command{
Annotations:workspaceCommand,
Use:"list",
Short:"List external workspaces",
Aliases: []string{"ls"},
Middleware:serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
Handler:func(inv*serpent.Invocation)error {
baseFilter:=filter.Filter()

ifbaseFilter.FilterQuery=="" {
baseFilter.FilterQuery="has-external-agent:true"
}else {
baseFilter.FilterQuery+=" has-external-agent:true"
}

res,err:=queryConvertWorkspaces(inv.Context(),client,baseFilter,workspaceListRowFromWorkspace)
iferr!=nil {
returnerr
}

iflen(res)==0&&formatter.FormatID()!=cliui.JSONFormat().ID() {
pretty.Fprintf(inv.Stderr,cliui.DefaultStyles.Prompt,"No workspaces found! Create one:\n")
_,_=fmt.Fprintln(inv.Stderr)
_,_=fmt.Fprintln(inv.Stderr," "+pretty.Sprint(cliui.DefaultStyles.Code,"coder external-workspaces create <name>"))
_,_=fmt.Fprintln(inv.Stderr)
returnnil
}

out,err:=formatter.Format(inv.Context(),res)
iferr!=nil {
returnerr
}

_,err=fmt.Fprintln(inv.Stdout,out)
returnerr
},
}
filter.AttachOptions(&cmd.Options)
formatter.AttachOptions(&cmd.Options)
returncmd
}

// fetchExternalAgents fetches the external agents for a workspace.
funcfetchExternalAgents(inv*serpent.Invocation,client*codersdk.Client,workspace codersdk.Workspace,resources []codersdk.WorkspaceResource) ([]externalAgent,error) {
iflen(resources)==0 {
returnnil,xerrors.Errorf("no resources found for workspace")
}

varexternalAgents []externalAgent

for_,resource:=rangeresources {
ifresource.Type!="coder_external_agent"||len(resource.Agents)==0 {
continue
}

agent:=resource.Agents[0]
credentials,err:=client.WorkspaceExternalAgentCredentials(inv.Context(),workspace.ID,agent.Name)
iferr!=nil {
returnnil,xerrors.Errorf("get external agent token for agent %q: %w",agent.Name,err)
}

externalAgents=append(externalAgents,externalAgent{
AgentName:agent.Name,
AuthType:"token",
AuthToken:credentials.AgentToken,
InitScript:credentials.Command,
})
}

returnexternalAgents,nil
}

// formatExternalAgent formats the instructions for an external agent.
funcformatExternalAgent(workspaceNamestring,externalAgents []externalAgent)string {
varoutput strings.Builder
_,_=output.WriteString(fmt.Sprintf("\nPlease run the following commands to attach external agent to the workspace %s:\n\n",cliui.Keyword(workspaceName)))

fori,agent:=rangeexternalAgents {
iflen(externalAgents)>1 {
_,_=output.WriteString(fmt.Sprintf("For 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(fmt.Sprintf("%s\n",pretty.Sprint(cliui.DefaultStyles.Code,fmt.Sprintf("curl -fsSL %s | sh",agent.InitScript))))

ifi<len(externalAgents)-1 {
_,_=output.WriteString("\n")
}
}

returnoutput.String()
}
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp