chore!: remove JFrog integration (#17353)

- Removes displaying XRay scan results in the dashboard. I'm not sure
  anyone was even using this integration so it's just debt for us to
  maintain. We can open up a separate issue to get rid of the db tables
  once we know for sure that we haven't broken anyone.
This commit is contained in:
Jon Ayers
2025-04-11 14:45:21 -04:00
committed by GitHub
parent 15584e69ef
commit c06ef7c1eb
23 changed files with 2 additions and 1102 deletions
-103
View File
@@ -1432,84 +1432,6 @@ const docTemplate = `{
}
}
},
"/integrations/jfrog/xray-scan": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Enterprise"
],
"summary": "Get JFrog XRay scan by workspace agent ID.",
"operationId": "get-jfrog-xray-scan-by-workspace-agent-id",
"parameters": [
{
"type": "string",
"description": "Workspace ID",
"name": "workspace_id",
"in": "query",
"required": true
},
{
"type": "string",
"description": "Agent ID",
"name": "agent_id",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.JFrogXrayScan"
}
}
}
},
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Enterprise"
],
"summary": "Post JFrog XRay scan by workspace agent ID.",
"operationId": "post-jfrog-xray-scan-by-workspace-agent-id",
"parameters": [
{
"description": "Post JFrog XRay scan request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.JFrogXrayScan"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.Response"
}
}
}
}
},
"/licenses": {
"get": {
"security": [
@@ -12579,31 +12501,6 @@ const docTemplate = `{
}
}
},
"codersdk.JFrogXrayScan": {
"type": "object",
"properties": {
"agent_id": {
"type": "string",
"format": "uuid"
},
"critical": {
"type": "integer"
},
"high": {
"type": "integer"
},
"medium": {
"type": "integer"
},
"results_url": {
"type": "string"
},
"workspace_id": {
"type": "string",
"format": "uuid"
}
}
},
"codersdk.JobErrorCode": {
"type": "string",
"enum": [
-93
View File
@@ -1249,74 +1249,6 @@
}
}
},
"/integrations/jfrog/xray-scan": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Enterprise"],
"summary": "Get JFrog XRay scan by workspace agent ID.",
"operationId": "get-jfrog-xray-scan-by-workspace-agent-id",
"parameters": [
{
"type": "string",
"description": "Workspace ID",
"name": "workspace_id",
"in": "query",
"required": true
},
{
"type": "string",
"description": "Agent ID",
"name": "agent_id",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.JFrogXrayScan"
}
}
}
},
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["Enterprise"],
"summary": "Post JFrog XRay scan by workspace agent ID.",
"operationId": "post-jfrog-xray-scan-by-workspace-agent-id",
"parameters": [
{
"description": "Post JFrog XRay scan request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.JFrogXrayScan"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.Response"
}
}
}
}
},
"/licenses": {
"get": {
"security": [
@@ -11311,31 +11243,6 @@
}
}
},
"codersdk.JFrogXrayScan": {
"type": "object",
"properties": {
"agent_id": {
"type": "string",
"format": "uuid"
},
"critical": {
"type": "integer"
},
"high": {
"type": "integer"
},
"medium": {
"type": "integer"
},
"results_url": {
"type": "string"
},
"workspace_id": {
"type": "string",
"format": "uuid"
}
}
},
"codersdk.JobErrorCode": {
"type": "string",
"enum": ["REQUIRED_TEMPLATE_VARIABLES"],
-28
View File
@@ -1895,13 +1895,6 @@ func (q *querier) GetInboxNotificationsByUserID(ctx context.Context, userID data
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetInboxNotificationsByUserID)(ctx, userID)
}
func (q *querier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) {
if _, err := fetch(q.log, q.auth, q.db.GetWorkspaceByID)(ctx, arg.WorkspaceID); err != nil {
return database.JfrogXrayScan{}, err
}
return q.db.GetJFrogXrayScanByWorkspaceAndAgentID(ctx, arg)
}
func (q *querier) GetLastUpdateCheck(ctx context.Context) (string, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
return "", err
@@ -4767,27 +4760,6 @@ func (q *querier) UpsertHealthSettings(ctx context.Context, value string) error
return q.db.UpsertHealthSettings(ctx, value)
}
func (q *querier) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error {
// TODO: Having to do all this extra querying makes me a sad panda.
workspace, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID)
if err != nil {
return xerrors.Errorf("get workspace by id: %w", err)
}
template, err := q.db.GetTemplateByID(ctx, workspace.TemplateID)
if err != nil {
return xerrors.Errorf("get template by id: %w", err)
}
// Only template admins should be able to write JFrog Xray scans to a workspace.
// We don't want this to be a workspace-level permission because then users
// could overwrite their own results.
if err := q.authorizeContext(ctx, policy.ActionCreate, template); err != nil {
return err
}
return q.db.UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx, arg)
}
func (q *querier) UpsertLastUpdateCheck(ctx context.Context, value string) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem); err != nil {
return err
-68
View File
@@ -4293,74 +4293,6 @@ func (s *MethodTestSuite) TestSystemFunctions() {
s.Run("GetUserLinksByUserID", s.Subtest(func(db database.Store, check *expects) {
check.Args(uuid.New()).Asserts(rbac.ResourceSystem, policy.ActionRead)
}))
s.Run("GetJFrogXrayScanByWorkspaceAndAgentID", s.Subtest(func(db database.Store, check *expects) {
u := dbgen.User(s.T(), db, database.User{})
org := dbgen.Organization(s.T(), db, database.Organization{})
tpl := dbgen.Template(s.T(), db, database.Template{
OrganizationID: org.ID,
CreatedBy: u.ID,
})
ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{
OwnerID: u.ID,
OrganizationID: org.ID,
TemplateID: tpl.ID,
})
pj := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{})
res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{
JobID: pj.ID,
})
agent := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{
ResourceID: res.ID,
})
err := db.UpsertJFrogXrayScanByWorkspaceAndAgentID(context.Background(), database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams{
AgentID: agent.ID,
WorkspaceID: ws.ID,
Critical: 1,
High: 12,
Medium: 14,
ResultsUrl: "http://hello",
})
require.NoError(s.T(), err)
expect := database.JfrogXrayScan{
WorkspaceID: ws.ID,
AgentID: agent.ID,
Critical: 1,
High: 12,
Medium: 14,
ResultsUrl: "http://hello",
}
check.Args(database.GetJFrogXrayScanByWorkspaceAndAgentIDParams{
WorkspaceID: ws.ID,
AgentID: agent.ID,
}).Asserts(ws, policy.ActionRead).Returns(expect)
}))
s.Run("UpsertJFrogXrayScanByWorkspaceAndAgentID", s.Subtest(func(db database.Store, check *expects) {
u := dbgen.User(s.T(), db, database.User{})
org := dbgen.Organization(s.T(), db, database.Organization{})
tpl := dbgen.Template(s.T(), db, database.Template{
OrganizationID: org.ID,
CreatedBy: u.ID,
})
ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{
OwnerID: u.ID,
OrganizationID: org.ID,
TemplateID: tpl.ID,
})
pj := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{})
res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{
JobID: pj.ID,
})
agent := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{
ResourceID: res.ID,
})
check.Args(database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams{
WorkspaceID: ws.ID,
AgentID: agent.ID,
}).Asserts(tpl, policy.ActionCreate)
}))
s.Run("DeleteRuntimeConfig", s.Subtest(func(db database.Store, check *expects) {
check.Args("test").Asserts(rbac.ResourceSystem, policy.ActionDelete)
}))
+2 -54
View File
@@ -222,7 +222,6 @@ type data struct {
gitSSHKey []database.GitSSHKey
groupMembers []database.GroupMemberTable
groups []database.Group
jfrogXRayScans []database.JfrogXrayScan
licenses []database.License
notificationMessages []database.NotificationMessage
notificationPreferences []database.NotificationPreference
@@ -3687,24 +3686,6 @@ func (q *FakeQuerier) GetInboxNotificationsByUserID(_ context.Context, params da
return notifications, nil
}
func (q *FakeQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(_ context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) {
err := validateDatabaseType(arg)
if err != nil {
return database.JfrogXrayScan{}, err
}
q.mutex.RLock()
defer q.mutex.RUnlock()
for _, scan := range q.jfrogXRayScans {
if scan.AgentID == arg.AgentID && scan.WorkspaceID == arg.WorkspaceID {
return scan, nil
}
}
return database.JfrogXrayScan{}, sql.ErrNoRows
}
func (q *FakeQuerier) GetLastUpdateCheck(_ context.Context) (string, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
@@ -4241,7 +4222,7 @@ func (q *FakeQuerier) GetPresetByID(ctx context.Context, presetID uuid.UUID) (da
if preset.ID == presetID {
tv, ok := versionMap[preset.TemplateVersionID]
if !ok {
return empty, fmt.Errorf("template version %v does not exist", preset.TemplateVersionID)
return empty, xerrors.Errorf("template version %v does not exist", preset.TemplateVersionID)
}
return database.GetPresetByIDRow{
ID: preset.ID,
@@ -4256,7 +4237,7 @@ func (q *FakeQuerier) GetPresetByID(ctx context.Context, presetID uuid.UUID) (da
}
}
return empty, fmt.Errorf("preset %v does not exist", presetID)
return empty, xerrors.Errorf("preset %v does not exist", presetID)
}
func (q *FakeQuerier) GetPresetByWorkspaceBuildID(_ context.Context, workspaceBuildID uuid.UUID) (database.TemplateVersionPreset, error) {
@@ -11986,39 +11967,6 @@ func (q *FakeQuerier) UpsertHealthSettings(_ context.Context, data string) error
return nil
}
func (q *FakeQuerier) UpsertJFrogXrayScanByWorkspaceAndAgentID(_ context.Context, arg database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error {
err := validateDatabaseType(arg)
if err != nil {
return err
}
q.mutex.Lock()
defer q.mutex.Unlock()
for i, scan := range q.jfrogXRayScans {
if scan.AgentID == arg.AgentID && scan.WorkspaceID == arg.WorkspaceID {
scan.Critical = arg.Critical
scan.High = arg.High
scan.Medium = arg.Medium
scan.ResultsUrl = arg.ResultsUrl
q.jfrogXRayScans[i] = scan
return nil
}
}
//nolint:gosimple
q.jfrogXRayScans = append(q.jfrogXRayScans, database.JfrogXrayScan{
WorkspaceID: arg.WorkspaceID,
AgentID: arg.AgentID,
Critical: arg.Critical,
High: arg.High,
Medium: arg.Medium,
ResultsUrl: arg.ResultsUrl,
})
return nil
}
func (q *FakeQuerier) UpsertLastUpdateCheck(_ context.Context, data string) error {
q.mutex.Lock()
defer q.mutex.Unlock()
-14
View File
@@ -858,13 +858,6 @@ func (m queryMetricsStore) GetInboxNotificationsByUserID(ctx context.Context, us
return r0, r1
}
func (m queryMetricsStore) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) {
start := time.Now()
r0, r1 := m.s.GetJFrogXrayScanByWorkspaceAndAgentID(ctx, arg)
m.queryLatencies.WithLabelValues("GetJFrogXrayScanByWorkspaceAndAgentID").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m queryMetricsStore) GetLastUpdateCheck(ctx context.Context) (string, error) {
start := time.Now()
version, err := m.s.GetLastUpdateCheck(ctx)
@@ -3042,13 +3035,6 @@ func (m queryMetricsStore) UpsertHealthSettings(ctx context.Context, value strin
return r0
}
func (m queryMetricsStore) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error {
start := time.Now()
r0 := m.s.UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx, arg)
m.queryLatencies.WithLabelValues("UpsertJFrogXrayScanByWorkspaceAndAgentID").Observe(time.Since(start).Seconds())
return r0
}
func (m queryMetricsStore) UpsertLastUpdateCheck(ctx context.Context, value string) error {
start := time.Now()
r0 := m.s.UpsertLastUpdateCheck(ctx, value)
-29
View File
@@ -1729,21 +1729,6 @@ func (mr *MockStoreMockRecorder) GetInboxNotificationsByUserID(ctx, arg any) *go
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationsByUserID), ctx, arg)
}
// GetJFrogXrayScanByWorkspaceAndAgentID mocks base method.
func (m *MockStore) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetJFrogXrayScanByWorkspaceAndAgentID", ctx, arg)
ret0, _ := ret[0].(database.JfrogXrayScan)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetJFrogXrayScanByWorkspaceAndAgentID indicates an expected call of GetJFrogXrayScanByWorkspaceAndAgentID.
func (mr *MockStoreMockRecorder) GetJFrogXrayScanByWorkspaceAndAgentID(ctx, arg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetJFrogXrayScanByWorkspaceAndAgentID", reflect.TypeOf((*MockStore)(nil).GetJFrogXrayScanByWorkspaceAndAgentID), ctx, arg)
}
// GetLastUpdateCheck mocks base method.
func (m *MockStore) GetLastUpdateCheck(ctx context.Context) (string, error) {
m.ctrl.T.Helper()
@@ -6415,20 +6400,6 @@ func (mr *MockStoreMockRecorder) UpsertHealthSettings(ctx, value any) *gomock.Ca
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertHealthSettings", reflect.TypeOf((*MockStore)(nil).UpsertHealthSettings), ctx, value)
}
// UpsertJFrogXrayScanByWorkspaceAndAgentID mocks base method.
func (m *MockStore) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpsertJFrogXrayScanByWorkspaceAndAgentID", ctx, arg)
ret0, _ := ret[0].(error)
return ret0
}
// UpsertJFrogXrayScanByWorkspaceAndAgentID indicates an expected call of UpsertJFrogXrayScanByWorkspaceAndAgentID.
func (mr *MockStoreMockRecorder) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx, arg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertJFrogXrayScanByWorkspaceAndAgentID", reflect.TypeOf((*MockStore)(nil).UpsertJFrogXrayScanByWorkspaceAndAgentID), ctx, arg)
}
// UpsertLastUpdateCheck mocks base method.
func (m *MockStore) UpsertLastUpdateCheck(ctx context.Context, value string) error {
m.ctrl.T.Helper()
-2
View File
@@ -200,7 +200,6 @@ type sqlcQuerier interface {
// param created_at_opt: The created_at timestamp to filter by. This parameter is usd for pagination - it fetches notifications created before the specified timestamp if it is not the zero value
// param limit_opt: The limit of notifications to fetch. If the limit is not specified, it defaults to 25
GetInboxNotificationsByUserID(ctx context.Context, arg GetInboxNotificationsByUserIDParams) ([]InboxNotification, error)
GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg GetJFrogXrayScanByWorkspaceAndAgentIDParams) (JfrogXrayScan, error)
GetLastUpdateCheck(ctx context.Context) (string, error)
GetLatestCryptoKeyByFeature(ctx context.Context, feature CryptoKeyFeature) (CryptoKey, error)
GetLatestWorkspaceAppStatusesByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAppStatus, error)
@@ -619,7 +618,6 @@ type sqlcQuerier interface {
// The functional values are immutable and controlled implicitly.
UpsertDefaultProxy(ctx context.Context, arg UpsertDefaultProxyParams) error
UpsertHealthSettings(ctx context.Context, value string) error
UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error
UpsertLastUpdateCheck(ctx context.Context, value string) error
UpsertLogoURL(ctx context.Context, value string) error
// Insert or update notification report generator logs with recent activity.
-69
View File
@@ -3570,75 +3570,6 @@ func (q *sqlQuerier) UpsertTemplateUsageStats(ctx context.Context) error {
return err
}
const getJFrogXrayScanByWorkspaceAndAgentID = `-- name: GetJFrogXrayScanByWorkspaceAndAgentID :one
SELECT
agent_id, workspace_id, critical, high, medium, results_url
FROM
jfrog_xray_scans
WHERE
agent_id = $1
AND
workspace_id = $2
LIMIT
1
`
type GetJFrogXrayScanByWorkspaceAndAgentIDParams struct {
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
}
func (q *sqlQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg GetJFrogXrayScanByWorkspaceAndAgentIDParams) (JfrogXrayScan, error) {
row := q.db.QueryRowContext(ctx, getJFrogXrayScanByWorkspaceAndAgentID, arg.AgentID, arg.WorkspaceID)
var i JfrogXrayScan
err := row.Scan(
&i.AgentID,
&i.WorkspaceID,
&i.Critical,
&i.High,
&i.Medium,
&i.ResultsUrl,
)
return i, err
}
const upsertJFrogXrayScanByWorkspaceAndAgentID = `-- name: UpsertJFrogXrayScanByWorkspaceAndAgentID :exec
INSERT INTO
jfrog_xray_scans (
agent_id,
workspace_id,
critical,
high,
medium,
results_url
)
VALUES
($1, $2, $3, $4, $5, $6)
ON CONFLICT (agent_id, workspace_id)
DO UPDATE SET critical = $3, high = $4, medium = $5, results_url = $6
`
type UpsertJFrogXrayScanByWorkspaceAndAgentIDParams struct {
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
Critical int32 `db:"critical" json:"critical"`
High int32 `db:"high" json:"high"`
Medium int32 `db:"medium" json:"medium"`
ResultsUrl string `db:"results_url" json:"results_url"`
}
func (q *sqlQuerier) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error {
_, err := q.db.ExecContext(ctx, upsertJFrogXrayScanByWorkspaceAndAgentID,
arg.AgentID,
arg.WorkspaceID,
arg.Critical,
arg.High,
arg.Medium,
arg.ResultsUrl,
)
return err
}
const deleteLicense = `-- name: DeleteLicense :one
DELETE
FROM licenses
-26
View File
@@ -1,26 +0,0 @@
-- name: GetJFrogXrayScanByWorkspaceAndAgentID :one
SELECT
*
FROM
jfrog_xray_scans
WHERE
agent_id = $1
AND
workspace_id = $2
LIMIT
1;
-- name: UpsertJFrogXrayScanByWorkspaceAndAgentID :exec
INSERT INTO
jfrog_xray_scans (
agent_id,
workspace_id,
critical,
high,
medium,
results_url
)
VALUES
($1, $2, $3, $4, $5, $6)
ON CONFLICT (agent_id, workspace_id)
DO UPDATE SET critical = $3, high = $4, medium = $5, results_url = $6;
-50
View File
@@ -1,50 +0,0 @@
package codersdk
import (
"context"
"encoding/json"
"net/http"
"github.com/google/uuid"
"golang.org/x/xerrors"
)
type JFrogXrayScan struct {
WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid"`
AgentID uuid.UUID `json:"agent_id" format:"uuid"`
Critical int `json:"critical"`
High int `json:"high"`
Medium int `json:"medium"`
ResultsURL string `json:"results_url"`
}
func (c *Client) PostJFrogXrayScan(ctx context.Context, req JFrogXrayScan) error {
res, err := c.Request(ctx, http.MethodPost, "/api/v2/integrations/jfrog/xray-scan", req)
if err != nil {
return xerrors.Errorf("make request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return ReadBodyAsError(res)
}
return nil
}
func (c *Client) JFrogXRayScan(ctx context.Context, workspaceID, agentID uuid.UUID) (JFrogXrayScan, error) {
res, err := c.Request(ctx, http.MethodGet, "/api/v2/integrations/jfrog/xray-scan", nil,
WithQueryParam("workspace_id", workspaceID.String()),
WithQueryParam("agent_id", agentID.String()),
)
if err != nil {
return JFrogXrayScan{}, xerrors.Errorf("make request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return JFrogXrayScan{}, ReadBodyAsError(res)
}
var resp JFrogXrayScan
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
-101
View File
@@ -490,107 +490,6 @@ curl -X PATCH http://coder-server:8080/api/v2/groups/{group} \
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get JFrog XRay scan by workspace agent ID
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/integrations/jfrog/xray-scan?workspace_id=string&agent_id=string \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /integrations/jfrog/xray-scan`
### Parameters
| Name | In | Type | Required | Description |
|----------------|-------|--------|----------|--------------|
| `workspace_id` | query | string | true | Workspace ID |
| `agent_id` | query | string | true | Agent ID |
### Example responses
> 200 Response
```json
{
"agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978",
"critical": 0,
"high": 0,
"medium": 0,
"results_url": "string",
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}
```
### Responses
| Status | Meaning | Description | Schema |
|--------|---------------------------------------------------------|-------------|------------------------------------------------------------|
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.JFrogXrayScan](schemas.md#codersdkjfrogxrayscan) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Post JFrog XRay scan by workspace agent ID
### Code samples
```shell
# Example request using curl
curl -X POST http://coder-server:8080/api/v2/integrations/jfrog/xray-scan \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`POST /integrations/jfrog/xray-scan`
> Body parameter
```json
{
"agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978",
"critical": 0,
"high": 0,
"medium": 0,
"results_url": "string",
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}
```
### Parameters
| Name | In | Type | Required | Description |
|--------|------|------------------------------------------------------------|----------|------------------------------|
| `body` | body | [codersdk.JFrogXrayScan](schemas.md#codersdkjfrogxrayscan) | true | Post JFrog XRay scan request |
### Example responses
> 200 Response
```json
{
"detail": "string",
"message": "string",
"validations": [
{
"detail": "string",
"field": "string"
}
]
}
```
### Responses
| Status | Meaning | Description | Schema |
|--------|---------------------------------------------------------|-------------|--------------------------------------------------|
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get licenses
### Code samples
-24
View File
@@ -3414,30 +3414,6 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
|----------------|--------|----------|--------------|-------------|
| `signed_token` | string | false | | |
## codersdk.JFrogXrayScan
```json
{
"agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978",
"critical": 0,
"high": 0,
"medium": 0,
"results_url": "string",
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|----------------|---------|----------|--------------|-------------|
| `agent_id` | string | false | | |
| `critical` | integer | false | | |
| `high` | integer | false | | |
| `medium` | integer | false | | |
| `results_url` | string | false | | |
| `workspace_id` | string | false | | |
## codersdk.JobErrorCode
```json
-10
View File
@@ -470,16 +470,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
r.Get("/", api.userQuietHoursSchedule)
r.Put("/", api.putUserQuietHoursSchedule)
})
r.Route("/integrations", func(r chi.Router) {
r.Use(
apiKeyMiddleware,
api.jfrogEnabledMW,
)
r.Post("/jfrog/xray-scan", api.postJFrogXrayScan)
r.Get("/jfrog/xray-scan", api.jFrogXrayScan)
})
// The /notifications base route is mounted by the AGPL router, so we can't group it here.
// Additionally, because we have a static route for /notifications/templates/system which conflicts
// with the below route, we need to register this route without any mounts or groups to make both work.
-120
View File
@@ -1,120 +0,0 @@
package coderd
import (
"net/http"
"github.com/google/uuid"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/codersdk"
)
// Post workspace agent results for a JFrog XRay scan.
//
// @Summary Post JFrog XRay scan by workspace agent ID.
// @ID post-jfrog-xray-scan-by-workspace-agent-id
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Enterprise
// @Param request body codersdk.JFrogXrayScan true "Post JFrog XRay scan request"
// @Success 200 {object} codersdk.Response
// @Router /integrations/jfrog/xray-scan [post]
func (api *API) postJFrogXrayScan(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var req codersdk.JFrogXrayScan
if !httpapi.Read(ctx, rw, r, &req) {
return
}
err := api.Database.UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx, database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams{
WorkspaceID: req.WorkspaceID,
AgentID: req.AgentID,
// #nosec G115 - Vulnerability counts are small and fit in int32
Critical: int32(req.Critical),
// #nosec G115 - Vulnerability counts are small and fit in int32
High: int32(req.High),
// #nosec G115 - Vulnerability counts are small and fit in int32
Medium: int32(req.Medium),
ResultsUrl: req.ResultsURL,
})
if httpapi.Is404Error(err) {
httpapi.ResourceNotFound(rw)
return
}
if err != nil {
httpapi.InternalServerError(rw, err)
return
}
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.Response{
Message: "Successfully inserted JFrog XRay scan!",
})
}
// Get workspace agent results for a JFrog XRay scan.
//
// @Summary Get JFrog XRay scan by workspace agent ID.
// @ID get-jfrog-xray-scan-by-workspace-agent-id
// @Security CoderSessionToken
// @Produce json
// @Tags Enterprise
// @Param workspace_id query string true "Workspace ID"
// @Param agent_id query string true "Agent ID"
// @Success 200 {object} codersdk.JFrogXrayScan
// @Router /integrations/jfrog/xray-scan [get]
func (api *API) jFrogXrayScan(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
vals = r.URL.Query()
p = httpapi.NewQueryParamParser()
wsID = p.RequiredNotEmpty("workspace_id").UUID(vals, uuid.UUID{}, "workspace_id")
agentID = p.RequiredNotEmpty("agent_id").UUID(vals, uuid.UUID{}, "agent_id")
)
if len(p.Errors) > 0 {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid query params.",
Validations: p.Errors,
})
return
}
scan, err := api.Database.GetJFrogXrayScanByWorkspaceAndAgentID(ctx, database.GetJFrogXrayScanByWorkspaceAndAgentIDParams{
WorkspaceID: wsID,
AgentID: agentID,
})
if httpapi.Is404Error(err) {
httpapi.ResourceNotFound(rw)
return
}
if err != nil {
httpapi.InternalServerError(rw, err)
return
}
httpapi.Write(ctx, rw, http.StatusOK, codersdk.JFrogXrayScan{
WorkspaceID: scan.WorkspaceID,
AgentID: scan.AgentID,
Critical: int(scan.Critical),
High: int(scan.High),
Medium: int(scan.Medium),
ResultsURL: scan.ResultsUrl,
})
}
func (api *API) jfrogEnabledMW(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// This doesn't actually use the external auth feature but we want
// to lock this behind an enterprise license and it's somewhat
// related to external auth (in that it is JFrog integration).
if !api.Entitlements.Enabled(codersdk.FeatureMultipleExternalAuth) {
httpapi.RouteNotFound(rw)
return
}
next.ServeHTTP(rw, r)
})
}
-122
View File
@@ -1,122 +0,0 @@
package coderd_test
import (
"net/http"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbfake"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
"github.com/coder/coder/v2/enterprise/coderd/license"
"github.com/coder/coder/v2/testutil"
)
func TestJFrogXrayScan(t *testing.T) {
t.Parallel()
t.Run("Post/Get", func(t *testing.T) {
t.Parallel()
ownerClient, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{codersdk.FeatureMultipleExternalAuth: 1},
},
})
tac, ta := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
wsResp := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
OrganizationID: owner.OrganizationID,
OwnerID: ta.ID,
}).WithAgent().Do()
ws := coderdtest.MustWorkspace(t, tac, wsResp.Workspace.ID)
require.Len(t, ws.LatestBuild.Resources, 1)
require.Len(t, ws.LatestBuild.Resources[0].Agents, 1)
agentID := ws.LatestBuild.Resources[0].Agents[0].ID
expectedPayload := codersdk.JFrogXrayScan{
WorkspaceID: ws.ID,
AgentID: agentID,
Critical: 19,
High: 5,
Medium: 3,
ResultsURL: "https://hello-world",
}
ctx := testutil.Context(t, testutil.WaitMedium)
err := tac.PostJFrogXrayScan(ctx, expectedPayload)
require.NoError(t, err)
resp1, err := tac.JFrogXRayScan(ctx, ws.ID, agentID)
require.NoError(t, err)
require.Equal(t, expectedPayload, resp1)
// Can update again without error.
expectedPayload = codersdk.JFrogXrayScan{
WorkspaceID: ws.ID,
AgentID: agentID,
Critical: 20,
High: 22,
Medium: 8,
ResultsURL: "https://goodbye-world",
}
err = tac.PostJFrogXrayScan(ctx, expectedPayload)
require.NoError(t, err)
resp2, err := tac.JFrogXRayScan(ctx, ws.ID, agentID)
require.NoError(t, err)
require.NotEqual(t, expectedPayload, resp1)
require.Equal(t, expectedPayload, resp2)
})
t.Run("MemberPostUnauthorized", func(t *testing.T) {
t.Parallel()
ownerClient, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{codersdk.FeatureMultipleExternalAuth: 1},
},
})
memberClient, member := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
wsResp := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
OrganizationID: owner.OrganizationID,
OwnerID: member.ID,
}).WithAgent().Do()
ws := coderdtest.MustWorkspace(t, memberClient, wsResp.Workspace.ID)
require.Len(t, ws.LatestBuild.Resources, 1)
require.Len(t, ws.LatestBuild.Resources[0].Agents, 1)
agentID := ws.LatestBuild.Resources[0].Agents[0].ID
expectedPayload := codersdk.JFrogXrayScan{
WorkspaceID: ws.ID,
AgentID: agentID,
Critical: 19,
High: 5,
Medium: 3,
ResultsURL: "https://hello-world",
}
ctx := testutil.Context(t, testutil.WaitMedium)
err := memberClient.PostJFrogXrayScan(ctx, expectedPayload)
require.Error(t, err)
cerr, ok := codersdk.AsError(err)
require.True(t, ok)
require.Equal(t, http.StatusNotFound, cerr.StatusCode())
err = ownerClient.PostJFrogXrayScan(ctx, expectedPayload)
require.NoError(t, err)
// We should still be able to fetch.
resp1, err := memberClient.JFrogXRayScan(ctx, ws.ID, agentID)
require.NoError(t, err)
require.Equal(t, expectedPayload, resp1)
})
}
-28
View File
@@ -381,11 +381,6 @@ export type InsightsTemplateParams = InsightsParams & {
interval: "day" | "week";
};
export type GetJFrogXRayScanParams = {
workspaceId: string;
agentId: string;
};
export class MissingBuildParameters extends Error {
parameters: TypesGen.TemplateVersionParameter[] = [];
versionId: string;
@@ -2277,29 +2272,6 @@ class ApiMethods {
await this.axios.delete(`/api/v2/workspaces/${workspaceID}/favorite`);
};
getJFrogXRayScan = async (options: GetJFrogXRayScanParams) => {
const searchParams = new URLSearchParams({
workspace_id: options.workspaceId,
agent_id: options.agentId,
});
try {
const res = await this.axios.get<TypesGen.JFrogXrayScan>(
`/api/v2/integrations/jfrog/xray-scan?${searchParams}`,
);
return res.data;
} catch (error) {
if (isAxiosError(error) && error.response?.status === 404) {
// react-query library does not allow undefined to be returned as a
// query result
return null;
}
throw error;
}
};
postWorkspaceUsage = async (
workspaceID: string,
options: PostWorkspaceUsageRequest,
-9
View File
@@ -1,9 +0,0 @@
import type { GetJFrogXRayScanParams } from "api/api";
import { API } from "api/api";
export const xrayScan = (params: GetJFrogXRayScanParams) => {
return {
queryKey: ["xray", params],
queryFn: () => API.getJFrogXRayScan(params),
};
};
-10
View File
@@ -1171,16 +1171,6 @@ export interface IssueReconnectingPTYSignedTokenResponse {
readonly signed_token: string;
}
// From codersdk/jfrog.go
export interface JFrogXrayScan {
readonly workspace_id: string;
readonly agent_id: string;
readonly critical: number;
readonly high: number;
readonly medium: number;
readonly results_url: string;
}
// From codersdk/provisionerdaemons.go
export type JobErrorCode = "REQUIRED_TEMPLATE_VARIABLES";
@@ -299,27 +299,6 @@ export const Deprecated: Story = {
},
};
export const WithXRayScan: Story = {
parameters: {
queries: [
{
key: [
"xray",
{ agentId: M.MockWorkspaceAgent.id, workspaceId: M.MockWorkspace.id },
],
data: {
workspace_id: M.MockWorkspace.id,
agent_id: M.MockWorkspaceAgent.id,
critical: 10,
high: 3,
medium: 5,
results_url: "http://localhost:8080",
},
},
],
},
};
export const HideApp: Story = {
args: {
agent: {
-9
View File
@@ -4,7 +4,6 @@ import Collapse from "@mui/material/Collapse";
import Divider from "@mui/material/Divider";
import Skeleton from "@mui/material/Skeleton";
import { API } from "api/api";
import { xrayScan } from "api/queries/integrations";
import type {
Template,
Workspace,
@@ -41,7 +40,6 @@ import { PortForwardButton } from "./PortForwardButton";
import { AgentSSHButton } from "./SSHButton/SSHButton";
import { TerminalLink } from "./TerminalLink/TerminalLink";
import { VSCodeDesktopButton } from "./VSCodeDesktopButton/VSCodeDesktopButton";
import { XRayScanAlert } from "./XRayScanAlert";
export interface AgentRowProps {
agent: WorkspaceAgent;
@@ -72,11 +70,6 @@ export const AgentRow: FC<AgentRowProps> = ({
storybookAgentMetadata,
sshPrefix,
}) => {
// XRay integration
const xrayScanQuery = useQuery(
xrayScan({ workspaceId: workspace.id, agentId: agent.id }),
);
// Apps visibility
const visibleApps = agent.apps.filter((app) => !app.hidden);
const hasAppsToDisplay = !hideVSCodeDesktopButton || visibleApps.length > 0;
@@ -227,8 +220,6 @@ export const AgentRow: FC<AgentRowProps> = ({
)}
</header>
{xrayScanQuery.data && <XRayScanAlert scan={xrayScanQuery.data} />}
<div css={styles.content}>
{agent.status === "connected" && (
<section css={styles.apps}>
@@ -1,108 +0,0 @@
import type { Interpolation, Theme } from "@emotion/react";
import type { JFrogXrayScan } from "api/typesGenerated";
import { Button } from "components/Button/Button";
import { ExternalImage } from "components/ExternalImage/ExternalImage";
import type { FC } from "react";
interface XRayScanAlertProps {
scan: JFrogXrayScan;
}
export const XRayScanAlert: FC<XRayScanAlertProps> = ({ scan }) => {
const display = scan.critical > 0 || scan.high > 0 || scan.medium > 0;
return display ? (
<div role="alert" css={styles.root}>
<ExternalImage
alt="JFrog logo"
src="/icon/jfrog.svg"
css={{ width: 40, height: 40 }}
/>
<div>
<span css={styles.title}>
JFrog Xray detected new vulnerabilities for this agent
</span>
<ul css={styles.issues}>
{scan.critical > 0 && (
<li css={[styles.critical, styles.issueItem]}>
{scan.critical} critical
</li>
)}
{scan.high > 0 && (
<li css={[styles.high, styles.issueItem]}>{scan.high} high</li>
)}
{scan.medium > 0 && (
<li css={[styles.medium, styles.issueItem]}>
{scan.medium} medium
</li>
)}
</ul>
</div>
<div css={styles.link}>
<Button size="sm" variant="subtle" asChild>
<a href={scan.results_url} target="_blank" rel="noreferrer">
Review results
</a>
</Button>
</div>
</div>
) : (
<></>
);
};
const styles = {
root: (theme) => ({
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
borderLeft: 0,
borderRight: 0,
fontSize: 14,
padding: "24px 16px 24px 32px",
lineHeight: "1.5",
display: "flex",
alignItems: "center",
gap: 24,
}),
title: {
display: "block",
fontWeight: 500,
},
issues: {
listStyle: "none",
margin: 0,
padding: 0,
fontSize: 13,
display: "flex",
alignItems: "center",
gap: 16,
marginTop: 4,
},
issueItem: {
display: "flex",
alignItems: "center",
gap: 8,
"&:before": {
content: '""',
display: "block",
width: 6,
height: 6,
borderRadius: "50%",
backgroundColor: "currentColor",
},
},
critical: (theme) => ({
color: theme.roles.error.fill.solid,
}),
high: (theme) => ({
color: theme.roles.warning.fill.solid,
}),
medium: (theme) => ({
color: theme.roles.notice.fill.solid,
}),
link: {
marginLeft: "auto",
alignSelf: "flex-start",
},
} satisfies Record<string, Interpolation<Theme>>;
-4
View File
@@ -374,8 +374,4 @@ export const handlers = [
http.get("/api/v2/workspaceagents/:agent/listening-ports", () => {
return HttpResponse.json(M.MockListeningPortsResponse);
}),
http.get("/api/v2/integrations/jfrog/xray-scan", () => {
return new HttpResponse(null, { status: 404 });
}),
];