Add API for creating cohorts

This commit is contained in:
Kyle Carberry 2024-05-01 15:26:42 +00:00
parent 852ba0a9dc
commit 5ccb8c6fd3
23 changed files with 1025 additions and 103 deletions

201
coderd/apidoc/docs.go generated
View File

@ -2157,6 +2157,119 @@ const docTemplate = `{
}
}
},
"/organizations/{organization}/intel/cohorts": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Intel"
],
"summary": "Create intel cohort",
"operationId": "create-intel-cohort",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID",
"name": "organization",
"in": "path",
"required": true
},
{
"description": "Create intel cohort request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.CreateIntelCohortRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/codersdk.IntelCohort"
}
}
}
}
},
"/organizations/{organization}/intel/serve": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Intel"
],
"summary": "Serve intel daemon",
"operationId": "serve-intel-daemon",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID",
"name": "organization",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Instance ID",
"name": "instance_id",
"in": "query",
"required": true
},
{
"type": "integer",
"description": "Number of CPU cores",
"name": "cpu_cores",
"in": "query"
},
{
"type": "integer",
"description": "Total memory in MB",
"name": "memory_total_mb",
"in": "query"
},
{
"type": "string",
"description": "Hostname",
"name": "hostname",
"in": "query"
},
{
"type": "string",
"description": "Operating system",
"name": "operating_system",
"in": "query"
},
{
"type": "string",
"description": "Operating system version",
"name": "operating_system_version",
"in": "query"
},
{
"type": "string",
"description": "Architecture",
"name": "architecture",
"in": "query"
}
],
"responses": {
"101": {
"description": "Switching Protocols"
}
}
}
},
"/organizations/{organization}/members/roles": {
"get": {
"security": [
@ -8729,6 +8842,32 @@ const docTemplate = `{
}
}
},
"codersdk.CreateIntelCohortRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"description": {
"type": "string"
},
"icon": {
"type": "string"
},
"name": {
"type": "string"
},
"regex_filters": {
"$ref": "#/definitions/codersdk.IntelCohortRegexFilters"
},
"tracked_executables": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"codersdk.CreateOrganizationRequest": {
"type": "object",
"required": [
@ -9894,6 +10033,68 @@ const docTemplate = `{
"InsightsReportIntervalWeek"
]
},
"codersdk.IntelCohort": {
"type": "object",
"properties": {
"created_at": {
"type": "string",
"format": "date-time"
},
"created_by": {
"type": "string"
},
"description": {
"type": "string"
},
"icon": {
"type": "string"
},
"id": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string"
},
"organization_id": {
"type": "string",
"format": "uuid"
},
"regex_filters": {
"$ref": "#/definitions/codersdk.IntelCohortRegexFilters"
},
"tracked_executables": {
"type": "array",
"items": {
"type": "string"
}
},
"updated_at": {
"type": "string",
"format": "date-time"
}
}
},
"codersdk.IntelCohortRegexFilters": {
"type": "object",
"properties": {
"architecture": {
"type": "string"
},
"instance_id": {
"type": "string"
},
"operating_system": {
"type": "string"
},
"operating_system_platform": {
"type": "string"
},
"operating_system_version": {
"type": "string"
}
}
},
"codersdk.IntelMachine": {
"type": "object",
"properties": {

View File

@ -1888,6 +1888,115 @@
}
}
},
"/organizations/{organization}/intel/cohorts": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Intel"],
"summary": "Create intel cohort",
"operationId": "create-intel-cohort",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID",
"name": "organization",
"in": "path",
"required": true
},
{
"description": "Create intel cohort request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.CreateIntelCohortRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/codersdk.IntelCohort"
}
}
}
}
},
"/organizations/{organization}/intel/serve": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Intel"],
"summary": "Serve intel daemon",
"operationId": "serve-intel-daemon",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID",
"name": "organization",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Instance ID",
"name": "instance_id",
"in": "query",
"required": true
},
{
"type": "integer",
"description": "Number of CPU cores",
"name": "cpu_cores",
"in": "query"
},
{
"type": "integer",
"description": "Total memory in MB",
"name": "memory_total_mb",
"in": "query"
},
{
"type": "string",
"description": "Hostname",
"name": "hostname",
"in": "query"
},
{
"type": "string",
"description": "Operating system",
"name": "operating_system",
"in": "query"
},
{
"type": "string",
"description": "Operating system version",
"name": "operating_system_version",
"in": "query"
},
{
"type": "string",
"description": "Architecture",
"name": "architecture",
"in": "query"
}
],
"responses": {
"101": {
"description": "Switching Protocols"
}
}
}
},
"/organizations/{organization}/members/roles": {
"get": {
"security": [
@ -7776,6 +7885,30 @@
}
}
},
"codersdk.CreateIntelCohortRequest": {
"type": "object",
"required": ["name"],
"properties": {
"description": {
"type": "string"
},
"icon": {
"type": "string"
},
"name": {
"type": "string"
},
"regex_filters": {
"$ref": "#/definitions/codersdk.IntelCohortRegexFilters"
},
"tracked_executables": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"codersdk.CreateOrganizationRequest": {
"type": "object",
"required": ["name"],
@ -8882,6 +9015,68 @@
"InsightsReportIntervalWeek"
]
},
"codersdk.IntelCohort": {
"type": "object",
"properties": {
"created_at": {
"type": "string",
"format": "date-time"
},
"created_by": {
"type": "string"
},
"description": {
"type": "string"
},
"icon": {
"type": "string"
},
"id": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string"
},
"organization_id": {
"type": "string",
"format": "uuid"
},
"regex_filters": {
"$ref": "#/definitions/codersdk.IntelCohortRegexFilters"
},
"tracked_executables": {
"type": "array",
"items": {
"type": "string"
}
},
"updated_at": {
"type": "string",
"format": "date-time"
}
}
},
"codersdk.IntelCohortRegexFilters": {
"type": "object",
"properties": {
"architecture": {
"type": "string"
},
"instance_id": {
"type": "string"
},
"operating_system": {
"type": "string"
},
"operating_system_platform": {
"type": "string"
},
"operating_system_version": {
"type": "string"
}
}
},
"codersdk.IntelMachine": {
"type": "object",
"properties": {

View File

@ -825,7 +825,18 @@ func New(options *Options) *API {
r.Route("/intel", func(r chi.Router) {
r.Get("/serve", api.intelDaemonServe)
r.Get("/machines", api.intelMachines)
r.Get("/cohorts", api.intelCohorts)
r.Route("/cohorts", func(r chi.Router) {
r.Get("/", api.intelCohorts)
r.Post("/", api.postIntelCohorts)
r.Route("/{cohort}", func(r chi.Router) {
r.Use(
httpmw.ExtractIntelCohortParam(options.Database),
)
r.Patch("/", api.patchIntelCohort)
r.Delete("/", api.deleteIntelCohort)
})
})
r.Get("/report", api.intelReport)
})
})

View File

@ -1167,8 +1167,9 @@ func (q *querier) GetHungProvisionerJobs(ctx context.Context, hungSince time.Tim
return q.db.GetHungProvisionerJobs(ctx, hungSince)
}
func (q *querier) GetIntelCohortsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]database.IntelCohort, error) {
panic("not implemented")
func (q *querier) GetIntelCohortsByOrganizationID(ctx context.Context, req database.GetIntelCohortsByOrganizationIDParams) ([]database.IntelCohort, error) {
// TODO: Do this
return q.db.GetIntelCohortsByOrganizationID(ctx, req)
}
func (q *querier) GetIntelCohortsMatchedByMachineIDs(ctx context.Context, ids []uuid.UUID) ([]database.GetIntelCohortsMatchedByMachineIDsRow, error) {
@ -1182,12 +1183,14 @@ func (q *querier) GetIntelMachinesMatchingFilters(ctx context.Context, arg datab
return q.db.GetIntelMachinesMatchingFilters(ctx, arg)
}
func (q *querier) GetIntelReportCommands(ctx context.Context, startsAt database.GetIntelReportCommandsParams) ([]database.GetIntelReportCommandsRow, error) {
panic("not implemented")
func (q *querier) GetIntelReportCommands(ctx context.Context, arg database.GetIntelReportCommandsParams) ([]database.GetIntelReportCommandsRow, error) {
// No authz checks possible. It's too weird
return q.db.GetIntelReportCommands(ctx, arg)
}
func (q *querier) GetIntelReportGitRemotes(ctx context.Context, startsAt database.GetIntelReportGitRemotesParams) ([]database.GetIntelReportGitRemotesRow, error) {
panic("not implemented")
func (q *querier) GetIntelReportGitRemotes(ctx context.Context, arg database.GetIntelReportGitRemotesParams) ([]database.GetIntelReportGitRemotesRow, error) {
// No authz checks possible. It's too weird
return q.db.GetIntelReportGitRemotes(ctx, arg)
}
func (q *querier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) {
@ -3367,7 +3370,10 @@ func (q *querier) UpsertIntelCohort(ctx context.Context, arg database.UpsertInte
}
func (q *querier) UpsertIntelInvocationSummaries(ctx context.Context) error {
panic("not implemented")
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
return err
}
return q.db.UpsertIntelInvocationSummaries(ctx)
}
func (q *querier) UpsertIntelMachine(ctx context.Context, arg database.UpsertIntelMachineParams) (database.IntelMachine, error) {

View File

@ -590,7 +590,6 @@ func IntelCohort(t testing.TB, db database.Store, orig database.IntelCohort) dat
Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)),
OrganizationID: takeFirst(orig.OrganizationID, uuid.New()),
CreatedBy: takeFirst(orig.CreatedBy, uuid.New()),
DisplayName: takeFirst(orig.DisplayName, namesgenerator.GetRandomName(1)),
Icon: takeFirst(orig.Icon, ""),
Description: takeFirst(orig.Description, ""),
RegexOperatingSystem: takeFirst(orig.RegexOperatingSystem, ".*"),

View File

@ -2387,8 +2387,34 @@ func (q *FakeQuerier) GetHungProvisionerJobs(_ context.Context, hungSince time.T
return hungJobs, nil
}
func (q *FakeQuerier) GetIntelCohortsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]database.IntelCohort, error) {
panic("not implemented")
func (q *FakeQuerier) GetIntelCohortsByOrganizationID(_ context.Context, req database.GetIntelCohortsByOrganizationIDParams) ([]database.IntelCohort, error) {
err := validateDatabaseType(req)
if err != nil {
return nil, err
}
q.mutex.RLock()
defer q.mutex.RUnlock()
cohorts := make([]database.IntelCohort, 0)
for _, cohort := range q.intelCohorts {
if cohort.OrganizationID != req.OrganizationID {
continue
}
if req.Name == nil {
cohorts = append(cohorts, cohort)
continue
}
name, ok := req.Name.(string)
if !ok {
return nil, xerrors.Errorf("invalid type for name: %T", req.Name)
}
if cohort.Name != name {
continue
}
cohorts = append(cohorts, cohort)
}
return cohorts, nil
}
func (q *FakeQuerier) GetIntelCohortsMatchedByMachineIDs(_ context.Context, ids []uuid.UUID) ([]database.GetIntelCohortsMatchedByMachineIDsRow, error) {
@ -8501,7 +8527,6 @@ func (q *FakeQuerier) UpsertIntelCohort(_ context.Context, arg database.UpsertIn
continue
}
cohort.UpdatedAt = arg.UpdatedAt
cohort.DisplayName = arg.DisplayName
cohort.Description = arg.Description
cohort.RegexOperatingSystem = arg.RegexOperatingSystem
cohort.RegexOperatingSystemPlatform = arg.RegexOperatingSystemPlatform
@ -8522,7 +8547,6 @@ func (q *FakeQuerier) UpsertIntelCohort(_ context.Context, arg database.UpsertIn
CreatedBy: arg.CreatedBy,
CreatedAt: arg.CreatedAt,
UpdatedAt: arg.UpdatedAt,
DisplayName: arg.DisplayName,
Description: arg.Description,
RegexOperatingSystem: arg.RegexOperatingSystem,
RegexOperatingSystemPlatform: arg.RegexOperatingSystemPlatform,
@ -8667,14 +8691,10 @@ func (q *FakeQuerier) UpsertIntelInvocationSummaries(_ context.Context) error {
}
summary.MedianDurationMs = medianFloat64s(wrapperSummary.DurationMS)
q.intelInvocationSummaries = append(q.intelInvocationSummaries, summary)
// Remove invocations that have been compressed
for id := range wrapperSummary.InvocationIDs {
q.intelInvocations = slices.DeleteFunc(q.intelInvocations, func(invocation database.IntelInvocation) bool {
return invocation.ID == id
})
}
}
// Delete all invocations because they should have been processed!
q.intelInvocations = make([]database.IntelInvocation, 0)
return nil
}

View File

@ -597,7 +597,7 @@ func (m metricsStore) GetHungProvisionerJobs(ctx context.Context, hungSince time
return jobs, err
}
func (m metricsStore) GetIntelCohortsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]database.IntelCohort, error) {
func (m metricsStore) GetIntelCohortsByOrganizationID(ctx context.Context, organizationID database.GetIntelCohortsByOrganizationIDParams) ([]database.IntelCohort, error) {
start := time.Now()
r0, r1 := m.s.GetIntelCohortsByOrganizationID(ctx, organizationID)
m.queryLatencies.WithLabelValues("GetIntelCohortsByOrganizationID").Observe(time.Since(start).Seconds())

View File

@ -1170,7 +1170,7 @@ func (mr *MockStoreMockRecorder) GetHungProvisionerJobs(arg0, arg1 any) *gomock.
}
// GetIntelCohortsByOrganizationID mocks base method.
func (m *MockStore) GetIntelCohortsByOrganizationID(arg0 context.Context, arg1 uuid.UUID) ([]database.IntelCohort, error) {
func (m *MockStore) GetIntelCohortsByOrganizationID(arg0 context.Context, arg1 database.GetIntelCohortsByOrganizationIDParams) ([]database.IntelCohort, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetIntelCohortsByOrganizationID", arg0, arg1)
ret0, _ := ret[0].([]database.IntelCohort)

View File

@ -487,7 +487,6 @@ CREATE TABLE intel_cohorts (
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
name text NOT NULL,
display_name text NOT NULL,
icon character varying(256) DEFAULT ''::character varying NOT NULL,
description text NOT NULL,
regex_operating_system character varying(255) DEFAULT '.*'::character varying NOT NULL,
@ -1504,6 +1503,9 @@ ALTER TABLE ONLY groups
ALTER TABLE ONLY groups
ADD CONSTRAINT groups_pkey PRIMARY KEY (id);
ALTER TABLE ONLY intel_cohorts
ADD CONSTRAINT intel_cohorts_organization_id_name_key UNIQUE (organization_id, name);
ALTER TABLE ONLY intel_cohorts
ADD CONSTRAINT intel_cohorts_pkey PRIMARY KEY (id);

View File

@ -5,7 +5,6 @@ CREATE TABLE intel_cohorts (
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
name TEXT NOT NULL,
display_name TEXT NOT NULL,
icon character varying(256) DEFAULT ''::character varying NOT NULL,
description TEXT NOT NULL,
@ -14,8 +13,9 @@ CREATE TABLE intel_cohorts (
regex_operating_system_version VARCHAR(255) NOT NULL DEFAULT '.*',
regex_architecture VARCHAR(255) NOT NULL DEFAULT '.*',
regex_instance_id VARCHAR(255) NOT NULL DEFAULT '.*',
tracked_executables TEXT[] NOT NULL,
tracked_executables TEXT[] NOT NULL
UNIQUE(organization_id, name)
);
CREATE INDEX idx_intel_cohorts_id ON intel_cohorts (id);

View File

@ -1853,7 +1853,6 @@ type IntelCohort struct {
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Name string `db:"name" json:"name"`
DisplayName string `db:"display_name" json:"display_name"`
Icon string `db:"icon" json:"icon"`
Description string `db:"description" json:"description"`
RegexOperatingSystem string `db:"regex_operating_system" json:"regex_operating_system"`

View File

@ -128,7 +128,7 @@ type sqlcQuerier interface {
GetGroupsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]Group, error)
GetHealthSettings(ctx context.Context) (string, error)
GetHungProvisionerJobs(ctx context.Context, updatedAt time.Time) ([]ProvisionerJob, error)
GetIntelCohortsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]IntelCohort, error)
GetIntelCohortsByOrganizationID(ctx context.Context, arg GetIntelCohortsByOrganizationIDParams) ([]IntelCohort, error)
// Obtains a list of cohorts that a user can track invocations for.
GetIntelCohortsMatchedByMachineIDs(ctx context.Context, ids []uuid.UUID) ([]GetIntelCohortsMatchedByMachineIDsRow, error)
GetIntelMachinesMatchingFilters(ctx context.Context, arg GetIntelMachinesMatchingFiltersParams) ([]GetIntelMachinesMatchingFiltersRow, error)
@ -430,6 +430,9 @@ type sqlcQuerier interface {
UpsertDefaultProxy(ctx context.Context, arg UpsertDefaultProxyParams) error
UpsertHealthSettings(ctx context.Context, value string) error
UpsertIntelCohort(ctx context.Context, arg UpsertIntelCohortParams) (IntelCohort, error)
// Delete all invocations after summarizing.
// If there are invocations that are not in a cohort,
// they must be purged!
UpsertIntelInvocationSummaries(ctx context.Context) error
UpsertIntelMachine(ctx context.Context, arg UpsertIntelMachineParams) (IntelMachine, error)
UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error

View File

@ -2926,11 +2926,16 @@ func (q *sqlQuerier) DeleteIntelCohortsByIDs(ctx context.Context, cohortIds []uu
}
const getIntelCohortsByOrganizationID = `-- name: GetIntelCohortsByOrganizationID :many
SELECT id, organization_id, created_by, created_at, updated_at, name, display_name, icon, description, regex_operating_system, regex_operating_system_platform, regex_operating_system_version, regex_architecture, regex_instance_id, tracked_executables FROM intel_cohorts WHERE organization_id = $1
SELECT id, organization_id, created_by, created_at, updated_at, name, icon, description, regex_operating_system, regex_operating_system_platform, regex_operating_system_version, regex_architecture, regex_instance_id, tracked_executables FROM intel_cohorts WHERE organization_id = $1 AND ($2 IS NULL OR name = $2)
`
func (q *sqlQuerier) GetIntelCohortsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]IntelCohort, error) {
rows, err := q.db.QueryContext(ctx, getIntelCohortsByOrganizationID, organizationID)
type GetIntelCohortsByOrganizationIDParams struct {
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
Name interface{} `db:"name" json:"name"`
}
func (q *sqlQuerier) GetIntelCohortsByOrganizationID(ctx context.Context, arg GetIntelCohortsByOrganizationIDParams) ([]IntelCohort, error) {
rows, err := q.db.QueryContext(ctx, getIntelCohortsByOrganizationID, arg.OrganizationID, arg.Name)
if err != nil {
return nil, err
}
@ -2945,7 +2950,6 @@ func (q *sqlQuerier) GetIntelCohortsByOrganizationID(ctx context.Context, organi
&i.CreatedAt,
&i.UpdatedAt,
&i.Name,
&i.DisplayName,
&i.Icon,
&i.Description,
&i.RegexOperatingSystem,
@ -3106,7 +3110,8 @@ SELECT
binary_args,
SUM(total_invocations) AS total_invocations,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY median_duration_ms) AS median_duration_ms,
-- We have to convert to text here because Go cannot scan
-- an array of jsonb.
array_agg(working_directories):: text [] AS aggregated_working_directories,
array_agg(binary_paths):: text [] AS aggregated_binary_paths,
array_agg(git_remote_urls):: text [] AS aggregated_git_remote_urls,
@ -3302,21 +3307,20 @@ func (q *sqlQuerier) InsertIntelInvocations(ctx context.Context, arg InsertIntel
}
const upsertIntelCohort = `-- name: UpsertIntelCohort :one
INSERT INTO intel_cohorts (id, organization_id, created_by, created_at, updated_at, name, display_name, icon, description, regex_operating_system, regex_operating_system_platform, regex_operating_system_version, regex_architecture, regex_instance_id, tracked_executables)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
INSERT INTO intel_cohorts (id, organization_id, created_by, created_at, updated_at, name, icon, description, regex_operating_system, regex_operating_system_platform, regex_operating_system_version, regex_architecture, regex_instance_id, tracked_executables)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
ON CONFLICT (id) DO UPDATE SET
updated_at = $5,
name = $6,
display_name = $7,
icon = $8,
description = $9,
regex_operating_system = $10,
regex_operating_system_platform = $11,
regex_operating_system_version = $12,
regex_architecture = $13,
regex_instance_id = $14,
tracked_executables = $15
RETURNING id, organization_id, created_by, created_at, updated_at, name, display_name, icon, description, regex_operating_system, regex_operating_system_platform, regex_operating_system_version, regex_architecture, regex_instance_id, tracked_executables
icon = $7,
description = $8,
regex_operating_system = $9,
regex_operating_system_platform = $10,
regex_operating_system_version = $11,
regex_architecture = $12,
regex_instance_id = $13,
tracked_executables = $14
RETURNING id, organization_id, created_by, created_at, updated_at, name, icon, description, regex_operating_system, regex_operating_system_platform, regex_operating_system_version, regex_architecture, regex_instance_id, tracked_executables
`
type UpsertIntelCohortParams struct {
@ -3326,7 +3330,6 @@ type UpsertIntelCohortParams struct {
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Name string `db:"name" json:"name"`
DisplayName string `db:"display_name" json:"display_name"`
Icon string `db:"icon" json:"icon"`
Description string `db:"description" json:"description"`
RegexOperatingSystem string `db:"regex_operating_system" json:"regex_operating_system"`
@ -3345,7 +3348,6 @@ func (q *sqlQuerier) UpsertIntelCohort(ctx context.Context, arg UpsertIntelCohor
arg.CreatedAt,
arg.UpdatedAt,
arg.Name,
arg.DisplayName,
arg.Icon,
arg.Description,
arg.RegexOperatingSystem,
@ -3363,7 +3365,6 @@ func (q *sqlQuerier) UpsertIntelCohort(ctx context.Context, arg UpsertIntelCohor
&i.CreatedAt,
&i.UpdatedAt,
&i.Name,
&i.DisplayName,
&i.Icon,
&i.Description,
&i.RegexOperatingSystem,
@ -3486,9 +3487,11 @@ saved AS (
FROM aggregated
)
DELETE FROM intel_invocations
WHERE id IN (SELECT id FROM invocations_with_cohorts)
`
// Delete all invocations after summarizing.
// If there are invocations that are not in a cohort,
// they must be purged!
func (q *sqlQuerier) UpsertIntelInvocationSummaries(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, upsertIntelInvocationSummaries)
return err

View File

@ -1,22 +1,21 @@
-- name: UpsertIntelCohort :one
INSERT INTO intel_cohorts (id, organization_id, created_by, created_at, updated_at, name, display_name, icon, description, regex_operating_system, regex_operating_system_platform, regex_operating_system_version, regex_architecture, regex_instance_id, tracked_executables)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
INSERT INTO intel_cohorts (id, organization_id, created_by, created_at, updated_at, name, icon, description, regex_operating_system, regex_operating_system_platform, regex_operating_system_version, regex_architecture, regex_instance_id, tracked_executables)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
ON CONFLICT (id) DO UPDATE SET
updated_at = $5,
name = $6,
display_name = $7,
icon = $8,
description = $9,
regex_operating_system = $10,
regex_operating_system_platform = $11,
regex_operating_system_version = $12,
regex_architecture = $13,
regex_instance_id = $14,
tracked_executables = $15
icon = $7,
description = $8,
regex_operating_system = $9,
regex_operating_system_platform = $10,
regex_operating_system_version = $11,
regex_architecture = $12,
regex_instance_id = $13,
tracked_executables = $14
RETURNING *;
-- name: GetIntelCohortsByOrganizationID :many
SELECT * FROM intel_cohorts WHERE organization_id = $1;
SELECT * FROM intel_cohorts WHERE organization_id = $1 AND (@name IS NULL OR name = @name);
-- name: DeleteIntelCohortsByIDs :exec
DELETE FROM intel_cohorts WHERE id = ANY(@cohort_ids::uuid[]);
@ -201,8 +200,10 @@ saved AS (
median_duration_ms
FROM aggregated
)
DELETE FROM intel_invocations
WHERE id IN (SELECT id FROM invocations_with_cohorts);
-- Delete all invocations after summarizing.
-- If there are invocations that are not in a cohort,
-- they must be purged!
DELETE FROM intel_invocations;
-- name: GetIntelReportGitRemotes :many
-- Get the total amount of time spent invoking commands

View File

@ -19,6 +19,7 @@ const (
UniqueGroupMembersUserIDGroupIDKey UniqueConstraint = "group_members_user_id_group_id_key" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_group_id_key UNIQUE (user_id, group_id);
UniqueGroupsNameOrganizationIDKey UniqueConstraint = "groups_name_organization_id_key" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_name_organization_id_key UNIQUE (name, organization_id);
UniqueGroupsPkey UniqueConstraint = "groups_pkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_pkey PRIMARY KEY (id);
UniqueIntelCohortsOrganizationIDNameKey UniqueConstraint = "intel_cohorts_organization_id_name_key" // ALTER TABLE ONLY intel_cohorts ADD CONSTRAINT intel_cohorts_organization_id_name_key UNIQUE (organization_id, name);
UniqueIntelCohortsPkey UniqueConstraint = "intel_cohorts_pkey" // ALTER TABLE ONLY intel_cohorts ADD CONSTRAINT intel_cohorts_pkey PRIMARY KEY (id);
UniqueIntelMachinesPkey UniqueConstraint = "intel_machines_pkey" // ALTER TABLE ONLY intel_machines ADD CONSTRAINT intel_machines_pkey PRIMARY KEY (id);
UniqueIntelMachinesUserIDInstanceIDKey UniqueConstraint = "intel_machines_user_id_instance_id_key" // ALTER TABLE ONLY intel_machines ADD CONSTRAINT intel_machines_user_id_instance_id_key UNIQUE (user_id, instance_id);

View File

@ -0,0 +1,57 @@
package httpmw
import (
"context"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/codersdk"
)
type (
intelCohortParamContextKey struct{}
)
// IntelCohortParam returns the cohort from the ExtractIntelCohortParam handler.
func IntelCohortParam(r *http.Request) database.IntelCohort {
cohort, ok := r.Context().Value(intelCohortParamContextKey{}).(database.IntelCohort)
if !ok {
panic("developer error: intel cohort param middleware not provided")
}
return cohort
}
// ExtractIntelCohortParam grabs a cohort from the "cohort" URL parameter.
func ExtractIntelCohortParam(db database.Store) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
arg := chi.URLParam(r, "cohort")
if arg == "" {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "\"cohort\" must be provided.",
})
return
}
organization := OrganizationParam(r)
cohorts, err := db.GetIntelCohortsByOrganizationID(ctx, database.GetIntelCohortsByOrganizationIDParams{
OrganizationID: organization.ID,
Name: arg,
})
if err != nil || len(cohorts) == 0 {
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
Message: "cohort not found",
})
return
}
ctx = context.WithValue(ctx, intelCohortParamContextKey{}, cohorts[0])
next.ServeHTTP(rw, r.WithContext(ctx))
})
}
}

