mirror of https://github.com/coder/coder.git
feat: add backend for jfrog xray support (#11829)
This commit is contained in:
parent
46d92dac57
commit
4f5a2f0a9b
|
@ -1182,6 +1182,84 @@ 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": [
|
||||
|
@ -9581,6 +9659,31 @@ 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": [
|
||||
|
|
|
@ -1018,6 +1018,74 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/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": [
|
||||
|
@ -8607,6 +8675,31 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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"],
|
||||
|
|
|
@ -1111,6 +1111,13 @@ func (q *querier) GetHungProvisionerJobs(ctx context.Context, hungSince time.Tim
|
|||
return q.db.GetHungProvisionerJobs(ctx, hungSince)
|
||||
}
|
||||
|
||||
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, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return "", err
|
||||
|
@ -3153,6 +3160,27 @@ 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, rbac.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, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
|
|
|
@ -364,7 +364,7 @@ func (s *MethodTestSuite) TestGroup() {
|
|||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestProvsionerJob() {
|
||||
func (s *MethodTestSuite) TestProvisionerJob() {
|
||||
s.Run("ArchiveUnusedTemplateVersions", s.Subtest(func(db database.Store, check *expects) {
|
||||
j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{
|
||||
Type: database.ProvisionerJobTypeTemplateVersionImport,
|
||||
|
@ -2216,6 +2216,44 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
|||
s.Run("GetUserLinksByUserID", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(uuid.New()).Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("GetJFrogXrayScanByWorkspaceAndAgentID", s.Subtest(func(db database.Store, check *expects) {
|
||||
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
agent := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{})
|
||||
|
||||
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, rbac.ActionRead).Returns(expect)
|
||||
}))
|
||||
s.Run("UpsertJFrogXrayScanByWorkspaceAndAgentID", s.Subtest(func(db database.Store, check *expects) {
|
||||
tpl := dbgen.Template(s.T(), db, database.Template{})
|
||||
ws := dbgen.Workspace(s.T(), db, database.Workspace{
|
||||
TemplateID: tpl.ID,
|
||||
})
|
||||
check.Args(database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams{
|
||||
WorkspaceID: ws.ID,
|
||||
AgentID: uuid.New(),
|
||||
}).Asserts(tpl, rbac.ActionCreate)
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestOAuth2ProviderApps() {
|
||||
|
|
|
@ -129,6 +129,7 @@ type data struct {
|
|||
gitSSHKey []database.GitSSHKey
|
||||
groupMembers []database.GroupMember
|
||||
groups []database.Group
|
||||
jfrogXRayScans []database.JfrogXrayScan
|
||||
licenses []database.License
|
||||
oauth2ProviderApps []database.OAuth2ProviderApp
|
||||
oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret
|
||||
|
@ -1986,6 +1987,24 @@ func (q *FakeQuerier) GetHungProvisionerJobs(_ context.Context, hungSince time.T
|
|||
return hungJobs, 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()
|
||||
|
@ -7292,6 +7311,39 @@ 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()
|
||||
|
|
|
@ -545,6 +545,13 @@ func (m metricsStore) GetHungProvisionerJobs(ctx context.Context, hungSince time
|
|||
return jobs, err
|
||||
}
|
||||
|
||||
func (m metricsStore) 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 metricsStore) GetLastUpdateCheck(ctx context.Context) (string, error) {
|
||||
start := time.Now()
|
||||
version, err := m.s.GetLastUpdateCheck(ctx)
|
||||
|
@ -2027,6 +2034,13 @@ func (m metricsStore) UpsertHealthSettings(ctx context.Context, value string) er
|
|||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) 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 metricsStore) UpsertLastUpdateCheck(ctx context.Context, value string) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpsertLastUpdateCheck(ctx, value)
|
||||
|
|
|
@ -1069,6 +1069,21 @@ func (mr *MockStoreMockRecorder) GetHungProvisionerJobs(arg0, arg1 any) *gomock.
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHungProvisionerJobs", reflect.TypeOf((*MockStore)(nil).GetHungProvisionerJobs), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetJFrogXrayScanByWorkspaceAndAgentID mocks base method.
|
||||
func (m *MockStore) GetJFrogXrayScanByWorkspaceAndAgentID(arg0 context.Context, arg1 database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetJFrogXrayScanByWorkspaceAndAgentID", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.JfrogXrayScan)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetJFrogXrayScanByWorkspaceAndAgentID indicates an expected call of GetJFrogXrayScanByWorkspaceAndAgentID.
|
||||
func (mr *MockStoreMockRecorder) GetJFrogXrayScanByWorkspaceAndAgentID(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetJFrogXrayScanByWorkspaceAndAgentID", reflect.TypeOf((*MockStore)(nil).GetJFrogXrayScanByWorkspaceAndAgentID), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetLastUpdateCheck mocks base method.
|
||||
func (m *MockStore) GetLastUpdateCheck(arg0 context.Context) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -4256,6 +4271,20 @@ func (mr *MockStoreMockRecorder) UpsertHealthSettings(arg0, arg1 any) *gomock.Ca
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertHealthSettings", reflect.TypeOf((*MockStore)(nil).UpsertHealthSettings), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertJFrogXrayScanByWorkspaceAndAgentID mocks base method.
|
||||
func (m *MockStore) UpsertJFrogXrayScanByWorkspaceAndAgentID(arg0 context.Context, arg1 database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpsertJFrogXrayScanByWorkspaceAndAgentID", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpsertJFrogXrayScanByWorkspaceAndAgentID indicates an expected call of UpsertJFrogXrayScanByWorkspaceAndAgentID.
|
||||
func (mr *MockStoreMockRecorder) UpsertJFrogXrayScanByWorkspaceAndAgentID(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertJFrogXrayScanByWorkspaceAndAgentID", reflect.TypeOf((*MockStore)(nil).UpsertJFrogXrayScanByWorkspaceAndAgentID), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertLastUpdateCheck mocks base method.
|
||||
func (m *MockStore) UpsertLastUpdateCheck(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
|
|
@ -438,6 +438,15 @@ COMMENT ON COLUMN groups.display_name IS 'Display name is a custom, human-friend
|
|||
|
||||
COMMENT ON COLUMN groups.source IS 'Source indicates how the group was created. It can be created by a user manually, or through some system process like OIDC group sync.';
|
||||
|
||||
CREATE TABLE jfrog_xray_scans (
|
||||
agent_id uuid NOT NULL,
|
||||
workspace_id uuid NOT NULL,
|
||||
critical integer DEFAULT 0 NOT NULL,
|
||||
high integer DEFAULT 0 NOT NULL,
|
||||
medium integer DEFAULT 0 NOT NULL,
|
||||
results_url text DEFAULT ''::text NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE licenses (
|
||||
id integer NOT NULL,
|
||||
uploaded_at timestamp with time zone NOT NULL,
|
||||
|
@ -1292,6 +1301,9 @@ ALTER TABLE ONLY groups
|
|||
ALTER TABLE ONLY groups
|
||||
ADD CONSTRAINT groups_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY jfrog_xray_scans
|
||||
ADD CONSTRAINT jfrog_xray_scans_pkey PRIMARY KEY (agent_id, workspace_id);
|
||||
|
||||
ALTER TABLE ONLY licenses
|
||||
ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt);
|
||||
|
||||
|
@ -1536,6 +1548,12 @@ ALTER TABLE ONLY group_members
|
|||
ALTER TABLE ONLY groups
|
||||
ADD CONSTRAINT groups_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY jfrog_xray_scans
|
||||
ADD CONSTRAINT jfrog_xray_scans_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY jfrog_xray_scans
|
||||
ADD CONSTRAINT jfrog_xray_scans_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY oauth2_provider_app_secrets
|
||||
ADD CONSTRAINT oauth2_provider_app_secrets_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ const (
|
|||
ForeignKeyGroupMembersGroupID ForeignKeyConstraint = "group_members_group_id_fkey" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_group_id_fkey FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE;
|
||||
ForeignKeyGroupMembersUserID ForeignKeyConstraint = "group_members_user_id_fkey" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyGroupsOrganizationID ForeignKeyConstraint = "groups_organization_id_fkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
ForeignKeyJfrogXrayScansAgentID ForeignKeyConstraint = "jfrog_xray_scans_agent_id_fkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
ForeignKeyJfrogXrayScansWorkspaceID ForeignKeyConstraint = "jfrog_xray_scans_workspace_id_fkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE;
|
||||
ForeignKeyOauth2ProviderAppSecretsAppID ForeignKeyConstraint = "oauth2_provider_app_secrets_app_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE;
|
||||
ForeignKeyOrganizationMembersOrganizationIDUUID ForeignKeyConstraint = "organization_members_organization_id_uuid_fkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_organization_id_uuid_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
ForeignKeyOrganizationMembersUserIDUUID ForeignKeyConstraint = "organization_members_user_id_uuid_fkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE jfrog_xray_scans;
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE jfrog_xray_scans (
|
||||
agent_id uuid NOT NULL REFERENCES workspace_agents(id) ON DELETE CASCADE,
|
||||
workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
||||
critical integer NOT NULL DEFAULT 0,
|
||||
high integer NOT NULL DEFAULT 0,
|
||||
medium integer NOT NULL DEFAULT 0,
|
||||
results_url text NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (agent_id, workspace_id)
|
||||
);
|
|
@ -0,0 +1,11 @@
|
|||
INSERT INTO jfrog_xray_scans
|
||||
(workspace_id, agent_id, critical, high, medium, results_url)
|
||||
VALUES (
|
||||
'b90547be-8870-4d68-8184-e8b2242b7c01',
|
||||
'8fa17bbd-c48c-44c7-91ae-d4acbc755fad',
|
||||
10,
|
||||
5,
|
||||
2,
|
||||
'https://hello-world'
|
||||
);
|
||||
|
|
@ -1779,6 +1779,15 @@ type GroupMember struct {
|
|||
GroupID uuid.UUID `db:"group_id" json:"group_id"`
|
||||
}
|
||||
|
||||
type JfrogXrayScan 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"`
|
||||
}
|
||||
|
||||
type License struct {
|
||||
ID int32 `db:"id" json:"id"`
|
||||
UploadedAt time.Time `db:"uploaded_at" json:"uploaded_at"`
|
||||
|
|
|
@ -119,6 +119,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)
|
||||
GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg GetJFrogXrayScanByWorkspaceAndAgentIDParams) (JfrogXrayScan, error)
|
||||
GetLastUpdateCheck(ctx context.Context) (string, error)
|
||||
GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (WorkspaceBuild, error)
|
||||
GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuild, error)
|
||||
|
@ -384,6 +385,7 @@ 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
|
||||
UpsertOAuthSigningKey(ctx context.Context, value string) error
|
||||
|
|
|
@ -2438,6 +2438,75 @@ func (q *sqlQuerier) GetUserLatencyInsights(ctx context.Context, arg GetUserLate
|
|||
return items, nil
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
-- 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;
|
|
@ -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);
|
||||
UniqueJfrogXrayScansPkey UniqueConstraint = "jfrog_xray_scans_pkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_pkey PRIMARY KEY (agent_id, workspace_id);
|
||||
UniqueLicensesJWTKey UniqueConstraint = "licenses_jwt_key" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt);
|
||||
UniqueLicensesPkey UniqueConstraint = "licenses_pkey" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_pkey PRIMARY KEY (id);
|
||||
UniqueOauth2ProviderAppSecretsAppIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_secrets_app_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret);
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
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)
|
||||
}
|
|
@ -263,7 +263,7 @@ type TemplateExample struct {
|
|||
func (c *Client) Template(ctx context.Context, template uuid.UUID) (Template, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templates/%s", template), nil)
|
||||
if err != nil {
|
||||
return Template{}, nil
|
||||
return Template{}, xerrors.Errorf("do request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
|
|
|
@ -359,6 +359,107 @@ 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
|
||||
|
|
|
@ -3339,6 +3339,30 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
| -------------- | ------ | -------- | ------------ | ----------- |
|
||||
| `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
|
||||
|
|
|
@ -348,6 +348,15 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
|||
})
|
||||
})
|
||||
})
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
if len(options.SCIMAPIKey) != 0 {
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
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,
|
||||
Critical: int32(req.Critical),
|
||||
High: int32(req.High),
|
||||
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.Required("workspace_id").UUID(vals, uuid.UUID{}, "workspace_id")
|
||||
agentID = p.Required("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) {
|
||||
api.entitlementsMu.RLock()
|
||||
// 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).
|
||||
enabled := api.entitlements.Features[codersdk.FeatureMultipleExternalAuth].Enabled
|
||||
api.entitlementsMu.RUnlock()
|
||||
|
||||
if !enabled {
|
||||
httpapi.RouteNotFound(rw)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
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.Workspace{
|
||||
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.Workspace{
|
||||
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)
|
||||
})
|
||||
}
|
|
@ -607,6 +607,16 @@ 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/licenses.go
|
||||
export interface License {
|
||||
readonly id: number;
|
||||
|
|
Loading…
Reference in New Issue