Skip to content

Commit

Permalink
Merge pull request #77 from whywaita/feat/change-resource-type
Browse files Browse the repository at this point in the history
Implement update target
  • Loading branch information
whywaita authored Jul 21, 2021
2 parents 8edab30 + c92d4bd commit 98029fd
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 3 deletions.
2 changes: 2 additions & 0 deletions pkg/datastore/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type Datastore interface {
UpdateTargetStatus(ctx context.Context, targetID uuid.UUID, newStatus TargetStatus, description string) error
UpdateToken(ctx context.Context, targetID uuid.UUID, newToken string, newExpiredAt time.Time) error

UpdateResourceType(ctx context.Context, targetID uuid.UUID, newResourceType ResourceType) error

EnqueueJob(ctx context.Context, job Job) error
ListJobs(ctx context.Context) ([]Job, error)
DeleteJob(ctx context.Context, id uuid.UUID) error
Expand Down
15 changes: 15 additions & 0 deletions pkg/datastore/memory/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,21 @@ func (m *Memory) UpdateToken(ctx context.Context, targetID uuid.UUID, newToken s
return nil
}

// UpdateResourceType update resource_type in target
func (m *Memory) UpdateResourceType(ctx context.Context, targetID uuid.UUID, newResourceType datastore.ResourceType) error {
m.mu.Lock()
defer m.mu.Unlock()

t, ok := m.targets[targetID]
if !ok {
return fmt.Errorf("not found")
}
t.ResourceType = newResourceType

m.targets[targetID] = t
return nil
}

// EnqueueJob add a job
func (m *Memory) EnqueueJob(ctx context.Context, job datastore.Job) error {
m.mu.Lock()
Expand Down
10 changes: 10 additions & 0 deletions pkg/datastore/mysql/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,13 @@ func (m *MySQL) UpdateToken(ctx context.Context, targetID uuid.UUID, newToken st

return nil
}

// UpdateResourceType update resource type in target
func (m *MySQL) UpdateResourceType(ctx context.Context, targetID uuid.UUID, newResourceType datastore.ResourceType) error {
query := `UPDATE targets SET resource_type = ? WHERE uuid = ?`
if _, err := m.Conn.ExecContext(ctx, query, newResourceType, targetID.String()); err != nil {
return fmt.Errorf("failed to execute UPDATE query: %w", err)
}

return nil
}
101 changes: 101 additions & 0 deletions pkg/datastore/mysql/target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,107 @@ func TestMySQL_UpdateToken(t *testing.T) {
}
}

func TestMySQL_UpdateResourceType(t *testing.T) {
testDatastore, teardown := testutils.GetTestDatastore()
defer teardown()
testDB, _ := testutils.GetTestDB()

tests := []struct {
input datastore.ResourceType
want *datastore.Target
err bool
}{
{
input: datastore.ResourceTypeNano,
want: &datastore.Target{
Scope: testScopeRepo,
GitHubToken: testGitHubToken,
ResourceType: datastore.ResourceTypeLarge,
RunnerVersion: sql.NullString{
String: testRunnerVersion,
Valid: true,
},
ProviderURL: sql.NullString{
String: testProviderURL,
Valid: true,
},
Status: datastore.TargetStatusActive,
StatusDescription: sql.NullString{
String: "",
Valid: false,
},
},
err: false,
},
{
input: datastore.ResourceType4XLarge,
want: &datastore.Target{
Scope: testScopeRepo,
GitHubToken: testGitHubToken,
ResourceType: datastore.ResourceTypeLarge,
RunnerVersion: sql.NullString{
String: testRunnerVersion,
Valid: true,
},
ProviderURL: sql.NullString{
String: testProviderURL,
Valid: true,
},
Status: datastore.TargetStatusActive,
StatusDescription: sql.NullString{
String: "",
Valid: false,
},
},
err: false,
},
}

for _, test := range tests {
tID := uuid.NewV4()
if err := testDatastore.CreateTarget(context.Background(), datastore.Target{
UUID: tID,
Scope: testScopeRepo,
GitHubToken: testGitHubToken,
TokenExpiredAt: testTime,
ResourceType: test.input,
RunnerVersion: sql.NullString{
String: testRunnerVersion,
Valid: true,
},
ProviderURL: sql.NullString{
String: testProviderURL,
Valid: true,
},
}); err != nil {
t.Fatalf("failed to create target: %+v", err)
}

if err := testDatastore.UpdateResourceType(context.Background(), tID, test.want.ResourceType); err != nil {
t.Fatalf("failed to UpdateResourceTyoe: %+v", err)
}

got, err := getTargetFromSQL(testDB, tID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
t.Fatalf("failed to get target from SQL: %+v", err)
}
if got != nil {
got.UUID = uuid.UUID{}
got.CreatedAt = time.Time{}
got.UpdatedAt = time.Time{}
got.TokenExpiredAt = time.Time{}
}

if diff := cmp.Diff(test.want, got); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
}

if err := testDatastore.DeleteTarget(context.Background(), tID); err != nil {
t.Fatalf("failed to delete target: %+v", err)
}
}
}