View File

@ -0,0 +1,69 @@
package httpmw_test
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/database/dbmem"
"github.com/coder/coder/v2/coderd/httpmw"
)
func TestIntelCohortParam(t *testing.T) {
t.Parallel()
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
var (
db = dbmem.New()
rw = httptest.NewRecorder()
r = httptest.NewRequest("GET", "/", nil)
rtr = chi.NewRouter()
)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, chi.NewRouteContext()))
organization := dbgen.Organization(t, db, database.Organization{})
chi.RouteContext(r.Context()).URLParams.Add("organization", organization.ID.String())
chi.RouteContext(r.Context()).URLParams.Add("cohort", "not-found")
rtr.Use(
httpmw.ExtractOrganizationParam(db),
httpmw.ExtractIntelCohortParam(db),
)
rtr.Get("/", nil)
rtr.ServeHTTP(rw, r)
res := rw.Result()
defer res.Body.Close()
require.Equal(t, http.StatusNotFound, res.StatusCode)
})
t.Run("Found", func(t *testing.T) {
t.Parallel()
var (
db = dbmem.New()
rw = httptest.NewRecorder()
r = httptest.NewRequest("GET", "/", nil)
rtr = chi.NewRouter()
)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, chi.NewRouteContext()))
organization := dbgen.Organization(t, db, database.Organization{})
cohort := dbgen.IntelCohort(t, db, database.IntelCohort{OrganizationID: organization.ID})
chi.RouteContext(r.Context()).URLParams.Add("organization", organization.ID.String())
chi.RouteContext(r.Context()).URLParams.Add("cohort", cohort.Name)
rtr.Use(
httpmw.ExtractOrganizationParam(db),
httpmw.ExtractIntelCohortParam(db),
)
rtr.Get("/", func(w http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusOK)
got := httpmw.IntelCohortParam(r)
require.Equal(t, cohort.ID, got.ID)
})
rtr.ServeHTTP(rw, r)
res := rw.Result()
defer res.Body.Close()
require.Equal(t, http.StatusOK, res.StatusCode)
})
}

