Skip to content

Commit e4d031f

Browse files
committed
Merge main
2 parents 4a5c353 + 61b3909 commit e4d031f

Some content is hidden

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

41 files changed

+1753
-1366
lines changed

coderd/coderd.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,27 @@ 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)
108113
r.Get("/serve", api.provisionerDaemonsServe)
109114
})
110-
r.Route("/jobs/{provisionerjob}", func(r chi.Router) {
111-
r.Use(httpmw.ExtractProvisionerJobParam(options.Database))
112-
r.Get("/logs", api.provisionerJobLogsByID)
115+
r.Route("/jobs/{organization}", func(r chi.Router) {
116+
r.Use(
117+
httpmw.ExtractAPIKey(options.Database, nil),
118+
httpmw.ExtractOrganizationParam(options.Database),
119+
)
120+
r.Post("/import", api.postProvisionerImportJobByOrganization)
121+
r.Route("/{provisionerjob}", func(r chi.Router) {
122+
r.Use(httpmw.ExtractProvisionerJobParam(options.Database))
123+
r.Get("/", api.provisionerJobByOrganization)
124+
r.Get("/logs", api.provisionerJobLogsByID)
125+
})
113126
})
114127
})
115128
})

coderd/coderdtest/coderdtest.go

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -122,40 +122,44 @@ func CreateInitialUser(t *testing.T, client *codersdk.Client) coderd.CreateIniti
122122
return req
123123
}
124124

125-
// CreateProject creates a project with the "echo" provisioner for
126-
// compatibility with testing. The name assigned is randomly generated.
127-
func CreateProject(t *testing.T, client *codersdk.Client, organization string) coderd.Project {
128-
project, err := client.CreateProject(context.Background(), organization, coderd.CreateProjectRequest{
129-
Name: randomUsername(),
130-
Provisioner: database.ProvisionerTypeEcho,
125+
// CreateProjectImportProvisionerJob creates a project import provisioner job
126+
// with the responses provided. It uses the "echo" provisioner for compatibility
127+
// with testing.
128+
func CreateProjectImportProvisionerJob(t *testing.T, client *codersdk.Client, organization string, res *echo.Responses) coderd.ProvisionerJob {
129+
data, err := echo.Tar(res)
130+
require.NoError(t, err)
131+
file, err := client.UploadFile(context.Background(), codersdk.ContentTypeTar, data)
132+
require.NoError(t, err)
133+
job, err := client.CreateProjectVersionImportProvisionerJob(context.Background(), organization, coderd.CreateProjectImportJobRequest{
134+
StorageSource: file.Hash,
135+
StorageMethod: database.ProvisionerStorageMethodFile,
136+
Provisioner: database.ProvisionerTypeEcho,
131137
})
132138
require.NoError(t, err)
133-
return project
139+
return job
134140
}
135141

136-
// CreateProjectVersion creates a project version for the "echo" provisioner
137-
// for compatibility with testing.
138-
func CreateProjectVersion(t *testing.T, client *codersdk.Client, organization, project string, responses *echo.Responses) coderd.ProjectVersion {
139-
data, err := echo.Tar(responses)
140-
require.NoError(t, err)
141-
version, err := client.CreateProjectVersion(context.Background(), organization, project, coderd.CreateProjectVersionRequest{
142-
StorageMethod: database.ProjectStorageMethodInlineArchive,
143-
StorageSource: data,
142+
// CreateProject creates a project with the "echo" provisioner for
143+
// compatibility with testing. The name assigned is randomly generated.
144+
func CreateProject(t *testing.T, client *codersdk.Client, organization string, job uuid.UUID) coderd.Project {
145+
project, err := client.CreateProject(context.Background(), organization, coderd.CreateProjectRequest{
146+
Name: randomUsername(),
147+
VersionImportJobID: job,
144148
})
145149
require.NoError(t, err)
146-
return version
150+
return project
147151
}
148152

149-
// AwaitProjectVersionImported awaits for the project import job to reach completed status.
150-
func AwaitProjectVersionImported(t *testing.T, client *codersdk.Client, organization, project, version string) coderd.ProjectVersion {
151-
var projectVersion coderd.ProjectVersion
153+
// AwaitProvisionerJob awaits for a job to reach completed status.
154+
func AwaitProvisionerJob(t *testing.T, client *codersdk.Client, organization string, job uuid.UUID) coderd.ProvisionerJob {
155+
var provisionerJob coderd.ProvisionerJob
152156
require.Eventually(t, func() bool {
153157
var err error
154-
projectVersion, err = client.ProjectVersion(context.Background(), organization, project, version)
158+
provisionerJob, err = client.ProvisionerJob(context.Background(), organization, job)
155159
require.NoError(t, err)
156-
return projectVersion.Import.Status.Completed()
160+
return provisionerJob.Status.Completed()
157161
}, 3*time.Second, 25*time.Millisecond)
158-
return projectVersion
162+
return provisionerJob
159163
}
160164

161165
// CreateWorkspace creates a workspace for the user and project provided.
@@ -169,18 +173,6 @@ func CreateWorkspace(t *testing.T, client *codersdk.Client, user string, project
169173
return workspace
170174
}
171175

172-
// AwaitWorkspaceHistoryProvisioned awaits for the workspace provision job to reach completed status.
173-
func AwaitWorkspaceHistoryProvisioned(t *testing.T, client *codersdk.Client, user, workspace, history string) coderd.WorkspaceHistory {
174-
var workspaceHistory coderd.WorkspaceHistory
175-
require.Eventually(t, func() bool {
176-
var err error
177-
workspaceHistory, err = client.WorkspaceHistory(context.Background(), user, workspace, history)
178-
require.NoError(t, err)
179-
return workspaceHistory.Provision.Status.Completed()
180-
}, 3*time.Second, 25*time.Millisecond)
181-
return workspaceHistory
182-
}
183-
184176
func randomUsername() string {
185177
return strings.ReplaceAll(namesgenerator.GetRandomName(0), "_", "-")
186178
}

coderd/coderdtest/coderdtest_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ func TestNew(t *testing.T) {
2222
client := coderdtest.New(t)
2323
user := coderdtest.CreateInitialUser(t, client)
2424
closer := coderdtest.NewProvisionerDaemon(t, client)
25-
project := coderdtest.CreateProject(t, client, user.Organization)
26-
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
27-
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
25+
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
26+
coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID)
27+
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
2828
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
2929
history, err := client.CreateWorkspaceHistory(context.Background(), "me", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
30-
ProjectVersionID: version.ID,
30+
ProjectVersionID: project.ActiveVersionID,
3131
Transition: database.WorkspaceTransitionStart,
3232
})
3333
require.NoError(t, err)
34-
coderdtest.AwaitWorkspaceHistoryProvisioned(t, client, "me", workspace.Name, history.Name)
34+
coderdtest.AwaitProvisionerJob(t, client, user.Organization, history.ProvisionJobID)
3535
closer.Close()
3636
}

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.StatusCreated)
57+
render.JSON(rw, r, UploadFileResponse{
58+
Hash: file.Hash,
59+
})
60+
}

coderd/files_test.go

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

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy