Skip to content

Commit 752f52e

Browse files
committed
add api routes
1 parent 5952b8e commit 752f52e

File tree

5 files changed

+259
-0
lines changed

5 files changed

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

0 commit comments

Comments
 (0)