func getTargetFromSQL(testDB *sqlx.DB, uuid uuid.UUID) (*datastore.Target, error) {
var t datastore.Target
query := `SELECT uuid, scope, ghe_domain, github_token, token_expired_at, resource_type, runner_user, runner_version, provider_url, status, status_description, created_at, updated_at FROM targets WHERE uuid = ?`
Expand Down
2 changes: 1 addition & 1 deletion pkg/web/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func NewMux(ds datastore.Datastore) *goji.Mux {
apacheLogging(r)
handleTargetRead(w, r, ds)
})
mux.HandleFunc(pat.Put("/target/:id"), func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc(pat.Post("/target/:id"), func(w http.ResponseWriter, r *http.Request) {
apacheLogging(r)
handleTargetUpdate(w, r, ds)
})
Expand Down
73 changes: 72 additions & 1 deletion pkg/web/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"strings"
"time"

"github.com/google/go-cmp/cmp"

"github.com/whywaita/myshoes/pkg/datastore"
"github.com/whywaita/myshoes/pkg/gh"
"github.com/whywaita/myshoes/pkg/logger"
Expand Down Expand Up @@ -193,7 +195,51 @@ func sanitizeTarget(t *datastore.Target) UserTarget {
}

func handleTargetUpdate(w http.ResponseWriter, r *http.Request, ds datastore.Datastore) {
outputErrorMsg(w, http.StatusMethodNotAllowed, "not implement")
ctx := r.Context()
targetID, err := parseReqTargetID(r)
if err != nil {
logger.Logf(false, "failed to decode request body: %+v", err)
outputErrorMsg(w, http.StatusBadRequest, "incorrect target id")
return
}

inputTarget := TargetCreateParam{}
if err := json.NewDecoder(r.Body).Decode(&inputTarget); err != nil {
logger.Logf(false, "failed to decode request body: %+v", err)
outputErrorMsg(w, http.StatusBadRequest, "json decode error")
return
}
newTarget := inputTarget.ToDS("", time.Time{})

oldTarget, err := ds.GetTarget(ctx, targetID)
if err != nil {
logger.Logf(false, "failed to get target: %+v", err)
outputErrorMsg(w, http.StatusBadRequest, "incorrect target id (not found)")
return
}
if err := validateUpdateTarget(oldTarget, &newTarget); err != nil {
logger.Logf(false, "input error in validateUpdateTarget: %+v", err)
outputErrorMsg(w, http.StatusBadRequest, "request parameter has value of not updatable")
return
}

if err := ds.UpdateResourceType(ctx, targetID, inputTarget.ResourceType); err != nil {
logger.Logf(false, "failed to ds.UpdateResourceType: %+v", err)
outputErrorMsg(w, http.StatusInternalServerError, "datastore update error")
return
}

updatedTarget, err := ds.GetTarget(ctx, targetID)
if err != nil {
logger.Logf(false, "failed to get recently target in datastore: %+v", err)
outputErrorMsg(w, http.StatusInternalServerError, "datastore get error")
return
}
ut := sanitizeTarget(updatedTarget)

w.Header().Set("Content-Type", "application/json;charset=utf-8")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(ut)
return
}

Expand Down Expand Up @@ -252,3 +298,28 @@ func outputErrorMsg(w http.ResponseWriter, status int, msg string) {
Error string `json:"error"`
}{Error: msg})
}

