feat: allow specifying devcontainer on agent in terraform (#16997)

This change allows specifying devcontainers in terraform and plumbs it
through to the agent via agent manifest.

This will be used for autostarting devcontainers in a workspace.

Depends on coder/terraform-provider-coder#368
Updates #16423
This commit is contained in:
Mathias Fredriksson
2025-03-20 19:09:39 +02:00
committed by GitHub
parent 287e3198d8
commit 69ba27e347
49 changed files with 2615 additions and 1253 deletions
+1
View File
@@ -121,6 +121,7 @@ type Manifest struct {
DisableDirectConnections bool `json:"disable_direct_connections"`
Metadata []codersdk.WorkspaceAgentMetadataDescription `json:"metadata"`
Scripts []codersdk.WorkspaceAgentScript `json:"scripts"`
Devcontainers []codersdk.WorkspaceAgentDevcontainer `json:"devcontainers"`
}
type LogSource struct {
+46
View File
@@ -31,6 +31,10 @@ func ManifestFromProto(manifest *proto.Manifest) (Manifest, error) {
if err != nil {
return Manifest{}, xerrors.Errorf("error converting workspace ID: %w", err)
}
devcontainers, err := DevcontainersFromProto(manifest.Devcontainers)
if err != nil {
return Manifest{}, xerrors.Errorf("error converting workspace agent devcontainers: %w", err)
}
return Manifest{
AgentID: agentID,
AgentName: manifest.AgentName,
@@ -48,6 +52,7 @@ func ManifestFromProto(manifest *proto.Manifest) (Manifest, error) {
MOTDFile: manifest.MotdPath,
DisableDirectConnections: manifest.DisableDirectConnections,
Metadata: MetadataDescriptionsFromProto(manifest.Metadata),
Devcontainers: devcontainers,
}, nil
}
@@ -73,6 +78,7 @@ func ProtoFromManifest(manifest Manifest) (*proto.Manifest, error) {
Scripts: ProtoFromScripts(manifest.Scripts),
Apps: apps,
Metadata: ProtoFromMetadataDescriptions(manifest.Metadata),
Devcontainers: ProtoFromDevcontainers(manifest.Devcontainers),
}, nil
}
@@ -424,3 +430,43 @@ func ProtoFromConnectionType(typ ConnectionType) (proto.Connection_Type, error)
return 0, xerrors.Errorf("unknown connection type %q", typ)
}
}
func DevcontainersFromProto(pdcs []*proto.WorkspaceAgentDevcontainer) ([]codersdk.WorkspaceAgentDevcontainer, error) {
ret := make([]codersdk.WorkspaceAgentDevcontainer, len(pdcs))
for i, pdc := range pdcs {
dc, err := DevcontainerFromProto(pdc)
if err != nil {
return nil, xerrors.Errorf("parse devcontainer %v: %w", i, err)
}
ret[i] = dc
}
return ret, nil
}
func DevcontainerFromProto(pdc *proto.WorkspaceAgentDevcontainer) (codersdk.WorkspaceAgentDevcontainer, error) {
id, err := uuid.FromBytes(pdc.Id)
if err != nil {
return codersdk.WorkspaceAgentDevcontainer{}, xerrors.Errorf("parse id: %w", err)
}
return codersdk.WorkspaceAgentDevcontainer{
ID: id,
WorkspaceFolder: pdc.WorkspaceFolder,
ConfigPath: pdc.ConfigPath,
}, nil
}
func ProtoFromDevcontainers(dcs []codersdk.WorkspaceAgentDevcontainer) []*proto.WorkspaceAgentDevcontainer {
ret := make([]*proto.WorkspaceAgentDevcontainer, len(dcs))
for i, dc := range dcs {
ret[i] = ProtoFromDevcontainer(dc)
}
return ret
}
func ProtoFromDevcontainer(dc codersdk.WorkspaceAgentDevcontainer) *proto.WorkspaceAgentDevcontainer {
return &proto.WorkspaceAgentDevcontainer{
Id: dc.ID[:],
WorkspaceFolder: dc.WorkspaceFolder,
ConfigPath: dc.ConfigPath,
}
}
+8
View File
@@ -130,6 +130,13 @@ func TestManifest(t *testing.T) {
DisplayName: "bar",
},
},
Devcontainers: []codersdk.WorkspaceAgentDevcontainer{
{
ID: uuid.New(),
WorkspaceFolder: "/home/coder/coder",
ConfigPath: "/home/coder/coder/.devcontainer/devcontainer.json",
},
},
}
p, err := agentsdk.ProtoFromManifest(manifest)
require.NoError(t, err)
@@ -152,6 +159,7 @@ func TestManifest(t *testing.T) {
require.Equal(t, manifest.DisableDirectConnections, back.DisableDirectConnections)
require.Equal(t, manifest.Metadata, back.Metadata)
require.Equal(t, manifest.Scripts, back.Scripts)
require.Equal(t, manifest.Devcontainers, back.Devcontainers)
}
func TestSubsystems(t *testing.T) {
+2
View File
@@ -35,6 +35,7 @@ const (
ResourceTemplate RBACResource = "template"
ResourceUser RBACResource = "user"
ResourceWorkspace RBACResource = "workspace"
ResourceWorkspaceAgentDevcontainers RBACResource = "workspace_agent_devcontainers"
ResourceWorkspaceAgentResourceMonitor RBACResource = "workspace_agent_resource_monitor"
ResourceWorkspaceDormant RBACResource = "workspace_dormant"
ResourceWorkspaceProxy RBACResource = "workspace_proxy"
@@ -93,6 +94,7 @@ var RBACResourceActions = map[RBACResource][]RBACAction{
ResourceTemplate: {ActionCreate, ActionDelete, ActionRead, ActionUpdate, ActionUse, ActionViewInsights},
ResourceUser: {ActionCreate, ActionDelete, ActionRead, ActionReadPersonal, ActionUpdate, ActionUpdatePersonal},
ResourceWorkspace: {ActionApplicationConnect, ActionCreate, ActionDelete, ActionRead, ActionSSH, ActionWorkspaceStart, ActionWorkspaceStop, ActionUpdate},
ResourceWorkspaceAgentDevcontainers: {ActionCreate},
ResourceWorkspaceAgentResourceMonitor: {ActionCreate, ActionRead, ActionUpdate},
ResourceWorkspaceDormant: {ActionApplicationConnect, ActionCreate, ActionDelete, ActionRead, ActionSSH, ActionWorkspaceStart, ActionWorkspaceStop, ActionUpdate},
ResourceWorkspaceProxy: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
+8
View File
@@ -392,6 +392,14 @@ func (c *Client) WorkspaceAgentListeningPorts(ctx context.Context, agentID uuid.
return listeningPorts, json.NewDecoder(res.Body).Decode(&listeningPorts)
}
// WorkspaceAgentDevcontainer defines the location of a devcontainer
// configuration in a workspace that is visible to the workspace agent.
type WorkspaceAgentDevcontainer struct {
ID uuid.UUID `json:"id" format:"uuid"`
WorkspaceFolder string `json:"workspace_folder"`
ConfigPath string `json:"config_path,omitempty"`
}
// WorkspaceAgentContainer describes a devcontainer of some sort
// that is visible to the workspace agent. This struct is an abstraction
// of potentially multiple implementations, and the fields will be