*: add organizations
This commit is contained in:
parent
e6595b2dba
commit
41002efbff
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2019 Sorint.lab
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdOrg = &cobra.Command{
|
||||
Use: "org",
|
||||
Short: "org",
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdAgola.AddCommand(cmdOrg)
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright 2019 Sorint.lab
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sorintlab/agola/internal/services/gateway/api"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdOrgCreate = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "create an organization",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := orgCreate(cmd, args); err != nil {
|
||||
log.Fatalf("err: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
type orgCreateOptions struct {
|
||||
name string
|
||||
}
|
||||
|
||||
var orgCreateOpts orgCreateOptions
|
||||
|
||||
func init() {
|
||||
flags := cmdOrgCreate.Flags()
|
||||
|
||||
flags.StringVarP(&orgCreateOpts.name, "name", "n", "", "organization name")
|
||||
|
||||
cmdOrgCreate.MarkFlagRequired("name")
|
||||
|
||||
cmdOrg.AddCommand(cmdOrgCreate)
|
||||
}
|
||||
|
||||
func orgCreate(cmd *cobra.Command, args []string) error {
|
||||
gwclient := api.NewClient(gatewayURL, token)
|
||||
|
||||
req := &api.CreateOrgRequest{
|
||||
Name: orgCreateOpts.name,
|
||||
}
|
||||
|
||||
log.Infof("creating org")
|
||||
org, _, err := gwclient.CreateOrg(context.TODO(), req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to create org")
|
||||
}
|
||||
log.Infof("org %q created, ID: %q", org.Name, org.ID)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2019 Sorint.lab
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sorintlab/agola/internal/services/gateway/api"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdOrgDelete = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "delete an organization",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := orgDelete(cmd, args); err != nil {
|
||||
log.Fatalf("err: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
type orgDeleteOptions struct {
|
||||
name string
|
||||
}
|
||||
|
||||
var orgDeleteOpts orgDeleteOptions
|
||||
|
||||
func init() {
|
||||
flags := cmdOrgDelete.Flags()
|
||||
|
||||
flags.StringVarP(&orgDeleteOpts.name, "name", "n", "", "organization name")
|
||||
|
||||
cmdOrgDelete.MarkFlagRequired("name")
|
||||
|
||||
cmdOrg.AddCommand(cmdOrgDelete)
|
||||
}
|
||||
|
||||
func orgDelete(cmd *cobra.Command, args []string) error {
|
||||
gwclient := api.NewClient(gatewayURL, token)
|
||||
|
||||
log.Infof("deleting organization %q", orgDeleteOpts.name)
|
||||
if _, err := gwclient.DeleteOrg(context.TODO(), orgDeleteOpts.name); err != nil {
|
||||
return errors.Wrapf(err, "failed to delete organization")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -19,6 +19,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sorintlab/agola/internal/services/gateway/api"
|
||||
"github.com/sorintlab/agola/internal/services/types"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -35,6 +36,7 @@ var cmdProjectCreate = &cobra.Command{
|
|||
|
||||
type projectCreateOptions struct {
|
||||
name string
|
||||
organizationName string
|
||||
repoURL string
|
||||
remoteSourceName string
|
||||
skipSSHHostKeyCheck bool
|
||||
|
@ -49,6 +51,7 @@ func init() {
|
|||
flags.StringVar(&projectCreateOpts.repoURL, "repo-url", "", "repository url")
|
||||
flags.StringVar(&projectCreateOpts.remoteSourceName, "remote-source", "", "remote source name")
|
||||
flags.BoolVarP(&projectCreateOpts.skipSSHHostKeyCheck, "skip-ssh-host-key-check", "s", false, "skip ssh host key check")
|
||||
flags.StringVar(&projectCreateOpts.organizationName, "orgname", "", "organization name where the project should be created")
|
||||
|
||||
cmdProjectCreate.MarkFlagRequired("name")
|
||||
cmdProjectCreate.MarkFlagRequired("repo-url")
|
||||
|
@ -68,7 +71,14 @@ func projectCreate(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
log.Infof("creating project")
|
||||
project, _, err := gwclient.CreateProject(context.TODO(), req)
|
||||
|
||||
var project *types.Project
|
||||
var err error
|
||||
if projectCreateOpts.organizationName != "" {
|
||||
project, _, err = gwclient.CreateOrgProject(context.TODO(), projectCreateOpts.organizationName, req)
|
||||
} else {
|
||||
project, _, err = gwclient.CreateCurrentUserProject(context.TODO(), req)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to create project")
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ var cmdProjectDelete = &cobra.Command{
|
|||
|
||||
type projectDeleteOptions struct {
|
||||
name string
|
||||
organizationName string
|
||||
}
|
||||
|
||||
var projectDeleteOpts projectDeleteOptions
|
||||
|
@ -43,6 +44,7 @@ func init() {
|
|||
flags := cmdProjectDelete.Flags()
|
||||
|
||||
flags.StringVarP(&projectDeleteOpts.name, "name", "n", "", "project name")
|
||||
flags.StringVar(&projectDeleteOpts.organizationName, "orgname", "", "organization name where the project should be deleted")
|
||||
|
||||
cmdProjectDelete.MarkFlagRequired("name")
|
||||
|
||||
|
@ -53,7 +55,14 @@ func projectDelete(cmd *cobra.Command, args []string) error {
|
|||
gwclient := api.NewClient(gatewayURL, token)
|
||||
|
||||
log.Infof("deleting project")
|
||||
if _, err := gwclient.DeleteProject(context.TODO(), projectDeleteOpts.name); err != nil {
|
||||
|
||||
var err error
|
||||
if projectDeleteOpts.organizationName != "" {
|
||||
_, err = gwclient.DeleteOrgProject(context.TODO(), projectDeleteOpts.organizationName, projectDeleteOpts.name)
|
||||
} else {
|
||||
_, err = gwclient.DeleteCurrentUserProject(context.TODO(), projectDeleteOpts.name)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete project")
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ func printProjects(projectsResponse *api.GetProjectsResponse) {
|
|||
func projectList(cmd *cobra.Command, args []string) error {
|
||||
gwclient := api.NewClient(gatewayURL, token)
|
||||
|
||||
projectsResponse, _, err := gwclient.GetProjects(context.TODO(), projectListOpts.start, projectListOpts.limit, false)
|
||||
projectsResponse, _, err := gwclient.GetCurrentUserProjects(context.TODO(), projectListOpts.start, projectListOpts.limit, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -45,8 +45,6 @@ func init() {
|
|||
flags.StringVarP(&userCreateOpts.username, "username", "n", "", "user name")
|
||||
|
||||
cmdUserCreate.MarkFlagRequired("username")
|
||||
cmdUserCreate.MarkFlagRequired("repo-url")
|
||||
cmdUserCreate.MarkFlagRequired("token")
|
||||
|
||||
cmdUser.AddCommand(cmdUserCreate)
|
||||
}
|
||||
|
|
|
@ -114,9 +114,9 @@ func (c *Client) GetProject(ctx context.Context, projectID string) (*types.Proje
|
|||
return project, resp, err
|
||||
}
|
||||
|
||||
func (c *Client) GetProjectByName(ctx context.Context, projectName string) (*types.Project, *http.Response, error) {
|
||||
func (c *Client) GetProjectByName(ctx context.Context, ownerid, projectName string) (*types.Project, *http.Response, error) {
|
||||
project := new(types.Project)
|
||||
resp, err := c.getParsedResponse(ctx, "GET", fmt.Sprintf("/projects/%s", projectName), nil, jsonContent, nil, project)
|
||||
resp, err := c.getParsedResponse(ctx, "GET", fmt.Sprintf("/projects/%s/%s", ownerid, projectName), nil, jsonContent, nil, project)
|
||||
return project, resp, err
|
||||
}
|
||||
|
||||
|
@ -131,11 +131,11 @@ func (c *Client) CreateProject(ctx context.Context, project *types.Project) (*ty
|
|||
return project, resp, err
|
||||
}
|
||||
|
||||
func (c *Client) DeleteProject(ctx context.Context, projectName string) (*http.Response, error) {
|
||||
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/projects/%s", projectName), nil, jsonContent, nil)
|
||||
func (c *Client) DeleteProject(ctx context.Context, projectID string) (*http.Response, error) {
|
||||
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/projects/%s", projectID), nil, jsonContent, nil)
|
||||
}
|
||||
|
||||
func (c *Client) GetProjects(ctx context.Context, start string, limit int, asc bool) ([]*types.Project, *http.Response, error) {
|
||||
func (c *Client) GetOwnerProjects(ctx context.Context, ownerid, start string, limit int, asc bool) ([]*types.Project, *http.Response, error) {
|
||||
q := url.Values{}
|
||||
if start != "" {
|
||||
q.Add("start", start)
|
||||
|
@ -148,7 +148,7 @@ func (c *Client) GetProjects(ctx context.Context, start string, limit int, asc b
|
|||
}
|
||||
|
||||
projects := []*types.Project{}
|
||||
resp, err := c.getParsedResponse(ctx, "GET", "/projects", q, jsonContent, nil, &projects)
|
||||
resp, err := c.getParsedResponse(ctx, "GET", fmt.Sprintf("/owner/%s/projects", ownerid), q, jsonContent, nil, &projects)
|
||||
return projects, resp, err
|
||||
}
|
||||
|
||||
|
@ -316,3 +316,47 @@ func (c *Client) CreateRemoteSource(ctx context.Context, rs *types.RemoteSource)
|
|||
func (c *Client) DeleteRemoteSource(ctx context.Context, name string) (*http.Response, error) {
|
||||
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/remotesources/%s", name), nil, jsonContent, nil)
|
||||
}
|
||||
|
||||
func (c *Client) CreateOrg(ctx context.Context, org *types.Organization) (*types.Organization, *http.Response, error) {
|
||||
oj, err := json.Marshal(org)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
org = new(types.Organization)
|
||||
resp, err := c.getParsedResponse(ctx, "PUT", "/orgs", nil, jsonContent, bytes.NewReader(oj), org)
|
||||
return org, resp, err
|
||||
}
|
||||
|
||||
func (c *Client) DeleteOrg(ctx context.Context, orgname string) (*http.Response, error) {
|
||||
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/orgs/%s", orgname), nil, jsonContent, nil)
|
||||
}
|
||||
|
||||
func (c *Client) GetOrgs(ctx context.Context, start string, limit int, asc bool) ([]*types.Organization, *http.Response, error) {
|
||||
q := url.Values{}
|
||||
if start != "" {
|
||||
q.Add("start", start)
|
||||
}
|
||||
if limit > 0 {
|
||||
q.Add("limit", strconv.Itoa(limit))
|
||||
}
|
||||
if asc {
|
||||
q.Add("asc", "")
|
||||
}
|
||||
|
||||
orgs := []*types.Organization{}
|
||||
resp, err := c.getParsedResponse(ctx, "GET", "/orgs", q, jsonContent, nil, &orgs)
|
||||
return orgs, resp, err
|
||||
}
|
||||
|
||||
func (c *Client) GetOrg(ctx context.Context, orgID string) (*types.Organization, *http.Response, error) {
|
||||
org := new(types.Organization)
|
||||
resp, err := c.getParsedResponse(ctx, "GET", fmt.Sprintf("/org/%s", orgID), nil, jsonContent, nil, org)
|
||||
return org, resp, err
|
||||
}
|
||||
|
||||
func (c *Client) GetOrgByName(ctx context.Context, orgname string) (*types.Organization, *http.Response, error) {
|
||||
org := new(types.Organization)
|
||||
resp, err := c.getParsedResponse(ctx, "GET", fmt.Sprintf("/orgs/%s", orgname), nil, jsonContent, nil, org)
|
||||
return org, resp, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
// Copyright 2019 Sorint.lab
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/sorintlab/agola/internal/db"
|
||||
"github.com/sorintlab/agola/internal/services/configstore/command"
|
||||
"github.com/sorintlab/agola/internal/services/configstore/readdb"
|
||||
"github.com/sorintlab/agola/internal/services/types"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type OrgHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
readDB *readdb.ReadDB
|
||||
}
|
||||
|
||||
func NewOrgHandler(logger *zap.Logger, readDB *readdb.ReadDB) *OrgHandler {
|
||||
return &OrgHandler{log: logger.Sugar(), readDB: readDB}
|
||||
}
|
||||
|
||||
func (h *OrgHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
orgID := vars["orgid"]
|
||||
|
||||
var org *types.Organization
|
||||
err := h.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
org, err = h.readDB.GetOrg(tx, orgID)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if org == nil {
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(org); err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type OrgByNameHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
readDB *readdb.ReadDB
|
||||
}
|
||||
|
||||
func NewOrgByNameHandler(logger *zap.Logger, readDB *readdb.ReadDB) *OrgByNameHandler {
|
||||
return &OrgByNameHandler{log: logger.Sugar(), readDB: readDB}
|
||||
}
|
||||
|
||||
func (h *OrgByNameHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
orgName := vars["orgname"]
|
||||
|
||||
var org *types.Organization
|
||||
err := h.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
org, err = h.readDB.GetOrgByName(tx, orgName)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if org == nil {
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(org); err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type CreateOrgHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
ch *command.CommandHandler
|
||||
}
|
||||
|
||||
func NewCreateOrgHandler(logger *zap.Logger, ch *command.CommandHandler) *CreateOrgHandler {
|
||||
return &CreateOrgHandler{log: logger.Sugar(), ch: ch}
|
||||
}
|
||||
|
||||
func (h *CreateOrgHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var req types.Organization
|
||||
d := json.NewDecoder(r.Body)
|
||||
if err := d.Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
org, err := h.ch.CreateOrg(ctx, &req)
|
||||
if err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(org); err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type DeleteOrgHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
ch *command.CommandHandler
|
||||
}
|
||||
|
||||
func NewDeleteOrgHandler(logger *zap.Logger, ch *command.CommandHandler) *DeleteOrgHandler {
|
||||
return &DeleteOrgHandler{log: logger.Sugar(), ch: ch}
|
||||
}
|
||||
|
||||
func (h *DeleteOrgHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.log.Infof("deleteorghandler")
|
||||
ctx := r.Context()
|
||||
|
||||
vars := mux.Vars(r)
|
||||
orgName := vars["orgname"]
|
||||
|
||||
if err := h.ch.DeleteOrg(ctx, orgName); err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultOrgsLimit = 10
|
||||
MaxOrgsLimit = 20
|
||||
)
|
||||
|
||||
type OrgsHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
readDB *readdb.ReadDB
|
||||
}
|
||||
|
||||
func NewOrgsHandler(logger *zap.Logger, readDB *readdb.ReadDB) *OrgsHandler {
|
||||
return &OrgsHandler{log: logger.Sugar(), readDB: readDB}
|
||||
}
|
||||
|
||||
func (h *OrgsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query()
|
||||
|
||||
limitS := query.Get("limit")
|
||||
limit := DefaultOrgsLimit
|
||||
if limitS != "" {
|
||||
var err error
|
||||
limit, err = strconv.Atoi(limitS)
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
if limit < 0 {
|
||||
http.Error(w, "limit must be greater or equal than 0", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if limit > MaxOrgsLimit {
|
||||
limit = MaxOrgsLimit
|
||||
}
|
||||
asc := false
|
||||
if _, ok := query["asc"]; ok {
|
||||
asc = true
|
||||
}
|
||||
|
||||
start := query.Get("start")
|
||||
|
||||
var orgs []*types.Organization
|
||||
err := h.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
orgs, err = h.readDB.GetOrgs(tx, start, limit, asc)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(orgs); err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -28,16 +28,16 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type GetProjectHandler struct {
|
||||
type ProjectHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
readDB *readdb.ReadDB
|
||||
}
|
||||
|
||||
func NewGetProjectHandler(logger *zap.Logger, readDB *readdb.ReadDB) *GetProjectHandler {
|
||||
return &GetProjectHandler{log: logger.Sugar(), readDB: readDB}
|
||||
func NewProjectHandler(logger *zap.Logger, readDB *readdb.ReadDB) *ProjectHandler {
|
||||
return &ProjectHandler{log: logger.Sugar(), readDB: readDB}
|
||||
}
|
||||
|
||||
func (h *GetProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *ProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
projectID := vars["projectid"]
|
||||
|
||||
|
@ -63,23 +63,24 @@ func (h *GetProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
type GetProjectByNameHandler struct {
|
||||
type ProjectByNameHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
readDB *readdb.ReadDB
|
||||
}
|
||||
|
||||
func NewGetProjectByNameHandler(logger *zap.Logger, readDB *readdb.ReadDB) *GetProjectByNameHandler {
|
||||
return &GetProjectByNameHandler{log: logger.Sugar(), readDB: readDB}
|
||||
func NewProjectByNameHandler(logger *zap.Logger, readDB *readdb.ReadDB) *ProjectByNameHandler {
|
||||
return &ProjectByNameHandler{log: logger.Sugar(), readDB: readDB}
|
||||
}
|
||||
|
||||
func (h *GetProjectByNameHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *ProjectByNameHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
ownerID := vars["ownerid"]
|
||||
projectName := vars["projectname"]
|
||||
|
||||
var project *types.Project
|
||||
err := h.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
project, err = h.readDB.GetProjectByName(tx, projectName)
|
||||
project, err = h.readDB.GetOwnerProjectByName(tx, ownerID, projectName)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -144,9 +145,9 @@ func (h *DeleteProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
|||
ctx := r.Context()
|
||||
|
||||
vars := mux.Vars(r)
|
||||
projectName := vars["projectname"]
|
||||
projectID := vars["projectid"]
|
||||
|
||||
if err := h.ch.DeleteProject(ctx, projectName); err != nil {
|
||||
if err := h.ch.DeleteProject(ctx, projectID); err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
|
@ -168,6 +169,9 @@ func NewProjectsHandler(logger *zap.Logger, readDB *readdb.ReadDB) *ProjectsHand
|
|||
}
|
||||
|
||||
func (h *ProjectsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
ownerID := vars["ownerid"]
|
||||
|
||||
query := r.URL.Query()
|
||||
|
||||
limitS := query.Get("limit")
|
||||
|
@ -194,7 +198,12 @@ func (h *ProjectsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
start := query.Get("start")
|
||||
|
||||
projects, err := h.readDB.GetProjects(start, limit, asc)
|
||||
var projects []*types.Project
|
||||
err := h.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
projects, err = h.readDB.GetOwnerProjects(tx, ownerID, start, limit, asc)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
|
|
@ -28,16 +28,16 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type GetRemoteSourceHandler struct {
|
||||
type RemoteSourceHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
readDB *readdb.ReadDB
|
||||
}
|
||||
|
||||
func NewGetRemoteSourceHandler(logger *zap.Logger, readDB *readdb.ReadDB) *GetRemoteSourceHandler {
|
||||
return &GetRemoteSourceHandler{log: logger.Sugar(), readDB: readDB}
|
||||
func NewRemoteSourceHandler(logger *zap.Logger, readDB *readdb.ReadDB) *RemoteSourceHandler {
|
||||
return &RemoteSourceHandler{log: logger.Sugar(), readDB: readDB}
|
||||
}
|
||||
|
||||
func (h *GetRemoteSourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *RemoteSourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
remoteSourceID := vars["id"]
|
||||
|
||||
|
@ -63,16 +63,16 @@ func (h *GetRemoteSourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
|
|||
}
|
||||
}
|
||||
|
||||
type GetRemoteSourceByNameHandler struct {
|
||||
type RemoteSourceByNameHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
readDB *readdb.ReadDB
|
||||
}
|
||||
|
||||
func NewGetRemoteSourceByNameHandler(logger *zap.Logger, readDB *readdb.ReadDB) *GetRemoteSourceByNameHandler {
|
||||
return &GetRemoteSourceByNameHandler{log: logger.Sugar(), readDB: readDB}
|
||||
func NewRemoteSourceByNameHandler(logger *zap.Logger, readDB *readdb.ReadDB) *RemoteSourceByNameHandler {
|
||||
return &RemoteSourceByNameHandler{log: logger.Sugar(), readDB: readDB}
|
||||
}
|
||||
|
||||
func (h *GetRemoteSourceByNameHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *RemoteSourceByNameHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
remoteSourceName := vars["name"]
|
||||
|
||||
|
|
|
@ -29,16 +29,16 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type GetUserHandler struct {
|
||||
type UserHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
readDB *readdb.ReadDB
|
||||
}
|
||||
|
||||
func NewGetUserHandler(logger *zap.Logger, readDB *readdb.ReadDB) *GetUserHandler {
|
||||
return &GetUserHandler{log: logger.Sugar(), readDB: readDB}
|
||||
func NewUserHandler(logger *zap.Logger, readDB *readdb.ReadDB) *UserHandler {
|
||||
return &UserHandler{log: logger.Sugar(), readDB: readDB}
|
||||
}
|
||||
|
||||
func (h *GetUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
userID := vars["userid"]
|
||||
|
||||
|
@ -66,16 +66,16 @@ func (h *GetUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
type GetUserByNameHandler struct {
|
||||
type UserByNameHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
readDB *readdb.ReadDB
|
||||
}
|
||||
|
||||
func NewGetUserByNameHandler(logger *zap.Logger, readDB *readdb.ReadDB) *GetUserByNameHandler {
|
||||
return &GetUserByNameHandler{log: logger.Sugar(), readDB: readDB}
|
||||
func NewUserByNameHandler(logger *zap.Logger, readDB *readdb.ReadDB) *UserByNameHandler {
|
||||
return &UserByNameHandler{log: logger.Sugar(), readDB: readDB}
|
||||
}
|
||||
|
||||
func (h *GetUserByNameHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *UserByNameHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
userName := vars["username"]
|
||||
|
||||
|
|
|
@ -48,12 +48,20 @@ func (s *CommandHandler) CreateProject(ctx context.Context, project *types.Proje
|
|||
if project.Name == "" {
|
||||
return nil, errors.Errorf("project name required")
|
||||
}
|
||||
if project.OwnerType == "" {
|
||||
return nil, errors.Errorf("project ownertype required")
|
||||
}
|
||||
if project.OwnerID == "" {
|
||||
return nil, errors.Errorf("project ownerid required")
|
||||
}
|
||||
if !types.IsValidOwnerType(project.OwnerType) {
|
||||
return nil, errors.Errorf("invalid project ownertype %q", project.OwnerType)
|
||||
}
|
||||
|
||||
var cgt *wal.ChangeGroupsUpdateToken
|
||||
cgNames := []string{project.Name}
|
||||
cgNames := []string{project.OwnerID}
|
||||
|
||||
// must do all the check in a single transaction to avoid concurrent changes
|
||||
// since the use token is related to the transaction time
|
||||
err := s.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
|
||||
|
@ -61,13 +69,32 @@ func (s *CommandHandler) CreateProject(ctx context.Context, project *types.Proje
|
|||
return err
|
||||
}
|
||||
|
||||
// check owner exists
|
||||
switch project.OwnerType {
|
||||
case types.OwnerTypeUser:
|
||||
user, err := s.readDB.GetUser(tx, project.OwnerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user == nil {
|
||||
return errors.Errorf("user id %q doesn't exist", project.OwnerID)
|
||||
}
|
||||
case types.OwnerTypeOrganization:
|
||||
org, err := s.readDB.GetOrg(tx, project.OwnerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if org == nil {
|
||||
return errors.Errorf("organization id %q doesn't exist", project.OwnerID)
|
||||
}
|
||||
}
|
||||
// check duplicate project name
|
||||
p, err := s.readDB.GetProjectByName(tx, project.Name)
|
||||
p, err := s.readDB.GetOwnerProjectByName(tx, project.OwnerID, project.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p != nil {
|
||||
return errors.Errorf("project %q already exists", p.Name)
|
||||
return errors.Errorf("project with name %q for %s with id %q already exists", p.Name, project.OwnerType, project.OwnerID)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
@ -93,14 +120,13 @@ func (s *CommandHandler) CreateProject(ctx context.Context, project *types.Proje
|
|||
return project, err
|
||||
}
|
||||
|
||||
func (s *CommandHandler) DeleteProject(ctx context.Context, projectName string) error {
|
||||
func (s *CommandHandler) DeleteProject(ctx context.Context, projectID string) error {
|
||||
var project *types.Project
|
||||
|
||||
var cgt *wal.ChangeGroupsUpdateToken
|
||||
cgNames := []string{project.Name}
|
||||
cgNames := []string{project.OwnerID}
|
||||
|
||||
// must do all the check in a single transaction to avoid concurrent changes
|
||||
// since the use token is related to the transaction time
|
||||
err := s.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
|
||||
|
@ -109,12 +135,12 @@ func (s *CommandHandler) DeleteProject(ctx context.Context, projectName string)
|
|||
}
|
||||
|
||||
// check project existance
|
||||
project, err = s.readDB.GetProjectByName(tx, projectName)
|
||||
project, err = s.readDB.GetProject(tx, projectID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if project == nil {
|
||||
return errors.Errorf("project %q doesn't exist", projectName)
|
||||
return errors.Errorf("project %q doesn't exist", projectID)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
@ -142,7 +168,6 @@ func (s *CommandHandler) CreateUser(ctx context.Context, user *types.User) (*typ
|
|||
cgNames := []string{user.UserName}
|
||||
|
||||
// must do all the check in a single transaction to avoid concurrent changes
|
||||
// since the use token is related to the transaction time
|
||||
err := s.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
|
||||
|
@ -156,7 +181,7 @@ func (s *CommandHandler) CreateUser(ctx context.Context, user *types.User) (*typ
|
|||
return err
|
||||
}
|
||||
if u != nil {
|
||||
return errors.Errorf("user %q already exists", u.UserName)
|
||||
return errors.Errorf("user with name %q already exists", u.UserName)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
@ -166,7 +191,7 @@ func (s *CommandHandler) CreateUser(ctx context.Context, user *types.User) (*typ
|
|||
|
||||
user.ID = uuid.NewV4().String()
|
||||
|
||||
pcj, err := json.Marshal(user)
|
||||
userj, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to marshal user")
|
||||
}
|
||||
|
@ -174,7 +199,7 @@ func (s *CommandHandler) CreateUser(ctx context.Context, user *types.User) (*typ
|
|||
{
|
||||
ActionType: wal.ActionTypePut,
|
||||
Path: common.StorageUserFile(user.ID),
|
||||
Data: pcj,
|
||||
Data: userj,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -189,7 +214,6 @@ func (s *CommandHandler) DeleteUser(ctx context.Context, userName string) error
|
|||
cgNames := []string{user.UserName}
|
||||
|
||||
// must do all the check in a single transaction to avoid concurrent changes
|
||||
// since the use token is related to the transaction time
|
||||
err := s.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
|
||||
|
@ -248,7 +272,6 @@ func (s *CommandHandler) CreateUserLA(ctx context.Context, req *CreateUserLARequ
|
|||
var cgt *wal.ChangeGroupsUpdateToken
|
||||
|
||||
// must do all the check in a single transaction to avoid concurrent changes
|
||||
// since the use token is related to the transaction time
|
||||
err := s.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
user, err = s.readDB.GetUserByName(tx, req.UserName)
|
||||
|
@ -294,7 +317,7 @@ func (s *CommandHandler) CreateUserLA(ctx context.Context, req *CreateUserLARequ
|
|||
|
||||
user.LinkedAccounts[la.ID] = la
|
||||
|
||||
pcj, err := json.Marshal(user)
|
||||
userj, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to marshal user")
|
||||
}
|
||||
|
@ -302,7 +325,7 @@ func (s *CommandHandler) CreateUserLA(ctx context.Context, req *CreateUserLARequ
|
|||
{
|
||||
ActionType: wal.ActionTypePut,
|
||||
Path: common.StorageUserFile(user.ID),
|
||||
Data: pcj,
|
||||
Data: userj,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -323,7 +346,6 @@ func (s *CommandHandler) DeleteUserLA(ctx context.Context, userName, laID string
|
|||
var cgt *wal.ChangeGroupsUpdateToken
|
||||
|
||||
// must do all the check in a single transaction to avoid concurrent changes
|
||||
// since the use token is related to the transaction time
|
||||
err := s.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
user, err = s.readDB.GetUserByName(tx, userName)
|
||||
|
@ -353,7 +375,7 @@ func (s *CommandHandler) DeleteUserLA(ctx context.Context, userName, laID string
|
|||
|
||||
delete(user.LinkedAccounts, laID)
|
||||
|
||||
pcj, err := json.Marshal(user)
|
||||
userj, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to marshal user")
|
||||
}
|
||||
|
@ -361,7 +383,7 @@ func (s *CommandHandler) DeleteUserLA(ctx context.Context, userName, laID string
|
|||
{
|
||||
ActionType: wal.ActionTypePut,
|
||||
Path: common.StorageUserFile(user.ID),
|
||||
Data: pcj,
|
||||
Data: userj,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -390,7 +412,6 @@ func (s *CommandHandler) UpdateUserLA(ctx context.Context, req *UpdateUserLARequ
|
|||
var cgt *wal.ChangeGroupsUpdateToken
|
||||
|
||||
// must do all the check in a single transaction to avoid concurrent changes
|
||||
// since the use token is related to the transaction time
|
||||
err := s.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
user, err = s.readDB.GetUserByName(tx, req.UserName)
|
||||
|
@ -514,7 +535,6 @@ func (s *CommandHandler) CreateRemoteSource(ctx context.Context, remoteSource *t
|
|||
cgNames := []string{remoteSource.Name}
|
||||
|
||||
// must do all the check in a single transaction to avoid concurrent changes
|
||||
// since the use token is related to the transaction time
|
||||
err := s.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
|
||||
|
@ -593,3 +613,103 @@ func (s *CommandHandler) DeleteRemoteSource(ctx context.Context, remoteSourceNam
|
|||
_, err = s.wal.WriteWal(ctx, actions, cgt)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *CommandHandler) CreateOrg(ctx context.Context, org *types.Organization) (*types.Organization, error) {
|
||||
if org.Name == "" {
|
||||
return nil, errors.Errorf("org name required")
|
||||
}
|
||||
|
||||
var cgt *wal.ChangeGroupsUpdateToken
|
||||
cgNames := []string{org.Name}
|
||||
|
||||
// must do all the check in a single transaction to avoid concurrent changes
|
||||
err := s.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check duplicate org name
|
||||
u, err := s.readDB.GetOrgByName(tx, org.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if u != nil {
|
||||
return errors.Errorf("org %q already exists", u.Name)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
org.ID = uuid.NewV4().String()
|
||||
|
||||
orgj, err := json.Marshal(org)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to marshal org")
|
||||
}
|
||||
actions := []*wal.Action{
|
||||
{
|
||||
ActionType: wal.ActionTypePut,
|
||||
Path: common.StorageOrgFile(org.ID),
|
||||
Data: orgj,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = s.wal.WriteWal(ctx, actions, cgt)
|
||||
return org, err
|
||||
}
|
||||
|
||||
func (s *CommandHandler) DeleteOrg(ctx context.Context, orgName string) error {
|
||||
var org *types.Organization
|
||||
var projects []*types.Project
|
||||
|
||||
var cgt *wal.ChangeGroupsUpdateToken
|
||||
cgNames := []string{org.ID}
|
||||
|
||||
// must do all the check in a single transaction to avoid concurrent changes
|
||||
err := s.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check org existance
|
||||
org, err = s.readDB.GetOrgByName(tx, orgName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if org == nil {
|
||||
return errors.Errorf("org %q doesn't exist", orgName)
|
||||
}
|
||||
// get org projects
|
||||
projects, err = s.readDB.GetOwnerProjects(tx, org.ID, "", 0, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actions := []*wal.Action{
|
||||
{
|
||||
ActionType: wal.ActionTypeDelete,
|
||||
Path: common.StorageOrgFile(org.ID),
|
||||
},
|
||||
}
|
||||
// delete all org projects
|
||||
for _, project := range projects {
|
||||
actions = append(actions, &wal.Action{
|
||||
ActionType: wal.ActionTypeDelete,
|
||||
Path: common.StorageProjectFile(project.ID),
|
||||
})
|
||||
}
|
||||
|
||||
_, err = s.wal.WriteWal(ctx, actions, cgt)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ var (
|
|||
StorageDataDir = "data"
|
||||
StorageProjectsDir = path.Join(StorageDataDir, "projects")
|
||||
StorageUsersDir = path.Join(StorageDataDir, "users")
|
||||
StorageOrgsDir = path.Join(StorageDataDir, "orgs")
|
||||
StorageRemoteSourcesDir = path.Join(StorageDataDir, "remotesources")
|
||||
)
|
||||
|
||||
|
@ -39,6 +40,10 @@ func StorageUserFile(userID string) string {
|
|||
return path.Join(StorageUsersDir, userID)
|
||||
}
|
||||
|
||||
func StorageOrgFile(orgID string) string {
|
||||
return path.Join(StorageOrgsDir, orgID)
|
||||
}
|
||||
|
||||
func StorageRemoteSourceFile(userID string) string {
|
||||
return path.Join(StorageRemoteSourcesDir, userID)
|
||||
}
|
||||
|
@ -48,6 +53,7 @@ type ConfigType string
|
|||
const (
|
||||
ConfigTypeProject ConfigType = "project"
|
||||
ConfigTypeUser ConfigType = "user"
|
||||
ConfigTypeOrg ConfigType = "org"
|
||||
ConfigTypeRemoteSource ConfigType = "remotesource"
|
||||
)
|
||||
|
||||
|
@ -58,6 +64,8 @@ func PathToTypeID(p string) (ConfigType, string) {
|
|||
configType = ConfigTypeProject
|
||||
case StorageUsersDir:
|
||||
configType = ConfigTypeUser
|
||||
case StorageOrgsDir:
|
||||
configType = ConfigTypeOrg
|
||||
case StorageRemoteSourcesDir:
|
||||
configType = ConfigTypeRemoteSource
|
||||
default:
|
||||
|
|
|
@ -109,15 +109,15 @@ func (s *ConfigStore) Run(ctx context.Context) error {
|
|||
corsAllowedOriginsOptions := ghandlers.AllowedOrigins([]string{"*"})
|
||||
corsHandler = ghandlers.CORS(corsAllowedMethodsOptions, corsAllowedHeadersOptions, corsAllowedOriginsOptions)
|
||||
|
||||
getProjectHandler := api.NewGetProjectHandler(logger, s.readDB)
|
||||
projectHandler := api.NewProjectHandler(logger, s.readDB)
|
||||
projectsHandler := api.NewProjectsHandler(logger, s.readDB)
|
||||
getProjectByNameHandler := api.NewGetProjectByNameHandler(logger, s.readDB)
|
||||
projectByNameHandler := api.NewProjectByNameHandler(logger, s.readDB)
|
||||
createProjectHandler := api.NewCreateProjectHandler(logger, s.ch)
|
||||
deleteProjectHandler := api.NewDeleteProjectHandler(logger, s.ch)
|
||||
|
||||
getUserHandler := api.NewGetUserHandler(logger, s.readDB)
|
||||
userHandler := api.NewUserHandler(logger, s.readDB)
|
||||
usersHandler := api.NewUsersHandler(logger, s.readDB)
|
||||
getUserByNameHandler := api.NewGetUserByNameHandler(logger, s.readDB)
|
||||
userByNameHandler := api.NewUserByNameHandler(logger, s.readDB)
|
||||
createUserHandler := api.NewCreateUserHandler(logger, s.ch)
|
||||
deleteUserHandler := api.NewDeleteUserHandler(logger, s.ch)
|
||||
|
||||
|
@ -127,25 +127,31 @@ func (s *ConfigStore) Run(ctx context.Context) error {
|
|||
|
||||
createUserTokenHandler := api.NewCreateUserTokenHandler(logger, s.ch)
|
||||
|
||||
getRemoteSourceHandler := api.NewGetRemoteSourceHandler(logger, s.readDB)
|
||||
orgHandler := api.NewOrgHandler(logger, s.readDB)
|
||||
orgsHandler := api.NewOrgsHandler(logger, s.readDB)
|
||||
orgByNameHandler := api.NewOrgByNameHandler(logger, s.readDB)
|
||||
createOrgHandler := api.NewCreateOrgHandler(logger, s.ch)
|
||||
deleteOrgHandler := api.NewDeleteOrgHandler(logger, s.ch)
|
||||
|
||||
remoteSourceHandler := api.NewRemoteSourceHandler(logger, s.readDB)
|
||||
remoteSourcesHandler := api.NewRemoteSourcesHandler(logger, s.readDB)
|
||||
getRemoteSourceByNameHandler := api.NewGetRemoteSourceByNameHandler(logger, s.readDB)
|
||||
remoteSourceByNameHandler := api.NewRemoteSourceByNameHandler(logger, s.readDB)
|
||||
createRemoteSourceHandler := api.NewCreateRemoteSourceHandler(logger, s.ch)
|
||||
deleteRemoteSourceHandler := api.NewDeleteRemoteSourceHandler(logger, s.ch)
|
||||
|
||||
router := mux.NewRouter()
|
||||
apirouter := router.PathPrefix("/api/v1alpha").Subrouter()
|
||||
|
||||
apirouter.Handle("/project/{projectid}", getProjectHandler).Methods("GET")
|
||||
apirouter.Handle("/projects", projectsHandler).Methods("GET")
|
||||
apirouter.Handle("/project/{projectid}", projectHandler).Methods("GET")
|
||||
apirouter.Handle("/owner/{ownerid}/projects", projectsHandler).Methods("GET")
|
||||
apirouter.Handle("/projects/{ownerid}/{projectname}", projectByNameHandler).Methods("GET")
|
||||
apirouter.Handle("/projects", createProjectHandler).Methods("PUT")
|
||||
apirouter.Handle("/projects/{projectname}", getProjectByNameHandler).Methods("GET")
|
||||
apirouter.Handle("/projects/{projectname}", deleteProjectHandler).Methods("DELETE")
|
||||
apirouter.Handle("/projects/{projectid}", deleteProjectHandler).Methods("DELETE")
|
||||
|
||||
apirouter.Handle("/user/{userid}", getUserHandler).Methods("GET")
|
||||
apirouter.Handle("/user/{userid}", userHandler).Methods("GET")
|
||||
apirouter.Handle("/users", usersHandler).Methods("GET")
|
||||
apirouter.Handle("/users", createUserHandler).Methods("PUT")
|
||||
apirouter.Handle("/users/{username}", getUserByNameHandler).Methods("GET")
|
||||
apirouter.Handle("/users/{username}", userByNameHandler).Methods("GET")
|
||||
apirouter.Handle("/users/{username}", deleteUserHandler).Methods("DELETE")
|
||||
|
||||
apirouter.Handle("/users/{username}/linkedaccounts", createUserLAHandler).Methods("PUT")
|
||||
|
@ -153,10 +159,16 @@ func (s *ConfigStore) Run(ctx context.Context) error {
|
|||
apirouter.Handle("/users/{username}/linkedaccounts/{laid}", updateUserLAHandler).Methods("PUT")
|
||||
apirouter.Handle("/users/{username}/tokens", createUserTokenHandler).Methods("PUT")
|
||||
|
||||
apirouter.Handle("/remotesource/{id}", getRemoteSourceHandler).Methods("GET")
|
||||
apirouter.Handle("/org/{orgid}", orgHandler).Methods("GET")
|
||||
apirouter.Handle("/orgs", orgsHandler).Methods("GET")
|
||||
apirouter.Handle("/orgs", createOrgHandler).Methods("PUT")
|
||||
apirouter.Handle("/orgs/{orgname}", orgByNameHandler).Methods("GET")
|
||||
apirouter.Handle("/orgs/{orgname}", deleteOrgHandler).Methods("DELETE")
|
||||
|
||||
apirouter.Handle("/remotesource/{id}", remoteSourceHandler).Methods("GET")
|
||||
apirouter.Handle("/remotesources", remoteSourcesHandler).Methods("GET")
|
||||
apirouter.Handle("/remotesources", createRemoteSourceHandler).Methods("PUT")
|
||||
apirouter.Handle("/remotesources/{name}", getRemoteSourceByNameHandler).Methods("GET")
|
||||
apirouter.Handle("/remotesources/{name}", remoteSourceByNameHandler).Methods("GET")
|
||||
apirouter.Handle("/remotesources/{name}", deleteRemoteSourceHandler).Methods("DELETE")
|
||||
|
||||
mainrouter := mux.NewRouter()
|
||||
|
|
|
@ -21,9 +21,11 @@ import (
|
|||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sorintlab/agola/internal/db"
|
||||
"github.com/sorintlab/agola/internal/services/config"
|
||||
"github.com/sorintlab/agola/internal/services/types"
|
||||
"github.com/sorintlab/agola/internal/testutil"
|
||||
|
@ -50,6 +52,60 @@ func shutdownEtcd(tetcd *testutil.TestEmbeddedEtcd) {
|
|||
}
|
||||
}
|
||||
|
||||
func setupConfigstore(t *testing.T, ctx context.Context, dir string) (*ConfigStore, *testutil.TestEtcd) {
|
||||
etcdDir, err := ioutil.TempDir(dir, "etcd")
|
||||
tetcd := setupEtcd(t, etcdDir)
|
||||
|
||||
listenAddress, port, err := testutil.GetFreePort(true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
ltsDir, err := ioutil.TempDir(dir, "lts")
|
||||
csDir, err := ioutil.TempDir(dir, "cs")
|
||||
|
||||
baseConfig := config.ConfigStore{
|
||||
Etcd: config.Etcd{
|
||||
Endpoints: tetcd.Endpoint,
|
||||
},
|
||||
LTS: config.LTS{
|
||||
Type: config.LTSTypePosix,
|
||||
Path: ltsDir,
|
||||
},
|
||||
Web: config.Web{},
|
||||
}
|
||||
csConfig := baseConfig
|
||||
csConfig.DataDir = csDir
|
||||
csConfig.Web.ListenAddress = net.JoinHostPort(listenAddress, port)
|
||||
|
||||
cs, err := NewConfigStore(ctx, &csConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
return cs, tetcd
|
||||
}
|
||||
|
||||
func getProjects(cs *ConfigStore) ([]*types.Project, error) {
|
||||
var projects []*types.Project
|
||||
err := cs.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
projects, err = cs.readDB.GetProjects(tx, "", 0, true)
|
||||
return err
|
||||
})
|
||||
return projects, err
|
||||
}
|
||||
|
||||
func getUsers(cs *ConfigStore) ([]*types.User, error) {
|
||||
var users []*types.User
|
||||
err := cs.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
users, err = cs.readDB.GetUsers(tx, "", 0, true)
|
||||
return err
|
||||
})
|
||||
return users, err
|
||||
}
|
||||
|
||||
func TestResync(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "agola")
|
||||
if err != nil {
|
||||
|
@ -126,7 +182,7 @@ func TestResync(t *testing.T) {
|
|||
time.Sleep(1 * time.Second)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := cs1.ch.CreateProject(ctx, &types.Project{Name: fmt.Sprintf("project%d", i)}); err != nil {
|
||||
if _, err := cs1.ch.CreateUser(ctx, &types.User{UserName: fmt.Sprintf("user%d", i)}); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
@ -140,7 +196,7 @@ func TestResync(t *testing.T) {
|
|||
|
||||
// Do some more changes
|
||||
for i := 11; i < 20; i++ {
|
||||
if _, err := cs1.ch.CreateProject(ctx, &types.Project{Name: fmt.Sprintf("project%d", i)}); err != nil {
|
||||
if _, err := cs1.ch.CreateUser(ctx, &types.User{UserName: fmt.Sprintf("user%d", i)}); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
@ -165,22 +221,22 @@ func TestResync(t *testing.T) {
|
|||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
projects1, err := cs1.readDB.GetProjects("", 0, true)
|
||||
users1, err := getUsers(cs1)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
projects2, err := cs2.readDB.GetProjects("", 0, true)
|
||||
users2, err := getUsers(cs2)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if !compareProjects(projects1, projects2) {
|
||||
t.Logf("len(projects1): %d", len(projects1))
|
||||
t.Logf("len(projects2): %d", len(projects2))
|
||||
t.Logf("projects1: %s", util.Dump(projects1))
|
||||
t.Logf("projects2: %s", util.Dump(projects2))
|
||||
t.Fatalf("projects are different between the two readdbs")
|
||||
if !compareUsers(users1, users2) {
|
||||
t.Logf("len(users1): %d", len(users1))
|
||||
t.Logf("len(users2): %d", len(users2))
|
||||
t.Logf("users1: %s", util.Dump(users1))
|
||||
t.Logf("users2: %s", util.Dump(users2))
|
||||
t.Fatalf("users are different between the two readdbs")
|
||||
}
|
||||
|
||||
// start cs3, since it's a new instance it should do a full resync
|
||||
|
@ -198,35 +254,220 @@ func TestResync(t *testing.T) {
|
|||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
projects1, err = cs1.readDB.GetProjects("", 0, true)
|
||||
users1, err = getUsers(cs1)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
projects3, err := cs3.readDB.GetProjects("", 0, true)
|
||||
users3, err := getUsers(cs3)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if !compareProjects(projects1, projects3) {
|
||||
t.Logf("len(projects1): %d", len(projects1))
|
||||
t.Logf("len(projects3): %d", len(projects3))
|
||||
t.Logf("projects1: %s", util.Dump(projects1))
|
||||
t.Logf("projects3: %s", util.Dump(projects3))
|
||||
t.Fatalf("projects are different between the two readdbs")
|
||||
if !compareUsers(users1, users3) {
|
||||
t.Logf("len(users1): %d", len(users1))
|
||||
t.Logf("len(users3): %d", len(users3))
|
||||
t.Logf("users1: %s", util.Dump(users1))
|
||||
t.Logf("users3: %s", util.Dump(users3))
|
||||
t.Fatalf("users are different between the two readdbs")
|
||||
}
|
||||
}
|
||||
|
||||
func compareProjects(p1, p2 []*types.Project) bool {
|
||||
p1ids := map[string]struct{}{}
|
||||
p2ids := map[string]struct{}{}
|
||||
func compareUsers(u1, u2 []*types.User) bool {
|
||||
u1ids := map[string]struct{}{}
|
||||
u2ids := map[string]struct{}{}
|
||||
|
||||
for _, p := range p1 {
|
||||
p1ids[p.ID] = struct{}{}
|
||||
for _, u := range u1 {
|
||||
u1ids[u.ID] = struct{}{}
|
||||
}
|
||||
for _, p := range p2 {
|
||||
p2ids[p.ID] = struct{}{}
|
||||
for _, u := range u2 {
|
||||
u2ids[u.ID] = struct{}{}
|
||||
}
|
||||
|
||||
return reflect.DeepEqual(p1ids, p2ids)
|
||||
return reflect.DeepEqual(u1ids, u2ids)
|
||||
}
|
||||
|
||||
func TestUser(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "agola")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
cs, tetcd := setupConfigstore(t, ctx, dir)
|
||||
defer shutdownEtcd(tetcd)
|
||||
|
||||
t.Logf("starting cs")
|
||||
go func() {
|
||||
if err := cs.Run(ctx); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(sgotti) change the sleep with a real check that all is ready
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
t.Run("create user", func(t *testing.T) {
|
||||
_, err := cs.ch.CreateUser(ctx, &types.User{UserName: "user01"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
// TODO(sgotti) change the sleep with a real check that user is in readdb
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
t.Run("create duplicated user", func(t *testing.T) {
|
||||
expectedErr := fmt.Sprintf("user with name %q already exists", "user01")
|
||||
_, err := cs.ch.CreateUser(ctx, &types.User{UserName: "user01"})
|
||||
if err == nil {
|
||||
t.Fatalf("expected error %v, got nil err", expectedErr)
|
||||
}
|
||||
if err.Error() != expectedErr {
|
||||
t.Fatalf("expected err %v, got err: %v", expectedErr, err)
|
||||
}
|
||||
})
|
||||
t.Run("concurrent user with same name creation", func(t *testing.T) {
|
||||
prevUsers, err := getUsers(cs)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go cs.ch.CreateUser(ctx, &types.User{UserName: "user02"})
|
||||
wg.Done()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
users, err := getUsers(cs)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
if len(users) != len(prevUsers)+1 {
|
||||
t.Fatalf("expected %d users, got %d", len(prevUsers)+1, len(users))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestProject(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "agola")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
cs, tetcd := setupConfigstore(t, ctx, dir)
|
||||
defer shutdownEtcd(tetcd)
|
||||
|
||||
t.Logf("starting cs")
|
||||
go func() {
|
||||
if err := cs.Run(ctx); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(sgotti) change the sleep with a real check that all is ready
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
user, err := cs.ch.CreateUser(ctx, &types.User{UserName: "user01"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
org, err := cs.ch.CreateOrg(ctx, &types.Organization{Name: "org01"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
// TODO(sgotti) change the sleep with a real check that user is in readdb
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
t.Run("create project with owner type user", func(t *testing.T) {
|
||||
_, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", OwnerType: "user", OwnerID: user.ID})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
})
|
||||
t.Run("create project with owner type org", func(t *testing.T) {
|
||||
_, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", OwnerType: "organization", OwnerID: org.ID})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
})
|
||||
t.Run("create duplicated project for user", func(t *testing.T) {
|
||||
expectedErr := fmt.Sprintf("project with name %q for user with id %q already exists", "project01", user.ID)
|
||||
_, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", OwnerType: "user", OwnerID: user.ID})
|
||||
if err.Error() != expectedErr {
|
||||
t.Fatalf("expected err %v, got err: %v", expectedErr, err)
|
||||
}
|
||||
})
|
||||
t.Run("create duplicated project for org", func(t *testing.T) {
|
||||
expectedErr := fmt.Sprintf("project with name %q for organization with id %q already exists", "project01", org.ID)
|
||||
_, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", OwnerType: "organization", OwnerID: org.ID})
|
||||
if err.Error() != expectedErr {
|
||||
t.Fatalf("expected err %v, got err: %v", expectedErr, err)
|
||||
}
|
||||
})
|
||||
t.Run("create project with owner as unexistent user", func(t *testing.T) {
|
||||
expectedErr := `user id "unexistentid" doesn't exist`
|
||||
_, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", OwnerType: "user", OwnerID: "unexistentid"})
|
||||
if err.Error() != expectedErr {
|
||||
t.Fatalf("expected err %v, got err: %v", expectedErr, err)
|
||||
}
|
||||
})
|
||||
t.Run("create project with owner as unexistent org", func(t *testing.T) {
|
||||
expectedErr := `organization id "unexistentid" doesn't exist`
|
||||
_, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", OwnerType: "organization", OwnerID: "unexistentid"})
|
||||
if err.Error() != expectedErr {
|
||||
t.Fatalf("expected err %v, got err: %v", expectedErr, err)
|
||||
}
|
||||
})
|
||||
t.Run("create project without ownertype specified", func(t *testing.T) {
|
||||
expectedErr := "project ownertype required"
|
||||
_, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01"})
|
||||
if err.Error() != expectedErr {
|
||||
t.Fatalf("expected err %v, got err: %v", expectedErr, err)
|
||||
}
|
||||
})
|
||||
t.Run("create project without ownerid specified", func(t *testing.T) {
|
||||
expectedErr := "project ownerid required"
|
||||
_, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", OwnerType: "organization"})
|
||||
if err.Error() != expectedErr {
|
||||
t.Fatalf("expected err %v, got err: %v", expectedErr, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("concurrent project with same name creation", func(t *testing.T) {
|
||||
prevProjects, err := getProjects(cs)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go cs.ch.CreateProject(ctx, &types.Project{Name: "project02", OwnerType: "user", OwnerID: user.ID})
|
||||
wg.Done()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
projects, err := getProjects(cs)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
if len(projects) != len(prevProjects)+1 {
|
||||
t.Fatalf("expected %d projects, got %d", len(prevProjects)+1, len(projects))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -28,11 +28,13 @@ var Stmts = []string{
|
|||
"create index project_name on project(name)",
|
||||
|
||||
"create table user (id uuid, name varchar, data bytea, PRIMARY KEY (id))",
|
||||
"create index user_name on user(name)",
|
||||
"create table user_token (tokenvalue varchar, userid uuid, PRIMARY KEY (tokenvalue, userid))",
|
||||
|
||||
"create table remotesource (id uuid, name varchar, data bytea, PRIMARY KEY (id))",
|
||||
"create table org (id uuid, name varchar, data bytea, PRIMARY KEY (id))",
|
||||
"create index org_name on org(name)",
|
||||
|
||||
"create table projectsource (id uuid, name varchar, data bytea, PRIMARY KEY (id))",
|
||||
"create table remotesource (id uuid, name varchar, data bytea, PRIMARY KEY (id))",
|
||||
|
||||
"create table linkedaccount_user (id uuid, remotesourceid uuid, userid uuid, remoteuserid uuid, PRIMARY KEY (id), FOREIGN KEY(userid) REFERENCES user(id))",
|
||||
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
// Copyright 2019 Sorint.lab
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package readdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/sorintlab/agola/internal/db"
|
||||
"github.com/sorintlab/agola/internal/services/types"
|
||||
"github.com/sorintlab/agola/internal/util"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
orgSelect = sb.Select("org.id", "org.data").From("org")
|
||||
orgInsert = sb.Insert("org").Columns("id", "name", "data")
|
||||
)
|
||||
|
||||
func (r *ReadDB) insertOrg(tx *db.Tx, data []byte) error {
|
||||
org := types.Organization{}
|
||||
if err := json.Unmarshal(data, &org); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal org")
|
||||
}
|
||||
r.log.Infof("inserting org: %s", util.Dump(org))
|
||||
// poor man insert or update...
|
||||
if err := r.deleteOrg(tx, org.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
q, args, err := orgInsert.Values(org.ID, org.Name, data).ToSql()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to build query")
|
||||
}
|
||||
if _, err := tx.Exec(q, args...); err != nil {
|
||||
return errors.Wrap(err, "failed to insert org")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReadDB) deleteOrg(tx *db.Tx, orgID string) error {
|
||||
if _, err := tx.Exec("delete from org where id = $1", orgID); err != nil {
|
||||
return errors.Wrap(err, "failed to delete org")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReadDB) GetOrg(tx *db.Tx, orgID string) (*types.Organization, error) {
|
||||
q, args, err := orgSelect.Where(sq.Eq{"id": orgID}).ToSql()
|
||||
r.log.Debugf("q: %s, args: %s", q, util.Dump(args))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build query")
|
||||
}
|
||||
|
||||
orgs, _, err := fetchOrgs(tx, q, args...)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
if len(orgs) > 1 {
|
||||
return nil, errors.Errorf("too many rows returned")
|
||||
}
|
||||
if len(orgs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return orgs[0], nil
|
||||
}
|
||||
|
||||
func (r *ReadDB) GetOrgByName(tx *db.Tx, name string) (*types.Organization, error) {
|
||||
q, args, err := orgSelect.Where(sq.Eq{"name": name}).ToSql()
|
||||
r.log.Debugf("q: %s, args: %s", q, util.Dump(args))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build query")
|
||||
}
|
||||
|
||||
orgs, _, err := fetchOrgs(tx, q, args...)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
if len(orgs) > 1 {
|
||||
return nil, errors.Errorf("too many rows returned")
|
||||
}
|
||||
if len(orgs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return orgs[0], nil
|
||||
}
|
||||
|
||||
func getOrgsFilteredQuery(startOrgName string, limit int, asc bool) sq.SelectBuilder {
|
||||
fields := []string{"id", "data"}
|
||||
|
||||
s := sb.Select(fields...).From("org as org")
|
||||
if asc {
|
||||
s = s.OrderBy("org.name asc")
|
||||
} else {
|
||||
s = s.OrderBy("org.name desc")
|
||||
}
|
||||
if startOrgName != "" {
|
||||
if asc {
|
||||
s = s.Where(sq.Gt{"org.name": startOrgName})
|
||||
} else {
|
||||
s = s.Where(sq.Lt{"org.name": startOrgName})
|
||||
}
|
||||
}
|
||||
if limit > 0 {
|
||||
s = s.Limit(uint64(limit))
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *ReadDB) GetOrgs(tx *db.Tx, startOrgName string, limit int, asc bool) ([]*types.Organization, error) {
|
||||
var orgs []*types.Organization
|
||||
|
||||
s := getOrgsFilteredQuery(startOrgName, limit, asc)
|
||||
q, args, err := s.ToSql()
|
||||
r.log.Debugf("q: %s, args: %s", q, util.Dump(args))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build query")
|
||||
}
|
||||
|
||||
rows, err := tx.Query(q, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgs, _, err = scanOrgs(rows)
|
||||
return orgs, err
|
||||
}
|
||||
|
||||
func fetchOrgs(tx *db.Tx, q string, args ...interface{}) ([]*types.Organization, []string, error) {
|
||||
rows, err := tx.Query(q, args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanOrgs(rows)
|
||||
}
|
||||
|
||||
func scanOrg(rows *sql.Rows, additionalFields ...interface{}) (*types.Organization, string, error) {
|
||||
var id string
|
||||
var data []byte
|
||||
if err := rows.Scan(&id, &data); err != nil {
|
||||
return nil, "", errors.Wrap(err, "failed to scan rows")
|
||||
}
|
||||
org := types.Organization{}
|
||||
if len(data) > 0 {
|
||||
if err := json.Unmarshal(data, &org); err != nil {
|
||||
return nil, "", errors.Wrap(err, "failed to unmarshal org")
|
||||
}
|
||||
}
|
||||
|
||||
return &org, id, nil
|
||||
}
|
||||
|
||||
func scanOrgs(rows *sql.Rows) ([]*types.Organization, []string, error) {
|
||||
orgs := []*types.Organization{}
|
||||
ids := []string{}
|
||||
for rows.Next() {
|
||||
p, id, err := scanOrg(rows)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
orgs = append(orgs, p)
|
||||
ids = append(ids, id)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return orgs, ids, nil
|
||||
}
|
|
@ -28,7 +28,7 @@ import (
|
|||
|
||||
var (
|
||||
projectSelect = sb.Select("id", "data").From("project")
|
||||
projectInsert = sb.Insert("project").Columns("id", "name", "data")
|
||||
projectInsert = sb.Insert("project").Columns("id", "name", "ownerid", "data")
|
||||
)
|
||||
|
||||
func (r *ReadDB) insertProject(tx *db.Tx, data []byte) error {
|
||||
|
@ -40,7 +40,7 @@ func (r *ReadDB) insertProject(tx *db.Tx, data []byte) error {
|
|||
if err := r.deleteProject(tx, project.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
q, args, err := projectInsert.Values(project.ID, project.Name, data).ToSql()
|
||||
q, args, err := projectInsert.Values(project.ID, project.Name, project.OwnerID, data).ToSql()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to build query")
|
||||
}
|
||||
|
@ -76,8 +76,8 @@ func (r *ReadDB) GetProject(tx *db.Tx, projectID string) (*types.Project, error)
|
|||
return projects[0], nil
|
||||
}
|
||||
|
||||
func (r *ReadDB) GetProjectByName(tx *db.Tx, name string) (*types.Project, error) {
|
||||
q, args, err := projectSelect.Where(sq.Eq{"name": name}).ToSql()
|
||||
func (r *ReadDB) GetOwnerProjectByName(tx *db.Tx, ownerid, name string) (*types.Project, error) {
|
||||
q, args, err := projectSelect.Where(sq.Eq{"ownerid": ownerid, "name": name}).ToSql()
|
||||
r.log.Debugf("q: %s, args: %s", q, util.Dump(args))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build query")
|
||||
|
@ -96,7 +96,7 @@ func (r *ReadDB) GetProjectByName(tx *db.Tx, name string) (*types.Project, error
|
|||
return projects[0], nil
|
||||
}
|
||||
|
||||
func getProjectsFilteredQuery(startProjectName string, limit int, asc bool) sq.SelectBuilder {
|
||||
func getProjectsFilteredQuery(ownerid, startProjectName string, limit int, asc bool) sq.SelectBuilder {
|
||||
fields := []string{"id", "data"}
|
||||
|
||||
s := sb.Select(fields...).From("project as project")
|
||||
|
@ -105,6 +105,9 @@ func getProjectsFilteredQuery(startProjectName string, limit int, asc bool) sq.S
|
|||
} else {
|
||||
s = s.OrderBy("project.name desc")
|
||||
}
|
||||
if ownerid != "" {
|
||||
s = s.Where(sq.Eq{"project.ownerid": ownerid})
|
||||
}
|
||||
if startProjectName != "" {
|
||||
if asc {
|
||||
s = s.Where(sq.Gt{"project.name": startProjectName})
|
||||
|
@ -118,26 +121,43 @@ func getProjectsFilteredQuery(startProjectName string, limit int, asc bool) sq.S
|
|||
|
||||
return s
|
||||
}
|
||||
func (r *ReadDB) GetProjects(startProjectName string, limit int, asc bool) ([]*types.Project, error) {
|
||||
|
||||
func (r *ReadDB) GetOwnerProjects(tx *db.Tx, ownerid, startProjectName string, limit int, asc bool) ([]*types.Project, error) {
|
||||
var projects []*types.Project
|
||||
|
||||
s := getProjectsFilteredQuery(startProjectName, limit, asc)
|
||||
s := getProjectsFilteredQuery(ownerid, startProjectName, limit, asc)
|
||||
q, args, err := s.ToSql()
|
||||
r.log.Debugf("q: %s, args: %s", q, util.Dump(args))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build query")
|
||||
}
|
||||
|
||||
err = r.rdb.Do(func(tx *db.Tx) error {
|
||||
rows, err := tx.Query(q, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projects, _, err = scanProjects(rows)
|
||||
return err
|
||||
})
|
||||
return projects, errors.WithStack(err)
|
||||
return projects, err
|
||||
}
|
||||
|
||||
func (r *ReadDB) GetProjects(tx *db.Tx, startProjectName string, limit int, asc bool) ([]*types.Project, error) {
|
||||
var projects []*types.Project
|
||||
|
||||
s := getProjectsFilteredQuery("", startProjectName, limit, asc)
|
||||
q, args, err := s.ToSql()
|
||||
r.log.Debugf("q: %s, args: %s", q, util.Dump(args))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build query")
|
||||
}
|
||||
|
||||
rows, err := tx.Query(q, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projects, _, err = scanProjects(rows)
|
||||
return projects, err
|
||||
}
|
||||
|
||||
func fetchProjects(tx *db.Tx, q string, args ...interface{}) ([]*types.Project, []string, error) {
|
||||
|
|
|
@ -627,6 +627,10 @@ func (r *ReadDB) applyAction(tx *db.Tx, action *wal.Action) error {
|
|||
if err := r.insertUser(tx, action.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
case common.ConfigTypeOrg:
|
||||
if err := r.insertOrg(tx, action.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
case common.ConfigTypeRemoteSource:
|
||||
if err := r.insertRemoteSource(tx, action.Data); err != nil {
|
||||
return err
|
||||
|
@ -645,6 +649,11 @@ func (r *ReadDB) applyAction(tx *db.Tx, action *wal.Action) error {
|
|||
if err := r.deleteUser(tx, ID); err != nil {
|
||||
return err
|
||||
}
|
||||
case common.ConfigTypeOrg:
|
||||
r.log.Debugf("deleting org with id: %s", ID)
|
||||
if err := r.deleteOrg(tx, ID); err != nil {
|
||||
return err
|
||||
}
|
||||
case common.ConfigTypeRemoteSource:
|
||||
r.log.Debugf("deleting remote source with id: %s", ID)
|
||||
if err := r.deleteRemoteSource(tx, ID); err != nil {
|
||||
|
|
|
@ -277,6 +277,7 @@ func getUsersFilteredQuery(startUserName string, limit int, asc bool) sq.SelectB
|
|||
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *ReadDB) GetUsers(tx *db.Tx, startUserName string, limit int, asc bool) ([]*types.User, error) {
|
||||
var users []*types.User
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -118,7 +119,19 @@ func (c *Client) GetProject(ctx context.Context, projectID string) (*types.Proje
|
|||
return project, resp, err
|
||||
}
|
||||
|
||||
func (c *Client) GetProjects(ctx context.Context, start string, limit int, asc bool) (*GetProjectsResponse, *http.Response, error) {
|
||||
func (c *Client) GetCurrentUserProjects(ctx context.Context, start string, limit int, asc bool) (*GetProjectsResponse, *http.Response, error) {
|
||||
return c.getProjects(ctx, "user", "", start, limit, asc)
|
||||
}
|
||||
|
||||
func (c *Client) GetUserProjects(ctx context.Context, username, start string, limit int, asc bool) (*GetProjectsResponse, *http.Response, error) {
|
||||
return c.getProjects(ctx, "user", username, start, limit, asc)
|
||||
}
|
||||
|
||||
func (c *Client) GetOrgProjects(ctx context.Context, orgname, start string, limit int, asc bool) (*GetProjectsResponse, *http.Response, error) {
|
||||
return c.getProjects(ctx, "org", orgname, start, limit, asc)
|
||||
}
|
||||
|
||||
func (c *Client) getProjects(ctx context.Context, ownertype, ownername, start string, limit int, asc bool) (*GetProjectsResponse, *http.Response, error) {
|
||||
q := url.Values{}
|
||||
if start != "" {
|
||||
q.Add("start", start)
|
||||
|
@ -131,23 +144,47 @@ func (c *Client) GetProjects(ctx context.Context, start string, limit int, asc b
|
|||
}
|
||||
|
||||
projects := new(GetProjectsResponse)
|
||||
resp, err := c.getParsedResponse(ctx, "GET", "/projects", q, jsonContent, nil, &projects)
|
||||
resp, err := c.getParsedResponse(ctx, "GET", path.Join("/", ownertype, ownername, "projects"), q, jsonContent, nil, &projects)
|
||||
return projects, resp, err
|
||||
}
|
||||
|
||||
func (c *Client) CreateProject(ctx context.Context, req *CreateProjectRequest) (*types.Project, *http.Response, error) {
|
||||
func (c *Client) CreateCurrentUserProject(ctx context.Context, req *CreateProjectRequest) (*types.Project, *http.Response, error) {
|
||||
return c.createProject(ctx, "user", "", req)
|
||||
}
|
||||
|
||||
func (c *Client) CreateUserProject(ctx context.Context, username string, req *CreateProjectRequest) (*types.Project, *http.Response, error) {
|
||||
return c.createProject(ctx, "user", username, req)
|
||||
}
|
||||
|
||||
func (c *Client) CreateOrgProject(ctx context.Context, orgname string, req *CreateProjectRequest) (*types.Project, *http.Response, error) {
|
||||
return c.createProject(ctx, "org", orgname, req)
|
||||
}
|
||||
|
||||
func (c *Client) createProject(ctx context.Context, ownertype, ownername string, req *CreateProjectRequest) (*types.Project, *http.Response, error) {
|
||||
reqj, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
project := new(types.Project)
|
||||
resp, err := c.getParsedResponse(ctx, "PUT", "/projects", nil, jsonContent, bytes.NewReader(reqj), project)
|
||||
resp, err := c.getParsedResponse(ctx, "PUT", path.Join("/", ownertype, ownername, "projects"), nil, jsonContent, bytes.NewReader(reqj), project)
|
||||
return project, resp, err
|
||||
}
|
||||
|
||||
func (c *Client) DeleteProject(ctx context.Context, projectName string) (*http.Response, error) {
|
||||
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/projects/%s", projectName), nil, jsonContent, nil)
|
||||
func (c *Client) DeleteCurrentUserProject(ctx context.Context, projectName string) (*http.Response, error) {
|
||||
return c.deleteProject(ctx, "user", "", projectName)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteUserProject(ctx context.Context, username, projectName string) (*http.Response, error) {
|
||||
return c.deleteProject(ctx, "user", username, projectName)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteOrgProject(ctx context.Context, orgname, projectName string) (*http.Response, error) {
|
||||
return c.deleteProject(ctx, "org", orgname, projectName)
|
||||
}
|
||||
|
||||
func (c *Client) deleteProject(ctx context.Context, ownertype, ownername, projectName string) (*http.Response, error) {
|
||||
return c.getResponse(ctx, "DELETE", path.Join("/projects", ownertype, ownername, projectName), nil, jsonContent, nil)
|
||||
}
|
||||
|
||||
func (c *Client) ReconfigProject(ctx context.Context, projectName string) (*http.Response, error) {
|
||||
|
@ -287,3 +324,18 @@ func (c *Client) CreateRemoteSource(ctx context.Context, req *CreateRemoteSource
|
|||
func (c *Client) DeleteRemoteSource(ctx context.Context, name string) (*http.Response, error) {
|
||||
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/remotesources/%s", name), nil, jsonContent, nil)
|
||||
}
|
||||
|
||||
func (c *Client) CreateOrg(ctx context.Context, req *CreateOrgRequest) (*OrgResponse, *http.Response, error) {
|
||||
reqj, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
org := new(OrgResponse)
|
||||
resp, err := c.getParsedResponse(ctx, "PUT", "/orgs", nil, jsonContent, bytes.NewReader(reqj), org)
|
||||
return org, resp, err
|
||||
}
|
||||
|
||||
func (c *Client) DeleteOrg(ctx context.Context, orgName string) (*http.Response, error) {
|
||||
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/orgs/%s", orgName), nil, jsonContent, nil)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,289 @@
|
|||
// Copyright 2019 Sorint.lab
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
csapi "github.com/sorintlab/agola/internal/services/configstore/api"
|
||||
"github.com/sorintlab/agola/internal/services/types"
|
||||
"github.com/sorintlab/agola/internal/util"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type CreateOrgRequest struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type CreateOrgHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
configstoreClient *csapi.Client
|
||||
}
|
||||
|
||||
func NewCreateOrgHandler(logger *zap.Logger, configstoreClient *csapi.Client) *CreateOrgHandler {
|
||||
return &CreateOrgHandler{log: logger.Sugar(), configstoreClient: configstoreClient}
|
||||
}
|
||||
|
||||
func (h *CreateOrgHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var req CreateOrgRequest
|
||||
d := json.NewDecoder(r.Body)
|
||||
if err := d.Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
org, err := h.createOrg(ctx, &req)
|
||||
if err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(org); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (h *CreateOrgHandler) createOrg(ctx context.Context, req *CreateOrgRequest) (*OrgResponse, error) {
|
||||
if !util.ValidateName(req.Name) {
|
||||
return nil, errors.Errorf("invalid org name %q", req.Name)
|
||||
}
|
||||
|
||||
u := &types.Organization{
|
||||
Name: req.Name,
|
||||
}
|
||||
|
||||
h.log.Infof("creating org")
|
||||
u, _, err := h.configstoreClient.CreateOrg(ctx, u)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to create org")
|
||||
}
|
||||
h.log.Infof("org %s created, ID: %s", u.Name, u.ID)
|
||||
|
||||
res := createOrgResponse(u)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type DeleteOrgHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
configstoreClient *csapi.Client
|
||||
}
|
||||
|
||||
func NewDeleteOrgHandler(logger *zap.Logger, configstoreClient *csapi.Client) *DeleteOrgHandler {
|
||||
return &DeleteOrgHandler{log: logger.Sugar(), configstoreClient: configstoreClient}
|
||||
}
|
||||
|
||||
func (h *DeleteOrgHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
vars := mux.Vars(r)
|
||||
orgName := vars["orgname"]
|
||||
|
||||
resp, err := h.configstoreClient.DeleteOrg(ctx, orgName)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type CurrentOrgHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
configstoreClient *csapi.Client
|
||||
}
|
||||
|
||||
func NewCurrentOrgHandler(logger *zap.Logger, configstoreClient *csapi.Client) *CurrentOrgHandler {
|
||||
return &CurrentOrgHandler{log: logger.Sugar(), configstoreClient: configstoreClient}
|
||||
}
|
||||
|
||||
func (h *CurrentOrgHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
orgIDVal := ctx.Value("orgid")
|
||||
if orgIDVal == nil {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
orgID := orgIDVal.(string)
|
||||
|
||||
org, resp, err := h.configstoreClient.GetOrg(ctx, orgID)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
res := createOrgResponse(org)
|
||||
if err := json.NewEncoder(w).Encode(res); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type OrgHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
configstoreClient *csapi.Client
|
||||
}
|
||||
|
||||
func NewOrgHandler(logger *zap.Logger, configstoreClient *csapi.Client) *OrgHandler {
|
||||
return &OrgHandler{log: logger.Sugar(), configstoreClient: configstoreClient}
|
||||
}
|
||||
|
||||
func (h *OrgHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
vars := mux.Vars(r)
|
||||
orgID := vars["orgid"]
|
||||
|
||||
org, resp, err := h.configstoreClient.GetOrg(ctx, orgID)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
res := createOrgResponse(org)
|
||||
if err := json.NewEncoder(w).Encode(res); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type OrgByNameHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
configstoreClient *csapi.Client
|
||||
}
|
||||
|
||||
func NewOrgByNameHandler(logger *zap.Logger, configstoreClient *csapi.Client) *OrgByNameHandler {
|
||||
return &OrgByNameHandler{log: logger.Sugar(), configstoreClient: configstoreClient}
|
||||
}
|
||||
|
||||
func (h *OrgByNameHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
vars := mux.Vars(r)
|
||||
orgName := vars["orgname"]
|
||||
|
||||
org, resp, err := h.configstoreClient.GetOrgByName(ctx, orgName)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
res := createOrgResponse(org)
|
||||
if err := json.NewEncoder(w).Encode(res); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type OrgsResponse struct {
|
||||
Orgs []*OrgResponse `json:"orgs"`
|
||||
}
|
||||
|
||||
type OrgResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func createOrgResponse(r *types.Organization) *OrgResponse {
|
||||
org := &OrgResponse{
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
}
|
||||
return org
|
||||
}
|
||||
|
||||
type OrgsHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
configstoreClient *csapi.Client
|
||||
}
|
||||
|
||||
func NewOrgsHandler(logger *zap.Logger, configstoreClient *csapi.Client) *OrgsHandler {
|
||||
return &OrgsHandler{log: logger.Sugar(), configstoreClient: configstoreClient}
|
||||
}
|
||||
|
||||
func (h *OrgsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
query := r.URL.Query()
|
||||
|
||||
limitS := query.Get("limit")
|
||||
limit := DefaultRunsLimit
|
||||
if limitS != "" {
|
||||
var err error
|
||||
limit, err = strconv.Atoi(limitS)
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
if limit < 0 {
|
||||
http.Error(w, "limit must be greater or equal than 0", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if limit > MaxRunsLimit {
|
||||
limit = MaxRunsLimit
|
||||
}
|
||||
asc := false
|
||||
if _, ok := query["asc"]; ok {
|
||||
asc = true
|
||||
}
|
||||
|
||||
start := query.Get("start")
|
||||
|
||||
csorgs, resp, err := h.configstoreClient.GetOrgs(ctx, start, limit, asc)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
orgs := make([]*OrgResponse, len(csorgs))
|
||||
for i, p := range csorgs {
|
||||
orgs[i] = createOrgResponse(p)
|
||||
}
|
||||
orgsResponse := &OrgsResponse{
|
||||
Orgs: orgs,
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(orgsResponse); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -15,10 +15,13 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
csapi "github.com/sorintlab/agola/internal/services/configstore/api"
|
||||
"github.com/sorintlab/agola/internal/services/gateway/command"
|
||||
"github.com/sorintlab/agola/internal/services/types"
|
||||
|
@ -47,6 +50,8 @@ func NewCreateProjectHandler(logger *zap.Logger, ch *command.CommandHandler, con
|
|||
|
||||
func (h *CreateProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
vars := mux.Vars(r)
|
||||
orgname := vars["orgname"]
|
||||
|
||||
var req CreateProjectRequest
|
||||
d := json.NewDecoder(r.Body)
|
||||
|
@ -57,19 +62,32 @@ func (h *CreateProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
ctxUserID := ctx.Value("userid")
|
||||
if ctxUserID == nil {
|
||||
http.Error(w, "no userid specified", http.StatusBadRequest)
|
||||
http.Error(w, "no authenticated user", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
userID := ctxUserID.(string)
|
||||
h.log.Infof("userID: %q", userID)
|
||||
|
||||
project, err := h.ch.CreateProject(ctx, &command.CreateProjectRequest{
|
||||
creq := &command.CreateProjectRequest{
|
||||
Name: req.Name,
|
||||
RepoURL: req.RepoURL,
|
||||
RemoteSourceName: req.RemoteSourceName,
|
||||
UserID: userID,
|
||||
SkipSSHHostKeyCheck: req.SkipSSHHostKeyCheck,
|
||||
})
|
||||
}
|
||||
|
||||
ownerID, code, userErr, err := getOwnerID(ctx, h.configstoreClient, "", orgname, true)
|
||||
if err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, userErr, code)
|
||||
return
|
||||
}
|
||||
if orgname != "" {
|
||||
creq.OwnerType = types.OwnerTypeOrganization
|
||||
creq.OwnerID = ownerID
|
||||
}
|
||||
|
||||
project, err := h.ch.CreateProject(ctx, creq)
|
||||
if err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
|
@ -81,7 +99,6 @@ func (h *CreateProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
|||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type ProjectReconfigHandler struct {
|
||||
|
@ -99,8 +116,17 @@ func (h *ProjectReconfigHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
|
|||
ctx := r.Context()
|
||||
vars := mux.Vars(r)
|
||||
projectName := vars["projectname"]
|
||||
username := vars["username"]
|
||||
orgname := vars["orgname"]
|
||||
|
||||
if err := h.ch.ReconfigProject(ctx, projectName); err != nil {
|
||||
ownerID, code, userErr, err := getOwnerID(ctx, h.configstoreClient, username, orgname, false)
|
||||
if err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, userErr, code)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.ch.ReconfigProject(ctx, ownerID, projectName); err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -120,8 +146,28 @@ func (h *DeleteProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
|||
ctx := r.Context()
|
||||
vars := mux.Vars(r)
|
||||
projectName := vars["projectname"]
|
||||
username := vars["username"]
|
||||
orgname := vars["orgname"]
|
||||
|
||||
resp, err := h.configstoreClient.DeleteProject(ctx, projectName)
|
||||
ownerID, code, userErr, err := getOwnerID(ctx, h.configstoreClient, username, orgname, true)
|
||||
if err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, userErr, code)
|
||||
return
|
||||
}
|
||||
|
||||
project, resp, err := h.configstoreClient.GetProjectByName(ctx, ownerID, projectName)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
http.Error(w, fmt.Sprintf("project with name %q doesn't exist", projectName), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err = h.configstoreClient.DeleteProject(ctx, project.ID)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
|
@ -179,8 +225,17 @@ func (h *ProjectByNameHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
|||
ctx := r.Context()
|
||||
vars := mux.Vars(r)
|
||||
projectName := vars["projectname"]
|
||||
username := vars["username"]
|
||||
orgname := vars["orgname"]
|
||||
|
||||
project, resp, err := h.configstoreClient.GetProjectByName(ctx, projectName)
|
||||
ownerID, code, userErr, err := getOwnerID(ctx, h.configstoreClient, username, orgname, false)
|
||||
if err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, userErr, code)
|
||||
return
|
||||
}
|
||||
|
||||
project, resp, err := h.configstoreClient.GetProjectByName(ctx, ownerID, projectName)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
|
@ -228,9 +283,19 @@ func NewProjectsHandler(logger *zap.Logger, configstoreClient *csapi.Client) *Pr
|
|||
|
||||
func (h *ProjectsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
vars := mux.Vars(r)
|
||||
query := r.URL.Query()
|
||||
|
||||
username := vars["username"]
|
||||
orgname := vars["orgname"]
|
||||
|
||||
ownerID, code, userErr, err := getOwnerID(ctx, h.configstoreClient, username, orgname, true)
|
||||
if err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
http.Error(w, userErr, code)
|
||||
return
|
||||
}
|
||||
|
||||
limitS := query.Get("limit")
|
||||
limit := DefaultRunsLimit
|
||||
if limitS != "" {
|
||||
|
@ -255,7 +320,7 @@ func (h *ProjectsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
start := query.Get("start")
|
||||
|
||||
csprojects, resp, err := h.configstoreClient.GetProjects(ctx, start, limit, asc)
|
||||
csprojects, resp, err := h.configstoreClient.GetOwnerProjects(ctx, ownerID, start, limit, asc)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
|
@ -280,3 +345,44 @@ func (h *ProjectsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getOwnerID(ctx context.Context, configstoreClient *csapi.Client, username, orgname string, useAuthUser bool) (string, int, string, error) {
|
||||
var ownerID string
|
||||
switch {
|
||||
case username != "":
|
||||
user, resp, err := configstoreClient.GetUserByName(ctx, username)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
err = errors.Errorf("user %q doens't exist", username)
|
||||
return "", http.StatusNotFound, err.Error(), err
|
||||
}
|
||||
return "", http.StatusInternalServerError, "", err
|
||||
}
|
||||
ownerID = user.ID
|
||||
case orgname != "":
|
||||
org, resp, err := configstoreClient.GetOrgByName(ctx, orgname)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
err = errors.Errorf("organization %q doens't exist", orgname)
|
||||
return "", http.StatusNotFound, err.Error(), err
|
||||
}
|
||||
return "", http.StatusInternalServerError, "", err
|
||||
}
|
||||
ownerID = org.ID
|
||||
default:
|
||||
if useAuthUser {
|
||||
// use the current authenticated user
|
||||
ctxUserID := ctx.Value("userid")
|
||||
if ctxUserID == nil {
|
||||
err := errors.New("no authenticated user")
|
||||
return "", http.StatusBadRequest, err.Error(), err
|
||||
}
|
||||
ownerID = ctxUserID.(string)
|
||||
} else {
|
||||
err := errors.New("no user or org name specified")
|
||||
return "", http.StatusBadRequest, err.Error(), err
|
||||
}
|
||||
}
|
||||
|
||||
return ownerID, 0, "", nil
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ type CreateProjectRequest struct {
|
|||
RemoteSourceName string
|
||||
RepoURL string
|
||||
UserID string
|
||||
OwnerType types.OwnerType
|
||||
OwnerID string
|
||||
SkipSSHHostKeyCheck bool
|
||||
}
|
||||
|
||||
|
@ -86,6 +88,8 @@ func (c *CommandHandler) CreateProject(ctx context.Context, req *CreateProjectRe
|
|||
|
||||
p := &types.Project{
|
||||
Name: req.Name,
|
||||
OwnerType: types.OwnerTypeUser,
|
||||
OwnerID: user.ID,
|
||||
LinkedAccountID: la.ID,
|
||||
Path: fmt.Sprintf("%s/%s", repoOwner, repoName),
|
||||
CloneURL: cloneURL,
|
||||
|
@ -93,6 +97,14 @@ func (c *CommandHandler) CreateProject(ctx context.Context, req *CreateProjectRe
|
|||
SSHPrivateKey: string(privateKey),
|
||||
}
|
||||
|
||||
if req.OwnerType == types.OwnerTypeOrganization {
|
||||
if req.OwnerID == "" {
|
||||
return nil, errors.Errorf("ownerid must be specified when adding a project outside the current user")
|
||||
}
|
||||
p.OwnerType = req.OwnerType
|
||||
p.OwnerID = req.OwnerID
|
||||
}
|
||||
|
||||
c.log.Infof("creating project")
|
||||
p, _, err = c.configstoreClient.CreateProject(ctx, p)
|
||||
if err != nil {
|
||||
|
@ -141,8 +153,8 @@ func (c *CommandHandler) SetupProject(ctx context.Context, rs *types.RemoteSourc
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *CommandHandler) ReconfigProject(ctx context.Context, projectName string) error {
|
||||
p, _, err := c.configstoreClient.GetProjectByName(ctx, projectName)
|
||||
func (c *CommandHandler) ReconfigProject(ctx context.Context, ownerID, projectName string) error {
|
||||
p, _, err := c.configstoreClient.GetProjectByName(ctx, ownerID, projectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -165,6 +165,12 @@ func (g *Gateway) Run(ctx context.Context) error {
|
|||
createRemoteSourceHandler := api.NewCreateRemoteSourceHandler(logger, g.configstoreClient)
|
||||
remoteSourcesHandler := api.NewRemoteSourcesHandler(logger, g.configstoreClient)
|
||||
|
||||
orgHandler := api.NewOrgHandler(logger, g.configstoreClient)
|
||||
orgByNameHandler := api.NewOrgByNameHandler(logger, g.configstoreClient)
|
||||
orgsHandler := api.NewOrgsHandler(logger, g.configstoreClient)
|
||||
createOrgHandler := api.NewCreateOrgHandler(logger, g.configstoreClient)
|
||||
deleteOrgHandler := api.NewDeleteOrgHandler(logger, g.configstoreClient)
|
||||
|
||||
runHandler := api.NewRunHandler(logger, g.runserviceClient)
|
||||
runsHandler := api.NewRunsHandler(logger, g.runserviceClient)
|
||||
runtaskHandler := api.NewRuntaskHandler(logger, g.runserviceClient)
|
||||
|
@ -188,11 +194,18 @@ func (g *Gateway) Run(ctx context.Context) error {
|
|||
apirouter.Handle("/logs", logsHandler).Methods("GET")
|
||||
|
||||
apirouter.Handle("/project/{projectid}", authForcedHandler(projectHandler)).Methods("GET")
|
||||
apirouter.Handle("/projects", authForcedHandler(projectsHandler)).Methods("GET")
|
||||
apirouter.Handle("/projects", authForcedHandler(createProjectHandler)).Methods("PUT")
|
||||
apirouter.Handle("/projects/{projectname}", authForcedHandler(projectByNameHandler)).Methods("GET")
|
||||
apirouter.Handle("/projects/{projectname}", authForcedHandler(deleteProjectHandler)).Methods("DELETE")
|
||||
apirouter.Handle("/projects/{projectname}/reconfig", authForcedHandler(projectReconfigHandler)).Methods("POST")
|
||||
apirouter.Handle("/user/projects", authForcedHandler(projectsHandler)).Methods("GET")
|
||||
apirouter.Handle("/user/{username}/projects", authForcedHandler(projectsHandler)).Methods("GET")
|
||||
apirouter.Handle("/org/{orgname}/projects", authForcedHandler(projectsHandler)).Methods("GET")
|
||||
apirouter.Handle("/user/projects", authForcedHandler(createProjectHandler)).Methods("PUT")
|
||||
apirouter.Handle("/org/{orgname}/projects", authForcedHandler(createProjectHandler)).Methods("PUT")
|
||||
apirouter.Handle("/projects/user/{username}/{projectname}", authForcedHandler(projectByNameHandler)).Methods("GET")
|
||||
apirouter.Handle("/projects/org/{orgname}/{projectname}", authForcedHandler(projectByNameHandler)).Methods("GET")
|
||||
apirouter.Handle("/projects/user/{projectname}", authForcedHandler(deleteProjectHandler)).Methods("DELETE")
|
||||
apirouter.Handle("/projects/user/{username}/{projectname}", authForcedHandler(deleteProjectHandler)).Methods("DELETE")
|
||||
apirouter.Handle("/projects/org/{orgname}/{projectname}", authForcedHandler(deleteProjectHandler)).Methods("DELETE")
|
||||
apirouter.Handle("/projects/user/{username}/{projectname}/reconfig", authForcedHandler(projectReconfigHandler)).Methods("POST")
|
||||
apirouter.Handle("/projects/org/{orgname}/{projectname}/reconfig", authForcedHandler(projectReconfigHandler)).Methods("POST")
|
||||
|
||||
apirouter.Handle("/user", authForcedHandler(currentUserHandler)).Methods("GET")
|
||||
apirouter.Handle("/user/{userid}", authForcedHandler(userHandler)).Methods("GET")
|
||||
|
@ -209,6 +222,12 @@ func (g *Gateway) Run(ctx context.Context) error {
|
|||
apirouter.Handle("/remotesources", authForcedHandler(createRemoteSourceHandler)).Methods("PUT")
|
||||
apirouter.Handle("/remotesources", authOptionalHandler(remoteSourcesHandler)).Methods("GET")
|
||||
|
||||
apirouter.Handle("/org/{orgid}", authForcedHandler(orgHandler)).Methods("GET")
|
||||
apirouter.Handle("/orgs", authForcedHandler(orgsHandler)).Methods("GET")
|
||||
apirouter.Handle("/orgs", authForcedHandler(createOrgHandler)).Methods("PUT")
|
||||
apirouter.Handle("/orgs/{orgname}", authForcedHandler(orgByNameHandler)).Methods("GET")
|
||||
apirouter.Handle("/orgs/{orgname}", authForcedHandler(deleteOrgHandler)).Methods("DELETE")
|
||||
|
||||
apirouter.Handle("/run/{runid}", authForcedHandler(runHandler)).Methods("GET")
|
||||
apirouter.Handle("/run/{runid}/task/{taskid}", authForcedHandler(runtaskHandler)).Methods("GET")
|
||||
apirouter.Handle("/runs", authForcedHandler(runsHandler)).Methods("GET")
|
||||
|
|
Loading…
Reference in New Issue