Skip to content

Commit f601be7

Browse files
committed
Merge branch 'main' into bryphe/prototype/workspaces-page
2 parents de7d79b + d76737b commit f601be7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+4009
-1590
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Generated files
2+
database/dump.sql linguist-generated=true
23
peerbroker/proto/*.go linguist-generated=true
34
provisionerd/proto/*.go linguist-generated=true
45
provisionersdk/proto/*.go linguist-generated=true

.github/dependabot.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ updates:
3434
prefix: "chore"
3535

3636
- package-ecosystem: "npm"
37-
directory: "/"
37+
directory: "/site/"
3838
schedule:
3939
interval: "daily"
4040
time: "06:00"

.github/workflows/coder.yaml

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ permissions:
2626
security-events: none
2727
statuses: none
2828

29+
# Cancel in-progress runs for pull requests when developers push
30+
# additional changes
31+
concurrency:
32+
group: ${{ github.workflow }}-${{ github.ref }}
33+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
34+
2935
jobs:
3036
style-lint-golangci:
3137
name: style/lint/golangci
@@ -55,9 +61,11 @@ jobs:
5561

5662
- name: Install node_modules
5763
run: yarn install
64+
working-directory: site
5865

5966
- name: "yarn lint"
6067
run: yarn lint
68+
working-directory: site
6169

6270
gen:
6371
name: "style/gen"
@@ -89,14 +97,7 @@ jobs:
8997
style:
9098
- fmt
9199
fail-fast: false
92-
permissions:
93-
actions: write # for cancel-workflow-action
94-
contents: read
95100
steps:
96-
- name: Cancel previous runs
97-
if: github.event_name == 'pull_request'
98-
uses: styfle/cancel-workflow-action@0.9.1
99-
100101
- name: Checkout
101102
uses: actions/checkout@v2
102103
with:
@@ -114,6 +115,7 @@ jobs:
114115

115116
- name: Install node_modules
116117
run: yarn install
118+
working-directory: site
117119

118120
- name: "make ${{ matrix.style }}"
119121
run: "make --output-sync -j ${{ matrix.style }}"
@@ -165,7 +167,7 @@ jobs:
165167
run:
166168
DB=true gotestsum --jsonfile="gotests.json" --packages="./..." --
167169
-covermode=atomic -coverprofile="gotests.coverage" -timeout=3m
168-
-count=1 -race -parallel=1
170+
-count=1 -race -parallel=2
169171

170172
- uses: codecov/codecov-action@v2
171173
if: github.actor != 'dependabot[bot]'
@@ -195,15 +197,18 @@ jobs:
195197
node-version: "14"
196198

197199
- run: yarn install
200+
working-directory: site
198201

199202
- run: yarn build
203+
working-directory: site
200204

201205
- run: yarn test:coverage
206+
working-directory: site
202207

203208
- uses: codecov/codecov-action@v2
204209
if: github.actor != 'dependabot[bot]'
205210
with:
206211
token: ${{ secrets.CODECOV_TOKEN }}
207-
files: ./coverage/lcov.info
212+
files: ./site/coverage/lcov.info
208213
flags: unittest-js
209214
fail_ci_if_error: true

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ yarn-error.log
1616

1717
# Front-end ignore
1818
.next/
19+
site/.eslintcache
1920
site/.next/
21+
site/node_modules/
22+
site/yarn-error.log
2023
coverage/
2124

2225
# Build
2326
bin/
24-
site/out/
27+
site/out/

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@
2222
"cmd": "make gen"
2323
}
2424
]
25-
}
25+
},
26+
"cSpell.words": ["coderd", "coderdtest", "codersdk", "httpmw", "oneof", "stretchr", "xerrors"]
2627
}

Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ fmt/prettier:
2626
@echo "--- prettier"
2727
# Avoid writing files in CI to reduce file write activity
2828
ifdef CI
29-
yarn run format:check
29+
cd site && yarn run format:check
3030
else
31-
yarn run format:write
31+
cd site && yarn run format:write
3232
endif
3333
.PHONY: fmt/prettier
3434

@@ -74,6 +74,6 @@ provisionersdk/proto: provisionersdk/proto/provisioner.proto
7474
.PHONY: provisionersdk/proto
7575

7676
site/out:
77-
yarn build
78-
yarn export
77+
cd site && yarn build
78+
cd site && yarn export
7979
.PHONY: site/out

coderd/coderd.go

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,14 @@ import (
1616
type Options struct {
1717
Logger slog.Logger
1818
Database database.Store
19+
Pubsub database.Pubsub
1920
}
2021

2122
// New constructs the Coder API into an HTTP handler.
2223
func New(options *Options) http.Handler {
23-
projects := &projects{
24-
Database: options.Database,
25-
}
26-
users := &users{
27-
Database: options.Database,
28-
}
29-
workspaces := &workspaces{
24+
api := &api{
3025
Database: options.Database,
26+
Pubsub: options.Pubsub,
3127
}
3228

3329
r := chi.NewRouter()
@@ -37,38 +33,38 @@ func New(options *Options) http.Handler {
3733
Message: "👋",
3834
})
3935
})
40-
r.Post("/login", users.loginWithPassword)
41-
r.Post("/logout", users.logout)
36+
r.Post("/login", api.postLogin)
37+
r.Post("/logout", api.postLogout)
4238
// Used for setup.
43-
r.Post("/user", users.createInitialUser)
39+
r.Post("/user", api.postUser)
4440
r.Route("/users", func(r chi.Router) {
4541
r.Use(
4642
httpmw.ExtractAPIKey(options.Database, nil),
4743
)
48-
r.Post("/", users.createUser)
44+
r.Post("/", api.postUsers)
4945
r.Group(func(r chi.Router) {
5046
r.Use(httpmw.ExtractUserParam(options.Database))
51-
r.Get("/{user}", users.user)
52-
r.Get("/{user}/organizations", users.userOrganizations)
47+
r.Get("/{user}", api.userByName)
48+
r.Get("/{user}/organizations", api.organizationsByUser)
5349
})
5450
})
5551
r.Route("/projects", func(r chi.Router) {
5652
r.Use(
5753
httpmw.ExtractAPIKey(options.Database, nil),
5854
)
59-
r.Get("/", projects.allProjects)
55+
r.Get("/", api.projects)
6056
r.Route("/{organization}", func(r chi.Router) {
6157
r.Use(httpmw.ExtractOrganizationParam(options.Database))
62-
r.Get("/", projects.allProjectsForOrganization)
63-
r.Post("/", projects.createProject)
58+
r.Get("/", api.projectsByOrganization)
59+
r.Post("/", api.postProjectsByOrganization)
6460
r.Route("/{project}", func(r chi.Router) {
6561
r.Use(httpmw.ExtractProjectParam(options.Database))
66-
r.Get("/", projects.project)
62+
r.Get("/", api.projectByOrganization)
63+
r.Get("/workspaces", api.workspacesByProject)
6764
r.Route("/history", func(r chi.Router) {
68-
r.Get("/", projects.allProjectHistory)
69-
r.Post("/", projects.createProjectHistory)
65+
r.Get("/", api.projectHistoryByOrganization)
66+
r.Post("/", api.postProjectHistoryByOrganization)
7067
})
71-
r.Get("/workspaces", workspaces.allWorkspacesForProject)
7268
})
7369
})
7470
})
@@ -77,18 +73,18 @@ func New(options *Options) http.Handler {
7773
// their respective routes. eg. /orgs/<name>/workspaces
7874
r.Route("/workspaces", func(r chi.Router) {
7975
r.Use(httpmw.ExtractAPIKey(options.Database, nil))
80-
r.Get("/", workspaces.listAllWorkspaces)
76+
r.Get("/", api.workspaces)
8177
r.Route("/{user}", func(r chi.Router) {
8278
r.Use(httpmw.ExtractUserParam(options.Database))
83-
r.Get("/", workspaces.listAllWorkspaces)
84-
r.Post("/", workspaces.createWorkspaceForUser)
79+
r.Get("/", api.workspaces)
80+
r.Post("/", api.postWorkspaceByUser)
8581
r.Route("/{workspace}", func(r chi.Router) {
8682
r.Use(httpmw.ExtractWorkspaceParam(options.Database))
87-
r.Get("/", workspaces.singleWorkspace)
83+
r.Get("/", api.workspaceByUser)
8884
r.Route("/history", func(r chi.Router) {
89-
r.Post("/", workspaces.createWorkspaceHistory)
90-
r.Get("/", workspaces.listAllWorkspaceHistory)
91-
r.Get("/latest", workspaces.latestWorkspaceHistory)
85+
r.Post("/", api.postWorkspaceHistoryByUser)
86+
r.Get("/", api.workspaceHistoryByUser)
87+
r.Get("/latest", api.latestWorkspaceHistoryByUser)
9288
})
9389
})
9490
})
@@ -97,3 +93,10 @@ func New(options *Options) http.Handler {
9793
r.NotFound(site.Handler().ServeHTTP)
9894
return r
9995
}
96+
97+
// API contains all route handlers. Only HTTP handlers should
98+
// be added to this struct for code clarity.
99+
type api struct {
100+
Database database.Store
101+
Pubsub database.Pubsub
102+
}

coderd/projecthistory.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package coderd
2+
3+
import (
4+
"archive/tar"
5+
"bytes"
6+
"database/sql"
7+
"errors"
8+
"fmt"
9+
"net/http"
10+
"time"
11+
12+
"github.com/go-chi/render"
13+
"github.com/google/uuid"
14+
"github.com/moby/moby/pkg/namesgenerator"
15+
16+
"github.com/coder/coder/database"
17+
"github.com/coder/coder/httpapi"
18+
"github.com/coder/coder/httpmw"
19+
)
20+
21+
// ProjectHistory is the JSON representation of Coder project version history.
22+
type ProjectHistory struct {
23+
ID uuid.UUID `json:"id"`
24+
ProjectID uuid.UUID `json:"project_id"`
25+
CreatedAt time.Time `json:"created_at"`
26+
UpdatedAt time.Time `json:"updated_at"`
27+
Name string `json:"name"`
28+
StorageMethod database.ProjectStorageMethod `json:"storage_method"`
29+
}
30+
31+
// CreateProjectHistoryRequest enables callers to create a new Project Version.
32+
type CreateProjectHistoryRequest struct {
33+
StorageMethod database.ProjectStorageMethod `json:"storage_method" validate:"oneof=inline-archive,required"`
34+
StorageSource []byte `json:"storage_source" validate:"max=1048576,required"`
35+
}
36+
37+
// Lists history for a single project.
38+
func (api *api) projectHistoryByOrganization(rw http.ResponseWriter, r *http.Request) {
39+
project := httpmw.ProjectParam(r)
40+
41+
history, err := api.Database.GetProjectHistoryByProjectID(r.Context(), project.ID)
42+
if errors.Is(err, sql.ErrNoRows) {
43+
err = nil
44+
}
45+
if err != nil {
46+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
47+
Message: fmt.Sprintf("get project history: %s", err),
48+
})
49+
return
50+
}
51+
apiHistory := make([]ProjectHistory, 0)
52+
for _, version := range history {
53+
apiHistory = append(apiHistory, convertProjectHistory(version))
54+
}
55+
render.Status(r, http.StatusOK)
56+
render.JSON(rw, r, apiHistory)
57+
}
58+
59+
// Creates a new version of the project. An import job is queued to parse
60+
// the storage method provided. Once completed, the import job will specify
61+
// the version as latest.
62+
func (api *api) postProjectHistoryByOrganization(rw http.ResponseWriter, r *http.Request) {
63+
var createProjectVersion CreateProjectHistoryRequest
64+
if !httpapi.Read(rw, r, &createProjectVersion) {
65+
return
66+
}
67+
68+
switch createProjectVersion.StorageMethod {
69+
case database.ProjectStorageMethodInlineArchive:
70+
tarReader := tar.NewReader(bytes.NewReader(createProjectVersion.StorageSource))
71+
_, err := tarReader.Next()
72+
if err != nil {
73+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
74+
Message: "the archive must be a tar",
75+
})
76+
return
77+
}
78+
default:
79+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
80+
Message: fmt.Sprintf("unsupported storage method %s", createProjectVersion.StorageMethod),
81+
})
82+
return
83+
}
84+
85+
project := httpmw.ProjectParam(r)
86+
history, err := api.Database.InsertProjectHistory(r.Context(), database.InsertProjectHistoryParams{
87+
ID: uuid.New(),
88+
ProjectID: project.ID,
89+
CreatedAt: database.Now(),
90+
UpdatedAt: database.Now(),
91+
Name: namesgenerator.GetRandomName(1),
92+
StorageMethod: createProjectVersion.StorageMethod,
93+
StorageSource: createProjectVersion.StorageSource,
94+
// TODO: Make this do something!
95+
ImportJobID: uuid.New(),
96+
})
97+
if err != nil {
98+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
99+
Message: fmt.Sprintf("insert project history: %s", err),
100+
})
101+
return
102+
}
103+
104+
// TODO: A job to process the new version should occur here.
105+
106+
render.Status(r, http.StatusCreated)
107+
render.JSON(rw, r, convertProjectHistory(history))
108+
}
109+
110+
func convertProjectHistory(history database.ProjectHistory) ProjectHistory {
111+
return ProjectHistory{
112+
ID: history.ID,
113+
ProjectID: history.ProjectID,
114+
CreatedAt: history.CreatedAt,
115+
UpdatedAt: history.UpdatedAt,
116+
Name: history.Name,
117+
}
118+
}

0 commit comments

Comments
 (0)