diff --git a/agent/agent.go b/agent/agent.go index 17298e7aa5772..9f105ee296f5c 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -1080,6 +1080,18 @@ func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context, if manifest.AgentID == uuid.Nil { return xerrors.New("nil agentID returned by manifest") } + if manifest.ParentID != uuid.Nil { + // This is a sub agent, disable all the features that should not + // be used by sub agents. + a.logger.Debug(ctx, "sub agent detected, disabling features", + slog.F("parent_id", manifest.ParentID), + slog.F("agent_id", manifest.AgentID), + ) + if a.experimentalDevcontainersEnabled { + a.logger.Info(ctx, "devcontainers are not supported on sub agents, disabling feature") + a.experimentalDevcontainersEnabled = false + } + } a.client.RewriteDERPMap(manifest.DERPMap) // Expand the directory and send it back to coderd so external diff --git a/agent/agent_test.go b/agent/agent_test.go index 3ef9e4f4c75ba..9a8073a289b5f 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -2423,6 +2423,34 @@ waitForOutcomeLoop: }(container) } +func TestAgent_DevcontainersDisabledForSubAgent(t *testing.T) { + t.Parallel() + + // Create a manifest with a ParentID to make this a sub agent. + manifest := agentsdk.Manifest{ + AgentID: uuid.New(), + ParentID: uuid.New(), + } + + // Setup the agent with devcontainers enabled initially. + //nolint:dogsled + conn, _, _, _, _ := setupAgent(t, manifest, 0, func(_ *agenttest.Client, o *agent.Options) { + o.ExperimentalDevcontainersEnabled = true + }) + + // Query the containers API endpoint. This should fail because + // devcontainers have been disabled for the sub agent. + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium) + defer cancel() + + _, err := conn.ListContainers(ctx) + require.Error(t, err) + + // Verify the error message contains the expected text. + require.Contains(t, err.Error(), "The agent dev containers feature is experimental and not enabled by default.") + require.Contains(t, err.Error(), "To enable this feature, set CODER_AGENT_DEVCONTAINERS_ENABLE=true in your template.") +} + func TestAgent_Dial(t *testing.T) { t.Parallel() diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index e3b036dcdf00a..f44c19b998e21 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -102,6 +102,7 @@ type PostMetadataRequest struct { type PostMetadataRequestDeprecated = codersdk.WorkspaceAgentMetadataResult type Manifest struct { + ParentID uuid.UUID `json:"parent_id"` AgentID uuid.UUID `json:"agent_id"` AgentName string `json:"agent_name"` // OwnerUsername and WorkspaceID are used by an open-source user to identify the workspace. diff --git a/codersdk/agentsdk/convert.go b/codersdk/agentsdk/convert.go index 2b7dff950a3e7..d01c9e527fce9 100644 --- a/codersdk/agentsdk/convert.go +++ b/codersdk/agentsdk/convert.go @@ -15,6 +15,14 @@ import ( ) func ManifestFromProto(manifest *proto.Manifest) (Manifest, error) { + parentID := uuid.Nil + if pid := manifest.GetParentId(); pid != nil { + var err error + parentID, err = uuid.FromBytes(pid) + if err != nil { + return Manifest{}, xerrors.Errorf("error converting workspace agent parent ID: %w", err) + } + } apps, err := AppsFromProto(manifest.Apps) if err != nil { return Manifest{}, xerrors.Errorf("error converting workspace agent apps: %w", err) @@ -36,6 +44,7 @@ func ManifestFromProto(manifest *proto.Manifest) (Manifest, error) { return Manifest{}, xerrors.Errorf("error converting workspace agent devcontainers: %w", err) } return Manifest{ + ParentID: parentID, AgentID: agentID, AgentName: manifest.AgentName, OwnerName: manifest.OwnerUsername, @@ -62,6 +71,7 @@ func ProtoFromManifest(manifest Manifest) (*proto.Manifest, error) { return nil, xerrors.Errorf("convert workspace apps: %w", err) } return &proto.Manifest{ + ParentId: manifest.ParentID[:], AgentId: manifest.AgentID[:], AgentName: manifest.AgentName, OwnerUsername: manifest.OwnerName, diff --git a/codersdk/agentsdk/convert_test.go b/codersdk/agentsdk/convert_test.go index 09482b1694910..f324d504b838a 100644 --- a/codersdk/agentsdk/convert_test.go +++ b/codersdk/agentsdk/convert_test.go @@ -19,6 +19,7 @@ import ( func TestManifest(t *testing.T) { t.Parallel() manifest := agentsdk.Manifest{ + ParentID: uuid.New(), AgentID: uuid.New(), AgentName: "test-agent", OwnerName: "test-owner", @@ -142,6 +143,7 @@ func TestManifest(t *testing.T) { require.NoError(t, err) back, err := agentsdk.ManifestFromProto(p) require.NoError(t, err) + require.Equal(t, manifest.ParentID, back.ParentID) require.Equal(t, manifest.AgentID, back.AgentID) require.Equal(t, manifest.AgentName, back.AgentName) require.Equal(t, manifest.OwnerName, back.OwnerName)