diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4e554ea..4b3f1c0 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,9 +13,33 @@ env: IMAGE_NAME: ${{ github.repository }} jobs: + test: + name: Test + runs-on: ubuntu-latest + + services: + test_db: + image: postgres:16 + env: + POSTGRES_USER: sgs + POSTGRES_PASSWORD: sgs-pass + ports: + - 5433:5432 + volumes: + - test_data:/var/lib/postgresql/data + + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v11 + - uses: DeterminateSystems/magic-nix-cache-action@v6 + + - name: Run tests + run: nix develop -c make check + build: name: Build runs-on: ubuntu-latest + needs: test steps: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v11 diff --git a/model/mock/mock.go b/model/mock/mock.go index 99e6cf1..ca613a3 100644 --- a/model/mock/mock.go +++ b/model/mock/mock.go @@ -56,7 +56,15 @@ func (svc *mockWorkspaces) CreateWorkspace(ctx context.Context, ws *model.Worksp newWS.ID = svc.nextID svc.nextID++ newWS.Enabled = false - newWS.Request = nil + newWS.Request = &model.WorkspaceUpdate{ + WorkspaceID: newWS.ID, + ByUser: newWS.Users[0], + Enabled: true, + Nodegroup: newWS.Nodegroup, + Userdata: newWS.Userdata, + Quotas: maps.Clone(newWS.Quotas), + Users: slices.Clone(newWS.Users), + } slices.Sort(newWS.Users) svc.data[newWS.ID] = newWS diff --git a/model/postgres/postgres.go b/model/postgres/postgres.go index 1f3cb8b..90c210a 100644 --- a/model/postgres/postgres.go +++ b/model/postgres/postgres.go @@ -131,6 +131,15 @@ func (svc *workspacesRepository) CreateWorkspace(ctx context.Context, ws *model. } } + upd := ws.InitialRequest() + upd.WorkspaceID = id + tx.Exec(ctx, ` + INSERT INTO workspaces_updaterequests (workspace_id, by_user, data) + VALUES ($1, $2, $3) + ON CONFLICT (workspace_id) DO UPDATE + SET by_user = EXCLUDED.by_user, data = EXCLUDED.data`, + upd.WorkspaceID, upd.ByUser, upd) + // we could reconstruct the ws here, but it's easier to just query it newWs, err = queryWorkspace(ctx, tx, id) return err diff --git a/model/test/workspace.go b/model/test/workspace.go index 0005537..98ea883 100644 --- a/model/test/workspace.go +++ b/model/test/workspace.go @@ -22,6 +22,7 @@ func TestWorkspace(t *testing.T, wsf func() model.WorkspaceService) { Users: []string{"user1"}, } want.ID = testWorkspaceCreate(t, wsSvc, &want, nil) + want.Request = want.InitialRequest() testWorkspaceListAll(t, wsSvc, []*model.Workspace{&want}) testWorkspaceListUser(t, wsSvc, "user1", []*model.Workspace{&want}) @@ -209,18 +210,21 @@ func TestWorkspace(t *testing.T, wsf func() model.WorkspaceService) { Users: []string{"user1"}, } ws1.ID = testWorkspaceCreate(t, wsSvc, &ws1, nil) + ws1.Request = ws1.InitialRequest() ws2 := model.Workspace{ Nodegroup: model.NodegroupGraduate, Users: []string{"user2"}, } ws2.ID = testWorkspaceCreate(t, wsSvc, &ws2, nil) + ws2.Request = ws2.InitialRequest() wsAll := model.Workspace{ Nodegroup: model.NodegroupUndergraduate, Users: []string{"user1", "user2"}, } wsAll.ID = testWorkspaceCreate(t, wsSvc, &wsAll, nil) + wsAll.Request = wsAll.InitialRequest() testWorkspaceListAll(t, wsSvc, []*model.Workspace{&ws1, &ws2, &wsAll}) testWorkspaceListUser(t, wsSvc, "user1", []*model.Workspace{&ws1, &wsAll}) @@ -291,7 +295,7 @@ func testWorkspaceCreate(t *testing.T, wsSvc model.WorkspaceService, ws *model.W want := *ws want.ID = got.ID want.Enabled = false - want.Request = nil + want.Request = want.InitialRequest() if diff := cmp.Diff(got, &want, cmpopts.EquateEmpty()); diff != "" { t.Fatalf("CreateWorkspace(%#v) = mismatch\n%s", ws, diff) } diff --git a/model/workspace.go b/model/workspace.go index c531a95..c37d3d9 100644 --- a/model/workspace.go +++ b/model/workspace.go @@ -43,6 +43,20 @@ func (ws Workspace) Valid() bool { return true } +// InitialRequest returns the initial request for a new workspace. It has the +// same attributes as the workspace itself, but enabled. +func (ws *Workspace) InitialRequest() *WorkspaceUpdate { + return &WorkspaceUpdate{ + WorkspaceID: ws.ID, + ByUser: ws.Users[0], + Enabled: true, + Nodegroup: ws.Nodegroup, + Userdata: ws.Userdata, + Quotas: ws.Quotas, + Users: ws.Users, + } +} + type WorkspaceUpdate struct { WorkspaceID ID ByUser string diff --git a/view/workspace.templ b/view/workspace.templ index eec3494..cda2d30 100644 --- a/view/workspace.templ +++ b/view/workspace.templ @@ -67,18 +67,25 @@ templ PageWorkspaceDetails(ws *model.Workspace, kubeconfig string) { // Render the status badge templ wsStatusButton(ws *model.Workspace) { - if ws.Enabled && ws.Request == nil { - // enabled (and created) and no pending requests: all good - Enabled - } else if !ws.Created { - // hasn't ever been enabled: pending approval - Pending approval - } else if !ws.Enabled { - // created && !enabled: disabled by admin - Disabled - } else if ws.Enabled && ws.Request != nil { - // otherwise, enabled but has pending changes - Pending changes + switch true { + case !ws.Created && ws.Request != nil: + // pending approval (initial state) + Pending + case !ws.Created && ws.Request == nil: + // request has been rejected + Rejected + case ws.Enabled && ws.Request == nil: + // enabled + Enabled + case !ws.Enabled && ws.Request == nil: + // disabled + Disabled + case ws.Enabled && ws.Request != nil: + // enabled, changes requested + Enabled, pending request + case !ws.Enabled && ws.Request != nil: + // disabled, changes requested + Disabled, pending request } } diff --git a/view/workspace_templ.go b/view/workspace_templ.go index 48047cd..872fd81 100644 --- a/view/workspace_templ.go +++ b/view/workspace_templ.go @@ -230,23 +230,34 @@ func wsStatusButton(ws *model.Workspace) templ.Component { templ_7745c5c3_Var10 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - if ws.Enabled && ws.Request == nil { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Enabled") + switch true { + case !ws.Created && ws.Request != nil: + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Pending") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case !ws.Created && ws.Request == nil: + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Rejected") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - } else if !ws.Created { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Pending approval") + case ws.Enabled && ws.Request == nil: + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Enabled") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - } else if !ws.Enabled { + case !ws.Enabled && ws.Request == nil: _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Disabled") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - } else if ws.Enabled && ws.Request != nil { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Pending changes") + case ws.Enabled && ws.Request != nil: + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Enabled, pending request") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case !ws.Enabled && ws.Request != nil: + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Disabled, pending request") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -280,7 +291,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(ws.ID.Hash()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 87, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 94, Col: 63} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -293,7 +304,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(ws.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 88, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 95, Col: 63} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -315,7 +326,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(ws.Request.ByUser) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 91, Col: 83} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 98, Col: 83} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -441,7 +452,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var23 string templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(string(ws.Nodegroup)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 103, Col: 34} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 110, Col: 34} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) if templ_7745c5c3_Err != nil { @@ -460,7 +471,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var24 string templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(string(ng)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 109, Col: 32} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 116, Col: 32} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { @@ -483,7 +494,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var25 string templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(string(ng)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 109, Col: 83} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 116, Col: 83} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { @@ -546,7 +557,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var30 string templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(ws.Userdata) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 114, Col: 84} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 121, Col: 84} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { @@ -559,7 +570,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var31 string templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(newWS.Userdata) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 115, Col: 98} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 122, Col: 98} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) if templ_7745c5c3_Err != nil { @@ -641,7 +652,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var36 string templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(user) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 125, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 132, Col: 67} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) if templ_7745c5c3_Err != nil { @@ -664,7 +675,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var37 string templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("user-%d", i)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 131, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 138, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) if templ_7745c5c3_Err != nil { @@ -677,7 +688,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var38 string templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("user-%d", i)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 131, Col: 99} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 138, Col: 99} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) if templ_7745c5c3_Err != nil { @@ -690,7 +701,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var39 string templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(user) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 131, Col: 114} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 138, Col: 114} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) if templ_7745c5c3_Err != nil { @@ -774,7 +785,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var46 string templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(ctxCSRF(ctx)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 138, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 145, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) if templ_7745c5c3_Err != nil { @@ -887,7 +898,7 @@ func workspaceDetails(ws, newWS *model.Workspace, kubeconfig string) templ.Compo var templ_7745c5c3_Var55 string templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(kubeconfig) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 159, Col: 128} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 166, Col: 128} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55)) if templ_7745c5c3_Err != nil { @@ -975,7 +986,7 @@ func wsQuotaInput(label, name, units string, res model.Resource, ws, newWS *mode var templ_7745c5c3_Var62 string templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(label) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 170, Col: 9} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 177, Col: 9} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62)) if templ_7745c5c3_Err != nil { @@ -993,7 +1004,7 @@ func wsQuotaInput(label, name, units string, res model.Resource, ws, newWS *mode var templ_7745c5c3_Var63 string templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(units) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 172, Col: 58} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 179, Col: 58} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63)) if templ_7745c5c3_Err != nil { @@ -1033,7 +1044,7 @@ func wsQuotaInput(label, name, units string, res model.Resource, ws, newWS *mode var templ_7745c5c3_Var66 string templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(ws.Quotas[res])) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 175, Col: 75} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 182, Col: 75} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66)) if templ_7745c5c3_Err != nil { @@ -1046,7 +1057,7 @@ func wsQuotaInput(label, name, units string, res model.Resource, ws, newWS *mode var templ_7745c5c3_Var67 string templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 176, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 183, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67)) if templ_7745c5c3_Err != nil { @@ -1059,7 +1070,7 @@ func wsQuotaInput(label, name, units string, res model.Resource, ws, newWS *mode var templ_7745c5c3_Var68 string templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 176, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 183, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68)) if templ_7745c5c3_Err != nil { @@ -1072,7 +1083,7 @@ func wsQuotaInput(label, name, units string, res model.Resource, ws, newWS *mode var templ_7745c5c3_Var69 string templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(newWS.Quotas[res])) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 176, Col: 107} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/workspace.templ`, Line: 183, Col: 107} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69)) if templ_7745c5c3_Err != nil {