View File

@ -3,6 +3,7 @@ package coderd
import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
@ -46,14 +47,18 @@ func (api *API) intelReport(rw http.ResponseWriter, r *http.Request) {
}
req.CohortIDs = append(req.CohortIDs, cohortID)
}
var err error
req.StartsAt, err = time.Parse(q.Get("starts_at"), time.DateOnly)
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid starts_at.",
Detail: err.Error(),
})
return
// Default to the beginning of time?
rawStartsAt := q.Get("starts_at")
if rawStartsAt != "" {
var err error
req.StartsAt, err = time.Parse(q.Get("starts_at"), time.DateOnly)
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid starts_at.",
Detail: err.Error(),
})
return
}
}
var eg errgroup.Group
@ -185,7 +190,7 @@ func (api *API) intelReport(rw http.ResponseWriter, r *http.Request) {
}
return nil
})
err = eg.Wait()
err := eg.Wait()
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error getting intel report.",
@ -262,7 +267,9 @@ func (api *API) intelCohorts(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
organization := httpmw.OrganizationParam(r)
cohorts, err := api.Database.GetIntelCohortsByOrganizationID(ctx, organization.ID)
cohorts, err := api.Database.GetIntelCohortsByOrganizationID(ctx, database.GetIntelCohortsByOrganizationIDParams{
OrganizationID: organization.ID,
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error getting intel cohorts.",
@ -270,10 +277,97 @@ func (api *API) intelCohorts(rw http.ResponseWriter, r *http.Request) {
})
return
}
_ = cohorts
httpapi.Write(ctx, rw, http.StatusOK, convertIntelCohorts(cohorts))
}
// postIntelCohorts creates a new intel cohort.
//
// @Summary Create intel cohort
// @ID create-intel-cohort
// @Security CoderSessionToken
// @Tags Intel
// @Param organization path string true "Organization ID" format(uuid)
// @Param request body codersdk.CreateIntelCohortRequest true "Create intel cohort request"
// @Success 201 {object} codersdk.IntelCohort
// @Router /organizations/{organization}/intel/cohorts [post]
func (api *API) postIntelCohorts(rw http.ResponseWriter, r *http.Request) {
organization := httpmw.OrganizationParam(r)
apiKey := httpmw.APIKey(r)
ctx := r.Context()
var req codersdk.CreateIntelCohortRequest
if !httpapi.Read(ctx, rw, r, &req) {
return
}
if req.RegexFilters == nil {
req.RegexFilters = &codersdk.IntelCohortRegexFilters{}
}
if req.TrackedExecutables == nil {
req.TrackedExecutables = []string{}
}
// Ensure defaults to match any exist!
req.RegexFilters.Normalize()
cohort, err := api.Database.UpsertIntelCohort(ctx, database.UpsertIntelCohortParams{
ID: uuid.New(),
OrganizationID: organization.ID,
CreatedBy: apiKey.UserID,
CreatedAt: dbtime.Now(),
UpdatedAt: dbtime.Now(),
Name: req.Name,
Icon: req.Icon,
Description: req.Description,
TrackedExecutables: req.TrackedExecutables,
RegexOperatingSystem: req.RegexFilters.OperatingSystem,
RegexOperatingSystemPlatform: req.RegexFilters.OperatingSystemPlatform,
RegexOperatingSystemVersion: req.RegexFilters.OperatingSystemVersion,
RegexArchitecture: req.RegexFilters.Architecture,
RegexInstanceID: req.RegexFilters.InstanceID,
})
if err != nil {
if database.IsUniqueViolation(err, database.UniqueIntelCohortsOrganizationIDNameKey) {
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
Message: fmt.Sprintf("A cohort with name %q already exists.", req.Name),
Validations: []codersdk.ValidationError{{
Field: "name",
Detail: "This value is already in use and should be unique.",
}},
})
return
}
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error creating intel cohort.",
Detail: err.Error(),
})
return
}
httpapi.Write(ctx, rw, http.StatusCreated, convertIntelCohorts([]database.IntelCohort{cohort})[0])
}
func (api *API) deleteIntelCohort(rw http.ResponseWriter, r *http.Request) {
}
func (api *API) patchIntelCohort(rw http.ResponseWriter, r *http.Request) {
}
// Serves the intel daemon protobuf API over a WebSocket.
//
// @Summary Serve intel daemon
// @ID serve-intel-daemon
// @Security CoderSessionToken
// @Tags Intel
// @Param organization path string true "Organization ID" format(uuid)
// @Param instance_id query string true "Instance ID"
// @Param cpu_cores query int false "Number of CPU cores"
// @Param memory_total_mb query int false "Total memory in MB"
// @Param hostname query string false "Hostname"
// @Param operating_system query string false "Operating system"
// @Param operating_system_version query string false "Operating system version"
// @Param architecture query string false "Architecture"
// @success 101
// @Router /organizations/{organization}/intel/serve [get]
func (api *API) intelDaemonServe(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)
@ -428,3 +522,30 @@ func convertIntelMachines(machines []database.IntelMachine) []codersdk.IntelMach
}
return converted
}
func convertIntelCohorts(cohorts []database.IntelCohort) []codersdk.IntelCohort {
converted := make([]codersdk.IntelCohort, len(cohorts))
for i, cohort := range cohorts {
converted[i] = codersdk.IntelCohort{
ID: cohort.ID,
OrganizationID: cohort.OrganizationID,
CreatedBy: cohort.CreatedBy,
UpdatedAt: cohort.UpdatedAt,
RegexFilters: codersdk.IntelCohortRegexFilters{
OperatingSystem: cohort.RegexOperatingSystem,
OperatingSystemPlatform: cohort.RegexOperatingSystemPlatform,
OperatingSystemVersion: cohort.RegexOperatingSystemVersion,
Architecture: cohort.RegexArchitecture,
InstanceID: cohort.RegexInstanceID,
},
CreatedAt: cohort.CreatedAt,
IntelCohortMetadata: codersdk.IntelCohortMetadata{
Name: cohort.Name,
Icon: cohort.Icon,
Description: cohort.Description,
TrackedExecutables: cohort.TrackedExecutables,
},
}
}
return converted
}

