Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2d82f89

Browse files
committed
Add files endpoint
1 parent b1e493f commit 2d82f89

File tree

12 files changed

+174
-6
lines changed

12 files changed

+174
-6
lines changed

coderd/coderd.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ func New(options *Options) http.Handler {
102102
})
103103
})
104104

105+
r.Route("/files", func(r chi.Router) {
106+
r.Use(httpmw.ExtractAPIKey(options.Database, nil))
107+
r.Post("/", api.postFiles)
108+
})
109+
105110
r.Route("/provisioners", func(r chi.Router) {
106111
r.Route("/daemons", func(r chi.Router) {
107112
r.Get("/", api.provisionerDaemons)

coderd/files.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package coderd
2+
3+
import (
4+
"crypto/sha256"
5+
"encoding/hex"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
10+
"github.com/go-chi/render"
11+
12+
"github.com/coder/coder/database"
13+
"github.com/coder/coder/httpapi"
14+
"github.com/coder/coder/httpmw"
15+
)
16+
17+
type UploadFileResponse struct {
18+
Hash string `json:"hash"`
19+
}
20+
21+
func (api *api) postFiles(rw http.ResponseWriter, r *http.Request) {
22+
apiKey := httpmw.APIKey(r)
23+
contentType := r.Header.Get("Content-Type")
24+
25+
switch contentType {
26+
case "application/x-tar":
27+
default:
28+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
29+
Message: fmt.Sprintf("unsupported content type: %s", contentType),
30+
})
31+
return
32+
}
33+
34+
r.Body = http.MaxBytesReader(rw, r.Body, 10*(10<<20))
35+
data, err := io.ReadAll(r.Body)
36+
if err != nil {
37+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
38+
Message: fmt.Sprintf("read file: %s", err),
39+
})
40+
return
41+
}
42+
hashBytes := sha256.Sum256(data)
43+
file, err := api.Database.InsertFile(r.Context(), database.InsertFileParams{
44+
Hash: hex.EncodeToString(hashBytes[:]),
45+
CreatedBy: apiKey.UserID,
46+
CreatedAt: database.Now(),
47+
Mimetype: contentType,
48+
Data: data,
49+
})
50+
if err != nil {
51+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
52+
Message: fmt.Sprintf("insert file: %s", err),
53+
})
54+
return
55+
}
56+
render.Status(r, http.StatusOK)
57+
render.JSON(rw, r, UploadFileResponse{
58+
Hash: file.Hash,
59+
})
60+
}

coderd/files_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package coderd_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/coder/coder/coderd/coderdtest"
8+
"github.com/coder/coder/codersdk"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestPostFiles(t *testing.T) {
13+
t.Parallel()
14+
t.Run("BadContentType", func(t *testing.T) {
15+
t.Parallel()
16+
client := coderdtest.New(t)
17+
_ = coderdtest.CreateInitialUser(t, client)
18+
_, err := client.Upload(context.Background(), "bad", []byte{'a'})
19+
require.NoError(t, err)
20+
})
21+
22+
t.Run("MassiveBody", func(t *testing.T) {
23+
t.Parallel()
24+
client := coderdtest.New(t)
25+
_ = coderdtest.CreateInitialUser(t, client)
26+
_, err := client.Upload(context.Background(), codersdk.ContentTypeTar, make([]byte, 11*(10<<20)))
27+
require.Error(t, err)
28+
})
29+
30+
t.Run("Insert", func(t *testing.T) {
31+
t.Parallel()
32+
client := coderdtest.New(t)
33+
_ = coderdtest.CreateInitialUser(t, client)
34+
_, err := client.Upload(context.Background(), codersdk.ContentTypeTar, make([]byte, 10<<20))
35+
require.NoError(t, err)
36+
})
37+
}

codersdk/client.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (c *Client) SetSessionToken(token string) error {
5151

5252
// request performs an HTTP request with the body provided.
5353
// The caller is responsible for closing the response body.
54-
func (c *Client) request(ctx context.Context, method, path string, body interface{}) (*http.Response, error) {
54+
func (c *Client) request(ctx context.Context, method, path string, body interface{}, opts ...func(r *http.Request)) (*http.Response, error) {
5555
serverURL, err := c.URL.Parse(path)
5656
if err != nil {
5757
return nil, xerrors.Errorf("parse url: %w", err)
@@ -74,6 +74,9 @@ func (c *Client) request(ctx context.Context, method, path string, body interfac
7474
if body != nil {
7575
req.Header.Set("Content-Type", "application/json")
7676
}
77+
for _, opt := range opts {
78+
opt(req)
79+
}
7780

7881
resp, err := c.httpClient.Do(req)
7982
if err != nil {

codersdk/files.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package codersdk
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
8+
"github.com/coder/coder/coderd"
9+
)
10+
11+
const (
12+
ContentTypeTar = "application/x-tar"
13+
)
14+
15+
func (c *Client) Upload(ctx context.Context, contentType string, content []byte) (coderd.UploadFileResponse, error) {
16+
res, err := c.request(ctx, http.MethodPost, "/api/v2/files", content, func(r *http.Request) {
17+
r.Header.Set("Content-Type", contentType)
18+
})
19+
if err != nil {
20+
return coderd.UploadFileResponse{}, err
21+
}
22+
defer res.Body.Close()
23+
if res.StatusCode != http.StatusCreated {
24+
return coderd.UploadFileResponse{}, readBodyAsError(res)
25+
}
26+
var resp coderd.UploadFileResponse
27+
return resp, json.NewDecoder(res.Body).Decode(&resp)
28+
}

codersdk/files_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package codersdk_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/coder/coder/coderd/coderdtest"
8+
"github.com/coder/coder/codersdk"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestUpload(t *testing.T) {
13+
t.Parallel()
14+
t.Run("Error", func(t *testing.T) {
15+
t.Parallel()
16+
client := coderdtest.New(t)
17+
_, err := client.Upload(context.Background(), "wow", []byte{})
18+
require.Error(t, err)
19+
})
20+
t.Run("Upload", func(t *testing.T) {
21+
t.Parallel()
22+
client := coderdtest.New(t)
23+
_ = coderdtest.CreateInitialUser(t, client)
24+
_, err := client.Upload(context.Background(), codersdk.ContentTypeTar, []byte{'a'})
25+
require.NoError(t, err)
26+
})
27+
}

database/databasefake/databasefake.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ func (q *fakeQuerier) InsertFile(_ context.Context, arg database.InsertFileParam
590590
file := database.File{
591591
Hash: arg.Hash,
592592
CreatedAt: arg.CreatedAt,
593+
CreatedBy: arg.CreatedBy,
593594
Mimetype: arg.Mimetype,
594595
Data: arg.Data,
595596
}

database/dump.sql

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

database/migrations/000002_projects.up.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
CREATE TABLE file (
33
hash varchar(32) NOT NULL UNIQUE,
44
created_at timestamptz NOT NULL,
5+
created_by text NOT NULL,
56
mimetype varchar(64) NOT NULL,
67
data bytea NOT NULL
78
);

database/models.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

database/query.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,9 +364,9 @@ VALUES
364364

365365
-- name: InsertFile :one
366366
INSERT INTO
367-
file (hash, created_at, mimetype, data)
367+
file (hash, created_at, created_by, mimetype, data)
368368
VALUES
369-
($1, $2, $3, $4) RETURNING *;
369+
($1, $2, $3, $4, $5) RETURNING *;
370370

371371
-- name: InsertProvisionerJobLogs :many
372372
INSERT INTO

database/query.sql.go

Lines changed: 7 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)