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

Skip to content

refactor: Allow provisioner jobs to be disconnected from projects #194

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 8, 2022
Prev Previous commit
Next Next commit
Add files endpoint
  • Loading branch information
kylecarbs committed Feb 8, 2022
commit 2d82f895adf39435bea625443ac53da8ccd32240
5 changes: 5 additions & 0 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ func New(options *Options) http.Handler {
})
})

r.Route("/files", func(r chi.Router) {
r.Use(httpmw.ExtractAPIKey(options.Database, nil))
r.Post("/", api.postFiles)
})

r.Route("/provisioners", func(r chi.Router) {
r.Route("/daemons", func(r chi.Router) {
r.Get("/", api.provisionerDaemons)
Expand Down
60 changes: 60 additions & 0 deletions coderd/files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package coderd

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"

"github.com/go-chi/render"

"github.com/coder/coder/database"
"github.com/coder/coder/httpapi"
"github.com/coder/coder/httpmw"
)

type UploadFileResponse struct {
Hash string `json:"hash"`
}

func (api *api) postFiles(rw http.ResponseWriter, r *http.Request) {
apiKey := httpmw.APIKey(r)
contentType := r.Header.Get("Content-Type")

switch contentType {
case "application/x-tar":
default:
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: fmt.Sprintf("unsupported content type: %s", contentType),
})
return
}

r.Body = http.MaxBytesReader(rw, r.Body, 10*(10<<20))
data, err := io.ReadAll(r.Body)
if err != nil {
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: fmt.Sprintf("read file: %s", err),
})
return
}
hashBytes := sha256.Sum256(data)
file, err := api.Database.InsertFile(r.Context(), database.InsertFileParams{
Hash: hex.EncodeToString(hashBytes[:]),
CreatedBy: apiKey.UserID,
CreatedAt: database.Now(),
Mimetype: contentType,
Data: data,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("insert file: %s", err),
})
return
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, UploadFileResponse{
Hash: file.Hash,
})
}
37 changes: 37 additions & 0 deletions coderd/files_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package coderd_test

import (
"context"
"testing"

"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
"github.com/stretchr/testify/require"
)

func TestPostFiles(t *testing.T) {
t.Parallel()
t.Run("BadContentType", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.Upload(context.Background(), "bad", []byte{'a'})
require.NoError(t, err)
})

t.Run("MassiveBody", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.Upload(context.Background(), codersdk.ContentTypeTar, make([]byte, 11*(10<<20)))
require.Error(t, err)
})

t.Run("Insert", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.Upload(context.Background(), codersdk.ContentTypeTar, make([]byte, 10<<20))
require.NoError(t, err)
})
}
5 changes: 4 additions & 1 deletion codersdk/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (c *Client) SetSessionToken(token string) error {

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

resp, err := c.httpClient.Do(req)
if err != nil {
Expand Down
28 changes: 28 additions & 0 deletions codersdk/files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package codersdk

import (
"context"
"encoding/json"
"net/http"

"github.com/coder/coder/coderd"
)

const (
ContentTypeTar = "application/x-tar"
)

func (c *Client) Upload(ctx context.Context, contentType string, content []byte) (coderd.UploadFileResponse, error) {
res, err := c.request(ctx, http.MethodPost, "/api/v2/files", content, func(r *http.Request) {
r.Header.Set("Content-Type", contentType)
})
if err != nil {
return coderd.UploadFileResponse{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.UploadFileResponse{}, readBodyAsError(res)
}
var resp coderd.UploadFileResponse
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
27 changes: 27 additions & 0 deletions codersdk/files_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package codersdk_test

import (
"context"
"testing"

"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
"github.com/stretchr/testify/require"
)

func TestUpload(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
_, err := client.Upload(context.Background(), "wow", []byte{})
require.Error(t, err)
})
t.Run("Upload", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.Upload(context.Background(), codersdk.ContentTypeTar, []byte{'a'})
require.NoError(t, err)
})
}
1 change: 1 addition & 0 deletions database/databasefake/databasefake.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,7 @@ func (q *fakeQuerier) InsertFile(_ context.Context, arg database.InsertFileParam
file := database.File{
Hash: arg.Hash,
CreatedAt: arg.CreatedAt,
CreatedBy: arg.CreatedBy,
Mimetype: arg.Mimetype,
Data: arg.Data,
}
Expand Down
1 change: 1 addition & 0 deletions database/dump.sql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions database/migrations/000002_projects.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
CREATE TABLE file (
hash varchar(32) NOT NULL UNIQUE,
created_at timestamptz NOT NULL,
created_by text NOT NULL,
mimetype varchar(64) NOT NULL,
data bytea NOT NULL
);
Expand Down
1 change: 1 addition & 0 deletions database/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions database/query.sql
Original file line number Diff line number Diff line change
Expand Up @@ -364,9 +364,9 @@ VALUES

-- name: InsertFile :one
INSERT INTO
file (hash, created_at, mimetype, data)
file (hash, created_at, created_by, mimetype, data)
VALUES
($1, $2, $3, $4) RETURNING *;
($1, $2, $3, $4, $5) RETURNING *;

-- name: InsertProvisionerJobLogs :many
INSERT INTO
Expand Down
10 changes: 7 additions & 3 deletions database/query.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.