View File

@ -15,9 +15,9 @@ func TestIntelMachines(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
user := coderdtest.CreateFirstUser(t, client)
res, err := client.IntelMachines(context.Background(), codersdk.IntelMachinesRequest{})
res, err := client.IntelMachines(context.Background(), user.OrganizationID, codersdk.IntelMachinesRequest{})
require.NoError(t, err)
require.Len(t, res.IntelMachines, 0)
require.Equal(t, res.Count, 0)
@ -25,15 +25,15 @@ func TestIntelMachines(t *testing.T) {
t.Run("Single", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
user := coderdtest.CreateFirstUser(t, client)
ctx := context.Background()
intelClient, err := client.ServeIntelDaemon(ctx, codersdk.ServeIntelDaemonRequest{
intelClient, err := client.ServeIntelDaemon(ctx, user.OrganizationID, codersdk.ServeIntelDaemonRequest{
InstanceID: "test",
})
require.NoError(t, err)
defer intelClient.DRPCConn().Close()
res, err := client.IntelMachines(context.Background(), codersdk.IntelMachinesRequest{})
res, err := client.IntelMachines(context.Background(), user.OrganizationID, codersdk.IntelMachinesRequest{})
require.NoError(t, err)
require.Len(t, res.IntelMachines, 1)
require.Equal(t, res.Count, 1)
@ -41,9 +41,9 @@ func TestIntelMachines(t *testing.T) {
t.Run("Filtered", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
user := coderdtest.CreateFirstUser(t, client)
ctx := context.Background()
firstClient, err := client.ServeIntelDaemon(ctx, codersdk.ServeIntelDaemonRequest{
firstClient, err := client.ServeIntelDaemon(ctx, user.OrganizationID, codersdk.ServeIntelDaemonRequest{
IntelDaemonHostInfo: codersdk.IntelDaemonHostInfo{
OperatingSystem: "linux",
},
@ -51,7 +51,7 @@ func TestIntelMachines(t *testing.T) {
})
require.NoError(t, err)
defer firstClient.DRPCConn().Close()
secondClient, err := client.ServeIntelDaemon(ctx, codersdk.ServeIntelDaemonRequest{
secondClient, err := client.ServeIntelDaemon(ctx, user.OrganizationID, codersdk.ServeIntelDaemonRequest{
IntelDaemonHostInfo: codersdk.IntelDaemonHostInfo{
OperatingSystem: "windows",
},
@ -60,7 +60,7 @@ func TestIntelMachines(t *testing.T) {
require.NoError(t, err)
defer secondClient.DRPCConn().Close()
res, err := client.IntelMachines(context.Background(), codersdk.IntelMachinesRequest{
res, err := client.IntelMachines(context.Background(), user.OrganizationID, codersdk.IntelMachinesRequest{
RegexFilters: codersdk.IntelCohortRegexFilters{
OperatingSystem: "windows",
},
@ -71,3 +71,18 @@ func TestIntelMachines(t *testing.T) {
require.Equal(t, res.IntelMachines[0].OperatingSystem, "windows")
})
}
func TestIntelCohorts(t *testing.T) {
t.Parallel()
t.Run("Create", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
ctx := context.Background()
cohort, err := client.CreateIntelCohort(ctx, user.OrganizationID, codersdk.CreateIntelCohortRequest{
Name: "example",
})
require.NoError(t, err)
require.Equal(t, cohort.Name, "example")
})
}

View File

@ -48,7 +48,6 @@ func (i *IntelCohortRegexFilters) Normalize() {
type IntelCohortMetadata struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Icon string `json:"icon"`
Description string `json:"description"`
TrackedExecutables []string `json:"tracked_executables"`
@ -58,8 +57,8 @@ type IntelCohort struct {
ID uuid.UUID `json:"id" format:"uuid"`
OrganizationID uuid.UUID `json:"organization_id" format:"uuid"`
CreatedBy uuid.UUID `json:"created_by"`
CreatedAt int64 `json:"created_at" format:"date-time"`
UpdatedAt int64 `json:"updated_at" format:"date-time"`
CreatedAt time.Time `json:"created_at" format:"date-time"`
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
RegexFilters IntelCohortRegexFilters `json:"regex_filters"`
IntelCohortMetadata
@ -77,8 +76,7 @@ type IntelDaemonHostInfo struct {
type ServeIntelDaemonRequest struct {
IntelDaemonHostInfo
InstanceID string `json:"instance_id"`
Organization uuid.UUID `json:"organization" format:"uuid"`
InstanceID string `json:"instance_id"`
}
type IntelMachine struct {
@ -97,11 +95,54 @@ type IntelMachine struct {
Architecture string `json:"architecture"`
}
func (c *Client) IntelCohorts(ctx context.Context, organizationID uuid.UUID) ([]IntelCohort, error) {
orgParam := organizationID.String()
if organizationID == uuid.Nil {
orgParam = DefaultOrganization
}
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/intel/cohorts", orgParam), nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, ReadBodyAsError(res)
}
var cohorts []IntelCohort
return cohorts, json.NewDecoder(res.Body).Decode(&cohorts)
}
// CreateIntelCohortRequest is the request to create a new cohort.
type CreateIntelCohortRequest struct {
Name string `json:"name" validate:"required"`
Icon string `json:"icon"`
Description string `json:"description"`
TrackedExecutables []string `json:"tracked_executables"`
RegexFilters *IntelCohortRegexFilters `json:"regex_filters"`
}
// CreateIntelCohort creates a new cohort.
func (c *Client) CreateIntelCohort(ctx context.Context, organizationID uuid.UUID, req CreateIntelCohortRequest) (IntelCohort, error) {
orgParam := organizationID.String()
if organizationID == uuid.Nil {
orgParam = DefaultOrganization
}
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/organizations/%s/intel/cohorts", orgParam), req)
if err != nil {
return IntelCohort{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return IntelCohort{}, ReadBodyAsError(res)
}
var cohort IntelCohort
return cohort, json.NewDecoder(res.Body).Decode(&cohort)
}
type IntelMachinesRequest struct {
OrganizationID uuid.UUID `json:"organization_id" format:"uuid"`
RegexFilters IntelCohortRegexFilters `json:"regex_filters"`
Offset int `json:"offset,omitempty" typescript:"-"`
Limit int `json:"limit,omitempty" typescript:"-"`
RegexFilters IntelCohortRegexFilters `json:"regex_filters"`
Offset int `json:"offset,omitempty" typescript:"-"`
Limit int `json:"limit,omitempty" typescript:"-"`
}
type IntelMachinesResponse struct {
@ -111,9 +152,9 @@ type IntelMachinesResponse struct {
// IntelMachines returns a set of machines that matches the filters provided.
// It will return all machines if no filters are provided.
func (c *Client) IntelMachines(ctx context.Context, req IntelMachinesRequest) (IntelMachinesResponse, error) {
orgParam := req.OrganizationID.String()
if req.OrganizationID == uuid.Nil {
func (c *Client) IntelMachines(ctx context.Context, organizationID uuid.UUID, req IntelMachinesRequest) (IntelMachinesResponse, error) {
orgParam := organizationID.String()
if organizationID == uuid.Nil {
orgParam = DefaultOrganization
}
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/intel/machines", orgParam), nil,
@ -137,9 +178,9 @@ func (c *Client) IntelMachines(ctx context.Context, req IntelMachinesRequest) (I
}
// ServeIntelDaemon returns the gRPC service for an intel daemon.
func (c *Client) ServeIntelDaemon(ctx context.Context, req ServeIntelDaemonRequest) (proto.DRPCIntelDaemonClient, error) {
orgParam := req.Organization.String()
if req.Organization == uuid.Nil {
func (c *Client) ServeIntelDaemon(ctx context.Context, organizationID uuid.UUID, req ServeIntelDaemonRequest) (proto.DRPCIntelDaemonClient, error) {
orgParam := organizationID.String()
if organizationID == uuid.Nil {
orgParam = DefaultOrganization
}
serverURL, err := c.URL.Parse(fmt.Sprintf("/api/v2/organizations/%s/intel/serve", orgParam))

84
docs/api/intel.md generated
View File

@ -59,3 +59,87 @@ curl -X GET http://coder-server:8080/api/v2/insights/daus \
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.IntelMachinesResponse](schemas.md#codersdkintelmachinesresponse) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Create intel cohort
### Code samples
```shell
# Example request using curl
curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/intel/cohorts \
-H 'Content-Type: application/json' \
-H 'Accept: */*' \
-H 'Coder-Session-Token: API_KEY'
```
`POST /organizations/{organization}/intel/cohorts`
> Body parameter
```json
{
"description": "string",
"icon": "string",
"name": "string",
"regex_filters": {
"architecture": "string",
"instance_id": "string",
"operating_system": "string",
"operating_system_platform": "string",
"operating_system_version": "string"
},
"tracked_executables": ["string"]
}
```
### Parameters
| Name | In | Type | Required | Description |
| -------------- | ---- | -------------------------------------------------------------------------------- | -------- | --------------------------- |
| `organization` | path | string(uuid) | true | Organization ID |
| `body` | body | [codersdk.CreateIntelCohortRequest](schemas.md#codersdkcreateintelcohortrequest) | true | Create intel cohort request |
### Example responses
> 201 Response
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------------ | ----------- | ------------------------------------------------------ |
| 201 | [Created](https://tools.ietf.org/html/rfc7231#section-6.3.2) | Created | [codersdk.IntelCohort](schemas.md#codersdkintelcohort) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Serve intel daemon
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/intel/serve?instance_id=string \
-H 'Coder-Session-Token: API_KEY'
```
`GET /organizations/{organization}/intel/serve`
### Parameters
| Name | In | Type | Required | Description |
| -------------------------- | ----- | ------------ | -------- | ------------------------ |
| `organization` | path | string(uuid) | true | Organization ID |
| `instance_id` | query | string | true | Instance ID |
| `cpu_cores` | query | integer | false | Number of CPU cores |
| `memory_total_mb` | query | integer | false | Total memory in MB |
| `hostname` | query | string | false | Hostname |
| `operating_system` | query | string | false | Operating system |
| `operating_system_version` | query | string | false | Operating system version |
| `architecture` | query | string | false | Architecture |
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------------------------ | ------------------- | ------ |
| 101 | [Switching Protocols](https://tools.ietf.org/html/rfc7231#section-6.2.2) | Switching Protocols | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).

88
docs/api/schemas.md generated
View File

@ -1336,6 +1336,34 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
| `name` | string | false | | |
| `quota_allowance` | integer | false | | |
## codersdk.CreateIntelCohortRequest
```json
{
"description": "string",
"icon": "string",
"name": "string",
"regex_filters": {
"architecture": "string",
"instance_id": "string",
"operating_system": "string",
"operating_system_platform": "string",
"operating_system_version": "string"
},
"tracked_executables": ["string"]
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| --------------------- | -------------------------------------------------------------------- | -------- | ------------ | ----------- |
| `description` | string | false | | |
| `icon` | string | false | | |
| `name` | string | true | | |
| `regex_filters` | [codersdk.IntelCohortRegexFilters](#codersdkintelcohortregexfilters) | false | | |
| `tracked_executables` | array of string | false | | |
## codersdk.CreateOrganizationRequest
```json
@ -3040,6 +3068,66 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
| `day` |
| `week` |
## codersdk.IntelCohort
```json
{
"created_at": "2019-08-24T14:15:22Z",
"created_by": "string",
"description": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"regex_filters": {
"architecture": "string",
"instance_id": "string",
"operating_system": "string",
"operating_system_platform": "string",
"operating_system_version": "string"
},
"tracked_executables": ["string"],
"updated_at": "2019-08-24T14:15:22Z"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| --------------------- | -------------------------------------------------------------------- | -------- | ------------ | ----------- |
| `created_at` | string | false | | |
| `created_by` | string | false | | |
| `description` | string | false | | |
| `icon` | string | false | | |
| `id` | string | false | | |
| `name` | string | false | | |
| `organization_id` | string | false | | |
| `regex_filters` | [codersdk.IntelCohortRegexFilters](#codersdkintelcohortregexfilters) | false | | |
| `tracked_executables` | array of string | false | | |
| `updated_at` | string | false | | |
## codersdk.IntelCohortRegexFilters
```json
{
"architecture": "string",
"instance_id": "string",
"operating_system": "string",
"operating_system_platform": "string",
"operating_system_version": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| --------------------------- | ------ | -------- | ------------ | ----------- |
| `architecture` | string | false | | |
| `instance_id` | string | false | | |
| `operating_system` | string | false | | |
| `operating_system_platform` | string | false | | |
| `operating_system_version` | string | false | | |
## codersdk.IntelMachine
```json

View File

@ -213,6 +213,15 @@ export interface CreateGroupRequest {
readonly quota_allowance: number;
}
// From codersdk/intel.go
export interface CreateIntelCohortRequest {
readonly name: string;
readonly icon: string;
readonly description: string;
readonly tracked_executables: readonly string[];
readonly regex_filters?: IntelCohortRegexFilters;
}
// From codersdk/users.go
export interface CreateOrganizationRequest {
readonly name: string;
@ -605,15 +614,14 @@ export interface IntelCohort extends IntelCohortMetadata {
readonly id: string;
readonly organization_id: string;
readonly created_by: string;
readonly created_at: number;
readonly updated_at: number;
readonly created_at: string;
readonly updated_at: string;
readonly regex_filters: IntelCohortRegexFilters;
}
// From codersdk/intel.go
export interface IntelCohortMetadata {
readonly name: string;
readonly display_name: string;
readonly icon: string;
readonly description: string;
readonly tracked_executables: readonly string[];
@ -658,7 +666,6 @@ export interface IntelMachine {
// From codersdk/intel.go
export interface IntelMachinesRequest {
readonly organization_id: string;
readonly regex_filters: IntelCohortRegexFilters;
}
@ -1088,7 +1095,6 @@ export interface SSHConfigResponse {
// From codersdk/intel.go
export interface ServeIntelDaemonRequest extends IntelDaemonHostInfo {
readonly instance_id: string;
readonly organization: string;
}
// From codersdk/serversentevents.go