// validateUpdateTarget check input target that can valid input in update.
func validateUpdateTarget(old, new *datastore.Target) error {
for _, t := range []*datastore.Target{old, new} {
t.UUID = uuid.UUID{}

// can update variables
t.ResourceType = datastore.ResourceTypeUnknown

// time
t.TokenExpiredAt = time.Time{}
t.CreatedAt = time.Time{}
t.UpdatedAt = time.Time{}

// generated
t.Status = ""
t.GitHubToken = ""
}

if diff := cmp.Diff(old, new); diff != "" {
return fmt.Errorf("mismatch (-want +got):\n%s", diff)
}

return nil
}
7 changes: 6 additions & 1 deletion pkg/web/target_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,17 @@ func handleTargetCreate(w http.ResponseWriter, r *http.Request, ds datastore.Dat
return
case target.Status == datastore.TargetStatusDeleted:
// deleted, need to recreate
// TODO: need to update resource
if err := ds.UpdateTargetStatus(ctx, target.UUID, datastore.TargetStatusActive, ""); err != nil {
logger.Logf(false, "failed to recreate target: %+v", err)
outputErrorMsg(w, http.StatusInternalServerError, "datastore recreate error")
return
}
if err := ds.UpdateResourceType(ctx, target.UUID, t.ResourceType); err != nil {
logger.Logf(false, "failed to update resource type in recreating target: %+v", err)
outputErrorMsg(w, http.StatusInternalServerError, "update resource type error")
return
}

targetUUID = target.UUID
}

Expand Down
65 changes: 65 additions & 0 deletions pkg/web/target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,71 @@ func Test_handleTargetRead(t *testing.T) {
}
}

func Test_handleTargetUpdate(t *testing.T) {
testURL := testutils.GetTestURL()
_, teardown := testutils.GetTestDatastore()
defer teardown()

setStubFunctions()

target := `{"scope": "repo", "resource_type": "micro", "runner_user": "ubuntu"}`

resp, err := http.Post(testURL+"/target", "application/json", bytes.NewBufferString(target))
if err != nil {
t.Fatalf("failed to POST request: %+v", err)
}
content, statusCode := parseResponse(resp)
if statusCode != http.StatusCreated {
t.Fatalf("must be response statuscode is 201, but got %d: %+v", resp.StatusCode, string(content))
}
var respTarget web.UserTarget
if err := json.Unmarshal(content, &respTarget); err != nil {
t.Fatalf("failed to unmarshal response JSON: %+v", err)
}
targetUUID := respTarget.UUID

tests := []struct {
input string
want *web.UserTarget
err bool
}{
{
input: `{"scope": "repo", "resource_type": "nano", "runner_user": "ubuntu"}`,
want: &web.UserTarget{
UUID: targetUUID,
Scope: "repo",
TokenExpiredAt: testTime,
ResourceType: datastore.ResourceTypeNano.String(),
RunnerUser: "ubuntu",
Status: datastore.TargetStatusActive,
},
},
}

for _, test := range tests {
resp, err := http.Post(fmt.Sprintf("%s/target/%s", testURL, targetUUID.String()), "application/json", bytes.NewBufferString(test.input))
if !test.err && err != nil {
t.Fatalf("failed to POST request: %+v", err)
}
content, code := parseResponse(resp)
if code != http.StatusOK {
t.Fatalf("must be response statuscode is 201, but got %d: %+v", code, string(content))
}

var got web.UserTarget
if err := json.Unmarshal(content, &got); err != nil {
t.Fatalf("failed to unmarshal resoponse content: %+v", err)
}

got.CreatedAt = time.Time{}
got.UpdatedAt = time.Time{}

if diff := cmp.Diff(test.want, &got); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
}
}
}

func Test_handleTargetDelete(t *testing.T) {
testURL := testutils.GetTestURL()
testDatastore, teardown := testutils.GetTestDatastore()
Expand Down

0 comments on commit 98029fd

Please sign in to comment.