Skip to content

Commit 76903af

Browse files
committed
feat(image): add stage import endpoints
1 parent 640b227 commit 76903af

5 files changed

Lines changed: 171 additions & 0 deletions

File tree

internal/api/image/data.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,52 @@ func (h Handler) uploadImageData(w http.ResponseWriter, r *http.Request) {
3030
w.WriteHeader(http.StatusNoContent)
3131
}
3232

33+
func (h Handler) stageImageData(w http.ResponseWriter, r *http.Request) {
34+
data, err := io.ReadAll(r.Body)
35+
if err != nil {
36+
respond.Error(w, http.StatusBadRequest, "invalid image data")
37+
return
38+
}
39+
40+
err = h.service.StageData(chi.URLParam(r, "image_id"), data)
41+
if errors.Is(err, appimage.ErrImageNotFound) {
42+
respond.Error(w, http.StatusNotFound, "image not found")
43+
return
44+
}
45+
if err != nil {
46+
respond.Error(w, http.StatusInternalServerError, "image data stage failed")
47+
return
48+
}
49+
50+
w.WriteHeader(http.StatusNoContent)
51+
}
52+
53+
func (h Handler) importImageData(w http.ResponseWriter, r *http.Request) {
54+
err := h.service.ImportData(chi.URLParam(r, "image_id"))
55+
if errors.Is(err, appimage.ErrImageNotFound) {
56+
respond.Error(w, http.StatusNotFound, "image not found")
57+
return
58+
}
59+
if err != nil {
60+
respond.Error(w, http.StatusInternalServerError, "image import failed")
61+
return
62+
}
63+
64+
w.WriteHeader(http.StatusAccepted)
65+
}
66+
67+
func (h Handler) getImportInfo(w http.ResponseWriter, r *http.Request) {
68+
respond.JSON(w, http.StatusOK, importInfoResponse{
69+
ImportMethods: importMethodsDocument{
70+
Description: "Import methods available.",
71+
Type: "array",
72+
Value: []string{
73+
"glance-direct",
74+
},
75+
},
76+
})
77+
}
78+
3379
func (h Handler) downloadImageData(w http.ResponseWriter, r *http.Request) {
3480
data, err := h.service.DownloadData(chi.URLParam(r, "image_id"))
3581
if errors.Is(err, appimage.ErrImageNotFound) {

internal/api/image/import_dto.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package image
2+
3+
type importInfoResponse struct {
4+
ImportMethods importMethodsDocument `json:"import-methods"`
5+
}
6+
7+
type importMethodsDocument struct {
8+
Description string `json:"description"`
9+
Type string `json:"type"`
10+
Value []string `json:"value"`
11+
}

internal/api/image/import_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package image_test
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/JSYoo5B/SandStack/internal/api/image"
10+
"github.com/JSYoo5B/SandStack/internal/testhelper"
11+
"github.com/gophercloud/gophercloud/v2/openstack/image/v2/imagedata"
12+
"github.com/gophercloud/gophercloud/v2/openstack/image/v2/imageimport"
13+
"github.com/gophercloud/gophercloud/v2/openstack/image/v2/images"
14+
"github.com/stretchr/testify/suite"
15+
)
16+
17+
type ImportSuite struct {
18+
suite.Suite
19+
server *httptest.Server
20+
}
21+
22+
func TestImportSuite(t *testing.T) {
23+
suite.Run(t, new(ImportSuite))
24+
}
25+
26+
func (s *ImportSuite) SetupTest() {
27+
s.server = httptest.NewServer(
28+
image.NewRouter(testhelper.DefaultConfig()),
29+
)
30+
}
31+
32+
func (s *ImportSuite) TearDownTest() {
33+
s.server.Close()
34+
}
35+
36+
func (s *ImportSuite) TestGetImportInfo() {
37+
client := testhelper.ServiceClient(s.server.URL)
38+
39+
info, err := imageimport.Get(context.Background(), client).Extract()
40+
s.Require().NoError(err)
41+
42+
s.Assert().Contains(info.ImportMethods.Value, "glance-direct")
43+
}
44+
45+
func (s *ImportSuite) TestStageAndImportImageData() {
46+
client := testhelper.ServiceClient(s.server.URL)
47+
48+
created, err := images.Create(
49+
context.Background(),
50+
client,
51+
images.CreateOpts{Name: "ubuntu"},
52+
).Extract()
53+
s.Require().NoError(err)
54+
55+
err = imagedata.Stage(
56+
context.Background(),
57+
client,
58+
created.ID,
59+
bytes.NewBufferString("image-bytes"),
60+
).ExtractErr()
61+
s.Require().NoError(err)
62+
63+
staged, err := images.Get(context.Background(), client, created.ID).Extract()
64+
s.Require().NoError(err)
65+
s.Assert().Equal(images.ImageStatus("uploading"), staged.Status)
66+
67+
err = imageimport.Create(
68+
context.Background(),
69+
client,
70+
created.ID,
71+
imageimport.CreateOpts{Name: imageimport.GlanceDirectMethod},
72+
).ExtractErr()
73+
s.Require().NoError(err)
74+
75+
imported, err := images.Get(context.Background(), client, created.ID).Extract()
76+
s.Require().NoError(err)
77+
s.Assert().Equal(images.ImageStatusActive, imported.Status)
78+
}

internal/api/image/router.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ func (h Handler) Router() http.Handler {
6060
router.Delete("/images/{image_id}", h.deleteImage)
6161
router.Put("/images/{image_id}/file", h.uploadImageData)
6262
router.Get("/images/{image_id}/file", h.downloadImageData)
63+
router.Put("/images/{image_id}/stage", h.stageImageData)
64+
router.Post("/images/{image_id}/import", h.importImageData)
65+
router.Get("/info/import", h.getImportInfo)
6366
router.Get("/images/{image_id}/members", h.listMembers)
6467
router.Post("/images/{image_id}/members", h.createMember)
6568
router.Get("/images/{image_id}/members/{member_id}", h.getMember)

internal/app/image/data.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,39 @@ func (s *Service) UploadData(id string, data []byte) error {
2121
return nil
2222
}
2323

24+
func (s *Service) StageData(id string, data []byte) error {
25+
image, err := s.repository.Get(id)
26+
if err != nil {
27+
return err
28+
}
29+
30+
now := s.clock.Now().UTC()
31+
image.Status = "uploading"
32+
image.SizeBytes = int64(len(data))
33+
image.UpdatedAt = now.Format(time.RFC3339)
34+
35+
if _, err := s.repository.Update(image); err != nil {
36+
return err
37+
}
38+
s.dataRepository.Put(id, data)
39+
40+
return nil
41+
}
42+
43+
func (s *Service) ImportData(id string) error {
44+
image, err := s.repository.Get(id)
45+
if err != nil {
46+
return err
47+
}
48+
49+
now := s.clock.Now().UTC()
50+
image.Status = "active"
51+
image.UpdatedAt = now.Format(time.RFC3339)
52+
53+
_, err = s.repository.Update(image)
54+
return err
55+
}
56+
2457
func (s *Service) DownloadData(id string) ([]byte, error) {
2558
if _, err := s.repository.Get(id); err != nil {
2659
return nil, err

0 commit comments

Comments
 (0)