Skip to content

Commit df0eb3e

Browse files
committed
add api routes
1 parent 0eea92f commit df0eb3e

File tree

6 files changed

+261
-1
lines changed

6 files changed

+261
-1
lines changed

coderd/coderd.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,16 @@ func New(options *Options) *API {
915915
})
916916
})
917917
})
918+
r.Route("/provisionerkeys", func(r chi.Router) {
919+
r.Get("/", api.provisionerKeys)
920+
r.Post("/", api.postProvisionerKey)
921+
r.Route("/{provisionerKey}", func(r chi.Router) {
922+
r.Use(
923+
httpmw.ExtractProvisionerKeyParam(options.Database),
924+
)
925+
r.Delete("/", api.deleteProvisionerKey)
926+
})
927+
})
918928
})
919929
})
920930
r.Route("/templates", func(r chi.Router) {

coderd/httpmw/provisionerkey.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package httpmw
2+
3+
import (
4+
"context"
5+
"net/http"
6+
7+
"github.com/go-chi/chi/v5"
8+
9+
"github.com/coder/coder/v2/coderd/database"
10+
"github.com/coder/coder/v2/coderd/httpapi"
11+
"github.com/coder/coder/v2/codersdk"
12+
)
13+
14+
type provisionerKeyParamContextKey struct{}
15+
16+
// ProvisionerKeyParam returns the user from the ExtractProvisionerKeyParam handler.
17+
func ProvisionerKeyParam(r *http.Request) database.ProvisionerKey {
18+
user, ok := r.Context().Value(userParamContextKey{}).(database.ProvisionerKey)
19+
if !ok {
20+
panic("developer error: provisioner key parameter middleware not provided")
21+
}
22+
return user
23+
}
24+
25+
// ExtractProvisionerKeyParam extracts a provisioner key from a name in the {provisionerKey} URL
26+
// parameter.
27+
func ExtractProvisionerKeyParam(db database.Store) func(http.Handler) http.Handler {
28+
return func(next http.Handler) http.Handler {
29+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
30+
ctx := r.Context()
31+
organization := OrganizationParam(r)
32+
33+
provisionerKeyQuery := chi.URLParam(r, "provisionerKey")
34+
if provisionerKeyQuery == "" {
35+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
36+
Message: "\"provisionerKey\" must be provided.",
37+
})
38+
return
39+
}
40+
41+
provisionerKey, err := db.GetProvisionerKeyByName(ctx, database.GetProvisionerKeyByNameParams{
42+
OrganizationID: organization.ID,
43+
Name: provisionerKeyQuery,
44+
})
45+
if httpapi.Is404Error(err) {
46+
httpapi.ResourceNotFound(rw)
47+
return
48+
}
49+
if err != nil {
50+
httpapi.InternalServerError(rw, err)
51+
return
52+
}
53+
54+
ctx = context.WithValue(ctx, provisionerKeyParamContextKey{}, provisionerKey)
55+
next.ServeHTTP(rw, r.WithContext(ctx))
56+
})
57+
}
58+
}

coderd/notifications/manager_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ import (
77
"testing"
88
"time"
99

10-
"github.com/coder/serpent"
1110
"github.com/google/uuid"
1211
"github.com/stretchr/testify/assert"
1312
"github.com/stretchr/testify/require"
1413
"golang.org/x/xerrors"
1514

15+
"github.com/coder/serpent"
16+
1617
"cdr.dev/slog"
1718
"cdr.dev/slog/sloggers/slogtest"
1819

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package provisionerkey
2+
3+
import (
4+
"crypto/sha256"
5+
"fmt"
6+
7+
"github.com/google/uuid"
8+
"golang.org/x/xerrors"
9+
10+
"github.com/coder/coder/v2/coderd/database"
11+
"github.com/coder/coder/v2/coderd/database/dbtime"
12+
"github.com/coder/coder/v2/cryptorand"
13+
)
14+
15+
func New(organizationID uuid.UUID, name string) (database.InsertProvisionerKeyParams, string, error) {
16+
id := uuid.New()
17+
secret, err := cryptorand.HexString(64)
18+
if err != nil {
19+
return database.InsertProvisionerKeyParams{}, "", xerrors.Errorf("generate token: %w", err)
20+
}
21+
hashedSecret := sha256.Sum256([]byte(secret))
22+
token := fmt.Sprintf("%s:%s", id, secret)
23+
24+
return database.InsertProvisionerKeyParams{
25+
ID: id,
26+
CreatedAt: dbtime.Now(),
27+
OrganizationID: organizationID,
28+
Name: name,
29+
HashedSecret: hashedSecret[:],
30+
}, token, nil
31+
}

coderd/provisionerkeys.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package coderd
2+
3+
import (
4+
"database/sql"
5+
"errors"
6+
"net/http"
7+
8+
"github.com/coder/coder/v2/coderd/database"
9+
"github.com/coder/coder/v2/coderd/httpapi"
10+
"github.com/coder/coder/v2/coderd/httpmw"
11+
"github.com/coder/coder/v2/coderd/provisionerkey"
12+
"github.com/coder/coder/v2/codersdk"
13+
)
14+
15+
func (api *API) postProvisionerKey(rw http.ResponseWriter, r *http.Request) {
16+
ctx := r.Context()
17+
organization := httpmw.OrganizationParam(r)
18+
19+
var req codersdk.CreateProvisionerKeyRequest
20+
if !httpapi.Read(ctx, rw, r, &req) {
21+
return
22+
}
23+
24+
params, token, err := provisionerkey.New(organization.ID, req.Name)
25+
if err != nil {
26+
httpapi.InternalServerError(rw, err)
27+
return
28+
}
29+
30+
_, err = api.Database.InsertProvisionerKey(ctx, params)
31+
if err != nil {
32+
httpapi.InternalServerError(rw, err)
33+
return
34+
}
35+
36+
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateProvisionerKeyResponse{
37+
Key: token,
38+
})
39+
}
40+
41+
func (api *API) provisionerKeys(rw http.ResponseWriter, r *http.Request) {
42+
ctx := r.Context()
43+
organization := httpmw.OrganizationParam(r)
44+
45+
pks, err := api.Database.ListProvisionerKeysByOrganization(ctx, organization.ID)
46+
if err != nil {
47+
httpapi.InternalServerError(rw, err)
48+
return
49+
}
50+
51+
httpapi.Write(ctx, rw, http.StatusOK, convertProvisionerKeys(pks))
52+
}
53+
54+
func (api *API) deleteProvisionerKey(rw http.ResponseWriter, r *http.Request) {
55+
ctx := r.Context()
56+
organization := httpmw.OrganizationParam(r)
57+
provisionerKey := httpmw.ProvisionerKeyParam(r)
58+
59+
pk, err := api.Database.GetProvisionerKeyByName(ctx, database.GetProvisionerKeyByNameParams{
60+
OrganizationID: organization.ID,
61+
Name: provisionerKey.Name,
62+
})
63+
if err != nil {
64+
if errors.Is(err, sql.ErrNoRows) {
65+
httpapi.ResourceNotFound(rw)
66+
return
67+
}
68+
httpapi.InternalServerError(rw, err)
69+
return
70+
}
71+
72+
err = api.Database.DeleteProvisionerKey(ctx, pk.ID)
73+
if err != nil {
74+
httpapi.InternalServerError(rw, err)
75+
return
76+
}
77+
78+
httpapi.Write(ctx, rw, http.StatusNoContent, nil)
79+
}
80+
81+
func convertProvisionerKeys(dbKeys []database.ListProvisionerKeysByOrganizationRow) []codersdk.ProvisionerKey {
82+
keys := make([]codersdk.ProvisionerKey, 0, len(dbKeys))
83+
for _, dbKey := range dbKeys {
84+
keys = append(keys, codersdk.ProvisionerKey{
85+
ID: dbKey.ID,
86+
CreatedAt: dbKey.CreatedAt,
87+
OrganizationID: dbKey.OrganizationID,
88+
Name: dbKey.Name,
89+
})
90+
}
91+
return keys
92+
}

codersdk/provisionerdaemons.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,71 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione
264264
}
265265
return proto.NewDRPCProvisionerDaemonClient(drpc.MultiplexedConn(session)), nil
266266
}
267+
268+
type ProvisionerKey struct {
269+
ID uuid.UUID `json:"id" format:"uuid"`
270+
CreatedAt time.Time `json:"created_at" format:"date-time"`
271+
OrganizationID uuid.UUID `json:"organization" format:"uuid"`
272+
Name string `json:"name"`
273+
}
274+
275+
type CreateProvisionerKeyRequest struct {
276+
Name string `json:"name"`
277+
}
278+
279+
type CreateProvisionerKeyResponse struct {
280+
Key string
281+
}
282+
283+
// CreateProvisionerKey creates a new provisioner key for an organization.
284+
func (c *Client) CreateProvisionerKey(ctx context.Context, organizationID uuid.UUID, req CreateProvisionerKeyRequest) (CreateProvisionerKeyResponse, error) {
285+
res, err := c.Request(ctx, http.MethodPost,
286+
fmt.Sprintf("/api/v2/organizations/%s/provisionerkeys", organizationID.String()),
287+
req,
288+
)
289+
if err != nil {
290+
return CreateProvisionerKeyResponse{}, xerrors.Errorf("make request: %w", err)
291+
}
292+
defer res.Body.Close()
293+
294+
if res.StatusCode != http.StatusCreated {
295+
return CreateProvisionerKeyResponse{}, ReadBodyAsError(res)
296+
}
297+
var resp CreateProvisionerKeyResponse
298+
return resp, json.NewDecoder(res.Body).Decode(&resp)
299+
}
300+
301+
// ListProvisionerKeys lists all provisioner keys for an organization.
302+
func (c *Client) ListProvisionerKeys(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error) {
303+
res, err := c.Request(ctx, http.MethodPost,
304+
fmt.Sprintf("/api/v2/organizations/%s/provisionerkeys", organizationID.String()),
305+
nil,
306+
)
307+
if err != nil {
308+
return nil, xerrors.Errorf("make request: %w", err)
309+
}
310+
defer res.Body.Close()
311+
312+
if res.StatusCode != http.StatusCreated {
313+
return nil, ReadBodyAsError(res)
314+
}
315+
var resp []ProvisionerKey
316+
return resp, json.NewDecoder(res.Body).Decode(&resp)
317+
}
318+
319+
// DeleteProvisionerKey deletes a provisioner key.
320+
func (c *Client) DeleteProvisionerKey(ctx context.Context, organizationID uuid.UUID, name string) error {
321+
res, err := c.Request(ctx, http.MethodDelete,
322+
fmt.Sprintf("/api/v2/organizations/%s/provisionerkeys/%s", organizationID.String(), name),
323+
nil,
324+
)
325+
if err != nil {
326+
return xerrors.Errorf("make request: %w", err)
327+
}
328+
defer res.Body.Close()
329+
330+
if res.StatusCode != http.StatusNoContent {
331+
return ReadBodyAsError(res)
332+
}
333+
return nil
334+
}

0 commit comments

Comments
 (0)