From c30707528f72cce323da328ccb882d03566fe7e5 Mon Sep 17 00:00:00 2001 From: Simone Gotti Date: Fri, 3 May 2019 12:47:22 +0200 Subject: [PATCH] configstore: split commands in multiple files --- .../services/configstore/command/command.go | 1344 ----------------- internal/services/configstore/command/org.go | 150 ++ .../services/configstore/command/project.go | 196 +++ .../configstore/command/projectgroup.go | 104 ++ .../configstore/command/remotesource.go | 150 ++ .../services/configstore/command/secret.go | 153 ++ internal/services/configstore/command/user.go | 626 ++++++++ .../services/configstore/command/variable.go | 146 ++ 8 files changed, 1525 insertions(+), 1344 deletions(-) create mode 100644 internal/services/configstore/command/org.go create mode 100644 internal/services/configstore/command/project.go create mode 100644 internal/services/configstore/command/projectgroup.go create mode 100644 internal/services/configstore/command/remotesource.go create mode 100644 internal/services/configstore/command/secret.go create mode 100644 internal/services/configstore/command/user.go create mode 100644 internal/services/configstore/command/variable.go diff --git a/internal/services/configstore/command/command.go b/internal/services/configstore/command/command.go index 756eaa0..9ebfaf6 100644 --- a/internal/services/configstore/command/command.go +++ b/internal/services/configstore/command/command.go @@ -15,19 +15,9 @@ package command import ( - "context" - "encoding/json" - "path" - "time" - "github.com/sorintlab/agola/internal/datamanager" - "github.com/sorintlab/agola/internal/db" "github.com/sorintlab/agola/internal/services/configstore/readdb" - "github.com/sorintlab/agola/internal/services/types" - "github.com/sorintlab/agola/internal/util" - "github.com/pkg/errors" - uuid "github.com/satori/go.uuid" "go.uber.org/zap" ) @@ -44,1337 +34,3 @@ func NewCommandHandler(logger *zap.Logger, readDB *readdb.ReadDB, dm *datamanage dm: dm, } } - -func (s *CommandHandler) CreateProjectGroup(ctx context.Context, projectGroup *types.ProjectGroup) (*types.ProjectGroup, error) { - if projectGroup.Name == "" { - return nil, util.NewErrBadRequest(errors.Errorf("project group name required")) - } - if !util.ValidateName(projectGroup.Name) { - return nil, util.NewErrBadRequest(errors.Errorf("invalid project group name %q", projectGroup.Name)) - } - if projectGroup.Parent.ID == "" { - return nil, util.NewErrBadRequest(errors.Errorf("project group parent id required")) - } - if !types.IsValidVisibility(projectGroup.Visibility) { - return nil, util.NewErrBadRequest(errors.Errorf("invalid project group visibility")) - } - - var cgt *datamanager.ChangeGroupsUpdateToken - - // must do all the checks in a single transaction to avoid concurrent changes - err := s.readDB.Do(func(tx *db.Tx) error { - parentProjectGroup, err := s.readDB.GetProjectGroup(tx, projectGroup.Parent.ID) - if err != nil { - return err - } - if parentProjectGroup == nil { - return util.NewErrBadRequest(errors.Errorf("project group with id %q doesn't exist", projectGroup.Parent.ID)) - } - projectGroup.Parent.ID = parentProjectGroup.ID - - groupPath, err := s.readDB.GetProjectGroupPath(tx, parentProjectGroup) - if err != nil { - return err - } - pp := path.Join(groupPath, projectGroup.Name) - - // changegroup is the projectgroup path. Use "projectpath" prefix as it must - // cover both projects and projectgroups - cgNames := []string{util.EncodeSha256Hex("projectpath-" + pp)} - cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) - if err != nil { - return err - } - - // check duplicate project name - p, err := s.readDB.GetProjectByName(tx, projectGroup.Parent.ID, projectGroup.Name) - if err != nil { - return err - } - if p != nil { - return util.NewErrBadRequest(errors.Errorf("project with name %q, path %q already exists", p.Name, pp)) - } - // check duplicate project group name - pg, err := s.readDB.GetProjectGroupByName(tx, projectGroup.Parent.ID, projectGroup.Name) - if err != nil { - return err - } - if pg != nil { - return util.NewErrBadRequest(errors.Errorf("project group with name %q, path %q already exists", pg.Name, pp)) - } - return nil - }) - if err != nil { - return nil, err - } - - projectGroup.ID = uuid.NewV4().String() - projectGroup.Parent.Type = types.ConfigTypeProjectGroup - - pcj, err := json.Marshal(projectGroup) - if err != nil { - return nil, errors.Wrapf(err, "failed to marshal projectGroup") - } - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeProjectGroup), - ID: projectGroup.ID, - Data: pcj, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return projectGroup, err -} - -func (s *CommandHandler) CreateProject(ctx context.Context, project *types.Project) (*types.Project, error) { - if project.Name == "" { - return nil, util.NewErrBadRequest(errors.Errorf("project name required")) - } - if !util.ValidateName(project.Name) { - return nil, util.NewErrBadRequest(errors.Errorf("invalid project name %q", project.Name)) - } - if project.Parent.ID == "" { - return nil, util.NewErrBadRequest(errors.Errorf("project parent id required")) - } - if project.Parent.Type != types.ConfigTypeProjectGroup { - return nil, util.NewErrBadRequest(errors.Errorf("invalid project parent type %q", project.Parent.Type)) - } - if !types.IsValidVisibility(project.Visibility) { - return nil, util.NewErrBadRequest(errors.Errorf("invalid project visibility")) - } - if !types.IsValidRemoteRepositoryConfigType(project.RemoteRepositoryConfigType) { - return nil, util.NewErrBadRequest(errors.Errorf("invalid project remote repository config type %q", project.RemoteRepositoryConfigType)) - } - if project.RemoteRepositoryConfigType == types.RemoteRepositoryConfigTypeRemoteSource { - if project.RemoteSourceID == "" { - return nil, util.NewErrBadRequest(errors.Errorf("empty remote source id")) - } - if project.LinkedAccountID == "" { - return nil, util.NewErrBadRequest(errors.Errorf("empty linked account id")) - } - if project.RepositoryID == "" { - return nil, util.NewErrBadRequest(errors.Errorf("empty remote repository id")) - } - if project.RepositoryPath == "" { - return nil, util.NewErrBadRequest(errors.Errorf("empty remote repository path")) - } - } - - var cgt *datamanager.ChangeGroupsUpdateToken - - // must do all the checks in a single transaction to avoid concurrent changes - err := s.readDB.Do(func(tx *db.Tx) error { - var err error - group, err := s.readDB.GetProjectGroup(tx, project.Parent.ID) - if err != nil { - return err - } - if group == nil { - return util.NewErrBadRequest(errors.Errorf("project group with id %q doesn't exist", project.Parent.ID)) - } - project.Parent.ID = group.ID - - groupPath, err := s.readDB.GetProjectGroupPath(tx, group) - if err != nil { - return err - } - pp := path.Join(groupPath, project.Name) - - // changegroup is the project path. Use "projectpath" prefix as it must - // cover both projects and projectgroups - cgNames := []string{util.EncodeSha256Hex("projectpath-" + pp)} - cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) - if err != nil { - return err - } - - // check duplicate project name - p, err := s.readDB.GetProjectByName(tx, project.Parent.ID, project.Name) - if err != nil { - return err - } - if p != nil { - return util.NewErrBadRequest(errors.Errorf("project with name %q, path %q already exists", p.Name, pp)) - } - // check duplicate project group name - pg, err := s.readDB.GetProjectGroupByName(tx, project.Parent.ID, project.Name) - if err != nil { - return err - } - if pg != nil { - return util.NewErrBadRequest(errors.Errorf("project group with name %q, path %q already exists", pg.Name, pp)) - } - - if project.RemoteRepositoryConfigType == types.RemoteRepositoryConfigTypeRemoteSource { - // check that the linked account matches the remote source - user, err := s.readDB.GetUserByLinkedAccount(tx, project.LinkedAccountID) - if err != nil { - return errors.Wrapf(err, "failed to get user with linked account id %q", project.LinkedAccountID) - } - if user == nil { - return util.NewErrBadRequest(errors.Errorf("user for linked account %q doesn't exist", project.LinkedAccountID)) - } - la, ok := user.LinkedAccounts[project.LinkedAccountID] - if !ok { - return util.NewErrBadRequest(errors.Errorf("linked account id %q for user %q doesn't exist", project.LinkedAccountID, user.Name)) - } - if la.RemoteSourceID != project.RemoteSourceID { - return util.NewErrBadRequest(errors.Errorf("linked account id %q remote source %q different than project remote source %q", project.LinkedAccountID, la.RemoteSourceID, project.RemoteSourceID)) - } - } - - return nil - }) - if err != nil { - return nil, err - } - - project.ID = uuid.NewV4().String() - project.Parent.Type = types.ConfigTypeProjectGroup - - pcj, err := json.Marshal(project) - if err != nil { - return nil, errors.Wrapf(err, "failed to marshal project") - } - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeProject), - ID: project.ID, - Data: pcj, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return project, err -} - -func (s *CommandHandler) DeleteProject(ctx context.Context, projectRef string) error { - var project *types.Project - - var cgt *datamanager.ChangeGroupsUpdateToken - - // must do all the checks in a single transaction to avoid concurrent changes - err := s.readDB.Do(func(tx *db.Tx) error { - var err error - - // check project existance - project, err = s.readDB.GetProject(tx, projectRef) - if err != nil { - return err - } - if project == nil { - return util.NewErrBadRequest(errors.Errorf("project %q doesn't exist", projectRef)) - } - - // changegroup is the project id. - cgNames := []string{util.EncodeSha256Hex(project.ID)} - cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return err - } - - // TODO(sgotti) delete project secrets/variables - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypeDelete, - DataType: string(types.ConfigTypeProject), - ID: project.ID, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return err -} - -type CreateUserRequest struct { - UserName string - - CreateUserLARequest *CreateUserLARequest -} - -func (s *CommandHandler) CreateUser(ctx context.Context, req *CreateUserRequest) (*types.User, error) { - if req.UserName == "" { - return nil, util.NewErrBadRequest(errors.Errorf("user name required")) - } - if !util.ValidateName(req.UserName) { - return nil, util.NewErrBadRequest(errors.Errorf("invalid user name %q", req.UserName)) - } - - var cgt *datamanager.ChangeGroupsUpdateToken - // changegroup is the username (and in future the email) to ensure no - // concurrent user creation/modification using the same name - cgNames := []string{util.EncodeSha256Hex("username-" + req.UserName)} - var rs *types.RemoteSource - - // must do all the checks 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 user name - u, err := s.readDB.GetUserByName(tx, req.UserName) - if err != nil { - return err - } - if u != nil { - return util.NewErrBadRequest(errors.Errorf("user with name %q already exists", u.Name)) - } - - if req.CreateUserLARequest != nil { - rs, err = s.readDB.GetRemoteSourceByName(tx, req.CreateUserLARequest.RemoteSourceName) - if err != nil { - return err - } - if rs == nil { - return util.NewErrBadRequest(errors.Errorf("remote source %q doesn't exist", req.CreateUserLARequest.RemoteSourceName)) - } - user, err := s.readDB.GetUserByLinkedAccountRemoteUserIDandSource(tx, req.CreateUserLARequest.RemoteUserID, rs.ID) - if err != nil { - return errors.Wrapf(err, "failed to get user for remote user id %q and remote source %q", req.CreateUserLARequest.RemoteUserID, rs.ID) - } - if user != nil { - return util.NewErrBadRequest(errors.Errorf("user for remote user id %q for remote source %q already exists", req.CreateUserLARequest.RemoteUserID, req.CreateUserLARequest.RemoteSourceName)) - } - } - return nil - }) - if err != nil { - return nil, err - } - - user := &types.User{ - ID: uuid.NewV4().String(), - Name: req.UserName, - } - if req.CreateUserLARequest != nil { - if user.LinkedAccounts == nil { - user.LinkedAccounts = make(map[string]*types.LinkedAccount) - } - - la := &types.LinkedAccount{ - ID: uuid.NewV4().String(), - RemoteSourceID: rs.ID, - RemoteUserID: req.CreateUserLARequest.RemoteUserID, - RemoteUserName: req.CreateUserLARequest.RemoteUserName, - UserAccessToken: req.CreateUserLARequest.UserAccessToken, - Oauth2AccessToken: req.CreateUserLARequest.Oauth2AccessToken, - Oauth2RefreshToken: req.CreateUserLARequest.Oauth2RefreshToken, - Oauth2AccessTokenExpiresAt: req.CreateUserLARequest.Oauth2AccessTokenExpiresAt, - } - - user.LinkedAccounts[la.ID] = la - } - - userj, err := json.Marshal(user) - if err != nil { - return nil, errors.Wrapf(err, "failed to marshal user") - } - - pg := &types.ProjectGroup{ - ID: uuid.NewV4().String(), - Parent: types.Parent{ - Type: types.ConfigTypeUser, - ID: user.ID, - }, - } - pgj, err := json.Marshal(pg) - if err != nil { - return nil, errors.Wrapf(err, "failed to marshal project group") - } - - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeUser), - ID: user.ID, - Data: userj, - }, - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeProjectGroup), - ID: pg.ID, - Data: pgj, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return user, err -} - -func (s *CommandHandler) DeleteUser(ctx context.Context, userRef string) error { - var user *types.User - - var cgt *datamanager.ChangeGroupsUpdateToken - // must do all the checks in a single transaction to avoid concurrent changes - err := s.readDB.Do(func(tx *db.Tx) error { - var err error - - // check user existance - user, err = s.readDB.GetUser(tx, userRef) - if err != nil { - return err - } - if user == nil { - return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", userRef)) - } - - // changegroup is the userid - cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)} - cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return err - } - - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypeDelete, - DataType: string(types.ConfigTypeUser), - ID: user.ID, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return err -} - -type UpdateUserRequest struct { - UserRef string - - UserName string -} - -func (s *CommandHandler) UpdateUser(ctx context.Context, req *UpdateUserRequest) (*types.User, error) { - var cgt *datamanager.ChangeGroupsUpdateToken - - cgNames := []string{} - var user *types.User - - // must do all the checks in a single transaction to avoid concurrent changes - err := s.readDB.Do(func(tx *db.Tx) error { - var err error - user, err = s.readDB.GetUser(tx, req.UserRef) - if err != nil { - return err - } - if user == nil { - return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", req.UserRef)) - } - - cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) - if err != nil { - return err - } - - if req.UserName != "" { - // check duplicate user name - u, err := s.readDB.GetUserByName(tx, req.UserName) - if err != nil { - return err - } - if u != nil { - return util.NewErrBadRequest(errors.Errorf("user with name %q already exists", u.Name)) - } - // changegroup is the username (and in future the email) to ensure no - // concurrent user creation/modification using the same name - cgNames = append(cgNames, util.EncodeSha256Hex("username-"+req.UserName)) - } - - return nil - }) - if err != nil { - return nil, err - } - - if req.UserName != "" { - user.Name = req.UserName - } - - userj, err := json.Marshal(user) - if err != nil { - return nil, errors.Wrapf(err, "failed to marshal user") - } - - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeUser), - ID: user.ID, - Data: userj, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return user, err -} - -type CreateUserLARequest struct { - UserRef string - - RemoteSourceName string - RemoteUserID string - RemoteUserName string - UserAccessToken string - Oauth2AccessToken string - Oauth2RefreshToken string - Oauth2AccessTokenExpiresAt time.Time -} - -func (s *CommandHandler) CreateUserLA(ctx context.Context, req *CreateUserLARequest) (*types.LinkedAccount, error) { - if req.UserRef == "" { - return nil, util.NewErrBadRequest(errors.Errorf("user ref required")) - } - if req.RemoteSourceName == "" { - return nil, util.NewErrBadRequest(errors.Errorf("remote source name required")) - } - - var user *types.User - var rs *types.RemoteSource - - var cgt *datamanager.ChangeGroupsUpdateToken - - // must do all the checks in a single transaction to avoid concurrent changes - err := s.readDB.Do(func(tx *db.Tx) error { - var err error - user, err = s.readDB.GetUser(tx, req.UserRef) - if err != nil { - return err - } - if user == nil { - return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", req.UserRef)) - } - - // changegroup is the userid - cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)} - cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) - if err != nil { - return err - } - - rs, err = s.readDB.GetRemoteSourceByName(tx, req.RemoteSourceName) - if err != nil { - return err - } - if rs == nil { - return util.NewErrBadRequest(errors.Errorf("remote source %q doesn't exist", req.RemoteSourceName)) - } - - user, err := s.readDB.GetUserByLinkedAccountRemoteUserIDandSource(tx, req.RemoteUserID, rs.ID) - if err != nil { - return errors.Wrapf(err, "failed to get user for remote user id %q and remote source %q", req.RemoteUserID, rs.ID) - } - if user != nil { - return util.NewErrBadRequest(errors.Errorf("user for remote user id %q for remote source %q already exists", req.RemoteUserID, req.RemoteSourceName)) - } - return nil - }) - if err != nil { - return nil, err - } - - if user.LinkedAccounts == nil { - user.LinkedAccounts = make(map[string]*types.LinkedAccount) - } - - la := &types.LinkedAccount{ - ID: uuid.NewV4().String(), - RemoteSourceID: rs.ID, - RemoteUserID: req.RemoteUserID, - RemoteUserName: req.RemoteUserName, - UserAccessToken: req.UserAccessToken, - Oauth2AccessToken: req.Oauth2AccessToken, - Oauth2RefreshToken: req.Oauth2RefreshToken, - Oauth2AccessTokenExpiresAt: req.Oauth2AccessTokenExpiresAt, - } - - user.LinkedAccounts[la.ID] = la - - userj, err := json.Marshal(user) - if err != nil { - return nil, errors.Wrapf(err, "failed to marshal user") - } - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeUser), - ID: user.ID, - Data: userj, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return la, err -} - -func (s *CommandHandler) DeleteUserLA(ctx context.Context, userRef, laID string) error { - if userRef == "" { - return util.NewErrBadRequest(errors.Errorf("user ref required")) - } - if laID == "" { - return util.NewErrBadRequest(errors.Errorf("user linked account id required")) - } - - var user *types.User - - var cgt *datamanager.ChangeGroupsUpdateToken - - // must do all the checks in a single transaction to avoid concurrent changes - err := s.readDB.Do(func(tx *db.Tx) error { - var err error - user, err = s.readDB.GetUser(tx, userRef) - if err != nil { - return err - } - if user == nil { - return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", userRef)) - } - - // changegroup is the userid - cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)} - cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return err - } - - _, ok := user.LinkedAccounts[laID] - if !ok { - return util.NewErrBadRequest(errors.Errorf("linked account id %q for user %q doesn't exist", laID, userRef)) - } - - delete(user.LinkedAccounts, laID) - - userj, err := json.Marshal(user) - if err != nil { - return errors.Wrapf(err, "failed to marshal user") - } - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeUser), - ID: user.ID, - Data: userj, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return err -} - -type UpdateUserLARequest struct { - UserRef string - - LinkedAccountID string - RemoteUserID string - RemoteUserName string - UserAccessToken string - Oauth2AccessToken string - Oauth2RefreshToken string - Oauth2AccessTokenExpiresAt time.Time -} - -func (s *CommandHandler) UpdateUserLA(ctx context.Context, req *UpdateUserLARequest) (*types.LinkedAccount, error) { - if req.UserRef == "" { - return nil, util.NewErrBadRequest(errors.Errorf("user ref required")) - } - - var user *types.User - var rs *types.RemoteSource - - var cgt *datamanager.ChangeGroupsUpdateToken - - // must do all the checks in a single transaction to avoid concurrent changes - err := s.readDB.Do(func(tx *db.Tx) error { - var err error - user, err = s.readDB.GetUser(tx, req.UserRef) - if err != nil { - return err - } - if user == nil { - return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", req.UserRef)) - } - - // changegroup is the userid - cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)} - cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) - if err != nil { - return err - } - - la, ok := user.LinkedAccounts[req.LinkedAccountID] - if !ok { - return util.NewErrBadRequest(errors.Errorf("linked account id %q for user %q doesn't exist", req.LinkedAccountID, user.Name)) - } - - rs, err = s.readDB.GetRemoteSource(tx, la.RemoteSourceID) - if err != nil { - return err - } - if rs == nil { - return util.NewErrBadRequest(errors.Errorf("remote source with id %q doesn't exist", la.RemoteSourceID)) - } - return nil - }) - if err != nil { - return nil, err - } - - la := user.LinkedAccounts[req.LinkedAccountID] - - la.RemoteUserID = req.RemoteUserID - la.RemoteUserName = req.RemoteUserName - la.UserAccessToken = req.UserAccessToken - la.Oauth2AccessToken = req.Oauth2AccessToken - la.Oauth2RefreshToken = req.Oauth2RefreshToken - la.Oauth2AccessTokenExpiresAt = req.Oauth2AccessTokenExpiresAt - - userj, err := json.Marshal(user) - if err != nil { - return nil, errors.Wrapf(err, "failed to marshal user") - } - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeUser), - ID: user.ID, - Data: userj, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return la, err -} - -func (s *CommandHandler) CreateUserToken(ctx context.Context, userRef, tokenName string) (string, error) { - if userRef == "" { - return "", util.NewErrBadRequest(errors.Errorf("user ref required")) - } - if tokenName == "" { - return "", util.NewErrBadRequest(errors.Errorf("token name required")) - } - - var user *types.User - - var cgt *datamanager.ChangeGroupsUpdateToken - - // must do all the checks in a single transaction to avoid concurrent changes - err := s.readDB.Do(func(tx *db.Tx) error { - var err error - user, err = s.readDB.GetUser(tx, userRef) - if err != nil { - return err - } - if user == nil { - return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", userRef)) - } - - // changegroup is the userid - cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)} - cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return "", err - } - if user.Tokens != nil { - if _, ok := user.Tokens[tokenName]; ok { - return "", util.NewErrBadRequest(errors.Errorf("token %q for user %q already exists", tokenName, userRef)) - } - } - - if user.Tokens == nil { - user.Tokens = make(map[string]string) - } - - token := util.EncodeSha1Hex(uuid.NewV4().String()) - user.Tokens[tokenName] = token - - userj, err := json.Marshal(user) - if err != nil { - return "", errors.Wrapf(err, "failed to marshal user") - } - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeUser), - ID: user.ID, - Data: userj, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return token, err -} - -func (s *CommandHandler) DeleteUserToken(ctx context.Context, userRef, tokenName string) error { - if userRef == "" { - return util.NewErrBadRequest(errors.Errorf("user ref required")) - } - if tokenName == "" { - return util.NewErrBadRequest(errors.Errorf("token name required")) - } - - var user *types.User - - var cgt *datamanager.ChangeGroupsUpdateToken - - // must do all the checks in a single transaction to avoid concurrent changes - err := s.readDB.Do(func(tx *db.Tx) error { - var err error - user, err = s.readDB.GetUser(tx, userRef) - if err != nil { - return err - } - if user == nil { - return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", userRef)) - } - - // changegroup is the userid - cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)} - cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return err - } - - _, ok := user.Tokens[tokenName] - if !ok { - return util.NewErrBadRequest(errors.Errorf("token %q for user %q doesn't exist", tokenName, userRef)) - } - - delete(user.Tokens, tokenName) - - userj, err := json.Marshal(user) - if err != nil { - return errors.Wrapf(err, "failed to marshal user") - } - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeUser), - ID: user.ID, - Data: userj, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return err -} - -func (s *CommandHandler) CreateRemoteSource(ctx context.Context, remoteSource *types.RemoteSource) (*types.RemoteSource, error) { - if remoteSource.Name == "" { - return nil, util.NewErrBadRequest(errors.Errorf("remotesource name required")) - } - if !util.ValidateName(remoteSource.Name) { - return nil, util.NewErrBadRequest(errors.Errorf("invalid remotesource name %q", remoteSource.Name)) - } - - if remoteSource.Name == "" { - return nil, util.NewErrBadRequest(errors.Errorf("remotesource name required")) - } - if remoteSource.APIURL == "" { - return nil, util.NewErrBadRequest(errors.Errorf("remotesource api url required")) - } - if remoteSource.Type == "" { - return nil, util.NewErrBadRequest(errors.Errorf("remotesource type required")) - } - if remoteSource.AuthType == "" { - return nil, util.NewErrBadRequest(errors.Errorf("remotesource auth type required")) - } - - // validate if the remote source type supports the required auth type - if !types.SourceSupportsAuthType(types.RemoteSourceType(remoteSource.Type), types.RemoteSourceAuthType(remoteSource.AuthType)) { - return nil, util.NewErrBadRequest(errors.Errorf("remotesource type %q doesn't support auth type %q", remoteSource.Type, remoteSource.AuthType)) - } - if remoteSource.AuthType == types.RemoteSourceAuthTypeOauth2 { - if remoteSource.Oauth2ClientID == "" { - return nil, util.NewErrBadRequest(errors.Errorf("remotesource oauth2clientid required for auth type %q", types.RemoteSourceAuthTypeOauth2)) - } - if remoteSource.Oauth2ClientSecret == "" { - return nil, util.NewErrBadRequest(errors.Errorf("remotesource oauth2clientsecret required for auth type %q", types.RemoteSourceAuthTypeOauth2)) - } - } - - var cgt *datamanager.ChangeGroupsUpdateToken - // changegroup is the remotesource name - cgNames := []string{util.EncodeSha256Hex("remotesourcename-" + remoteSource.Name)} - - // must do all the checks 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 remoteSource name - u, err := s.readDB.GetRemoteSourceByName(tx, remoteSource.Name) - if err != nil { - return err - } - if u != nil { - return util.NewErrBadRequest(errors.Errorf("remoteSource %q already exists", u.Name)) - } - return nil - }) - if err != nil { - return nil, err - } - - remoteSource.ID = uuid.NewV4().String() - - rsj, err := json.Marshal(remoteSource) - if err != nil { - return nil, errors.Wrapf(err, "failed to marshal remotesource") - } - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeRemoteSource), - ID: remoteSource.ID, - Data: rsj, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return remoteSource, err -} - -func (s *CommandHandler) DeleteRemoteSource(ctx context.Context, remoteSourceName string) error { - var remoteSource *types.RemoteSource - - var cgt *datamanager.ChangeGroupsUpdateToken - - // changegroup is the remotesource id - cgNames := []string{util.EncodeSha256Hex("remotesourceid-" + remoteSource.ID)} - - // must do all the checks 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 remoteSource existance - remoteSource, err = s.readDB.GetRemoteSourceByName(tx, remoteSourceName) - if err != nil { - return err - } - if remoteSource == nil { - return util.NewErrBadRequest(errors.Errorf("remotesource %q doesn't exist", remoteSourceName)) - } - return nil - }) - if err != nil { - return err - } - - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypeDelete, - DataType: string(types.ConfigTypeRemoteSource), - ID: remoteSource.ID, - }, - } - - // changegroup is all the remote sources - _, err = s.dm.WriteWal(ctx, actions, cgt) - return err -} - -func (s *CommandHandler) CreateOrg(ctx context.Context, org *types.Organization) (*types.Organization, error) { - if org.Name == "" { - return nil, util.NewErrBadRequest(errors.Errorf("organization name required")) - } - if !util.ValidateName(org.Name) { - return nil, util.NewErrBadRequest(errors.Errorf("invalid organization name %q", org.Name)) - } - - var cgt *datamanager.ChangeGroupsUpdateToken - // changegroup is the org name - cgNames := []string{util.EncodeSha256Hex("orgname-" + org.Name)} - - // must do all the checks 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 util.NewErrBadRequest(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") - } - - pg := &types.ProjectGroup{ - ID: uuid.NewV4().String(), - Parent: types.Parent{ - Type: types.ConfigTypeOrg, - ID: org.ID, - }, - } - pgj, err := json.Marshal(pg) - if err != nil { - return nil, errors.Wrapf(err, "failed to marshal project group") - } - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeOrg), - ID: org.ID, - Data: orgj, - }, - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeProjectGroup), - ID: pg.ID, - Data: pgj, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return org, err -} - -func (s *CommandHandler) DeleteOrg(ctx context.Context, orgRef string) error { - var org *types.Organization - var projects []*types.Project - - var cgt *datamanager.ChangeGroupsUpdateToken - - // must do all the checks in a single transaction to avoid concurrent changes - err := s.readDB.Do(func(tx *db.Tx) error { - var err error - // check org existance - org, err = s.readDB.GetOrgByName(tx, orgRef) - if err != nil { - return err - } - if org == nil { - return util.NewErrBadRequest(errors.Errorf("org %q doesn't exist", orgRef)) - } - - // changegroup is the org id - cgNames := []string{util.EncodeSha256Hex("orgid-" + org.ID)} - cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return err - } - - // TODO(sgotti) delete all project groups, projects etc... - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypeDelete, - DataType: string(types.ConfigTypeOrg), - ID: org.ID, - }, - } - // delete all org projects - for _, project := range projects { - actions = append(actions, &datamanager.Action{ - ActionType: datamanager.ActionTypeDelete, - DataType: string(types.ConfigTypeProject), - ID: project.ID, - }) - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return err -} - -func (s *CommandHandler) CreateSecret(ctx context.Context, secret *types.Secret) (*types.Secret, error) { - if secret.Name == "" { - return nil, util.NewErrBadRequest(errors.Errorf("secret name required")) - } - if !util.ValidateName(secret.Name) { - return nil, util.NewErrBadRequest(errors.Errorf("invalid secret name %q", secret.Name)) - } - if secret.Type != types.SecretTypeInternal { - return nil, util.NewErrBadRequest(errors.Errorf("invalid secret type %q", secret.Type)) - } - switch secret.Type { - case types.SecretTypeInternal: - if len(secret.Data) == 0 { - return nil, util.NewErrBadRequest(errors.Errorf("empty secret data")) - } - } - if secret.Parent.Type == "" { - return nil, util.NewErrBadRequest(errors.Errorf("secret parent type required")) - } - if secret.Parent.ID == "" { - return nil, util.NewErrBadRequest(errors.Errorf("secret parentid required")) - } - if secret.Parent.Type != types.ConfigTypeProject && secret.Parent.Type != types.ConfigTypeProjectGroup { - return nil, util.NewErrBadRequest(errors.Errorf("invalid secret parent type %q", secret.Parent.Type)) - } - - var cgt *datamanager.ChangeGroupsUpdateToken - // changegroup is the secret name - cgNames := []string{util.EncodeSha256Hex("secretname-" + secret.Name)} - - // must do all the checks 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 - } - - parentID, err := s.readDB.ResolveConfigID(tx, secret.Parent.Type, secret.Parent.ID) - if err != nil { - return err - } - secret.Parent.ID = parentID - - // check duplicate secret name - s, err := s.readDB.GetSecretByName(tx, secret.Parent.ID, secret.Name) - if err != nil { - return err - } - if s != nil { - return util.NewErrBadRequest(errors.Errorf("secret with name %q for %s with id %q already exists", secret.Name, secret.Parent.Type, secret.Parent.ID)) - } - - return nil - }) - if err != nil { - return nil, err - } - - secret.ID = uuid.NewV4().String() - - secretj, err := json.Marshal(secret) - if err != nil { - return nil, errors.Wrapf(err, "failed to marshal secret") - } - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeSecret), - ID: secret.ID, - Data: secretj, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return secret, err -} - -func (s *CommandHandler) DeleteSecret(ctx context.Context, parentType types.ConfigType, parentRef, secretName string) error { - var secret *types.Secret - - var cgt *datamanager.ChangeGroupsUpdateToken - - // must do all the checks in a single transaction to avoid concurrent changes - err := s.readDB.Do(func(tx *db.Tx) error { - var err error - parentID, err := s.readDB.ResolveConfigID(tx, parentType, parentRef) - if err != nil { - return err - } - - // check secret existance - secret, err = s.readDB.GetSecretByName(tx, parentID, secretName) - if err != nil { - return err - } - if secret == nil { - return util.NewErrBadRequest(errors.Errorf("secret with name %q doesn't exist", secretName)) - } - - // changegroup is the secret id - cgNames := []string{util.EncodeSha256Hex("secretid-" + secret.ID)} - cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return err - } - - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypeDelete, - DataType: string(types.ConfigTypeSecret), - ID: secret.ID, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return err -} - -func (s *CommandHandler) CreateVariable(ctx context.Context, variable *types.Variable) (*types.Variable, error) { - if variable.Name == "" { - return nil, util.NewErrBadRequest(errors.Errorf("variable name required")) - } - if !util.ValidateName(variable.Name) { - return nil, util.NewErrBadRequest(errors.Errorf("invalid variable name %q", variable.Name)) - } - if len(variable.Values) == 0 { - return nil, util.NewErrBadRequest(errors.Errorf("variable values required")) - } - if variable.Parent.Type == "" { - return nil, util.NewErrBadRequest(errors.Errorf("variable parent type required")) - } - if variable.Parent.ID == "" { - return nil, util.NewErrBadRequest(errors.Errorf("variable parent id required")) - } - if variable.Parent.Type != types.ConfigTypeProject && variable.Parent.Type != types.ConfigTypeProjectGroup { - return nil, util.NewErrBadRequest(errors.Errorf("invalid variable parent type %q", variable.Parent.Type)) - } - - var cgt *datamanager.ChangeGroupsUpdateToken - // changegroup is the variable name - cgNames := []string{util.EncodeSha256Hex("variablename-" + variable.Name)} - - // must do all the checks 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 - } - - parentID, err := s.readDB.ResolveConfigID(tx, variable.Parent.Type, variable.Parent.ID) - if err != nil { - return err - } - variable.Parent.ID = parentID - - // check duplicate variable name - s, err := s.readDB.GetVariableByName(tx, variable.Parent.ID, variable.Name) - if err != nil { - return err - } - if s != nil { - return util.NewErrBadRequest(errors.Errorf("variable with name %q for %s with id %q already exists", variable.Name, variable.Parent.Type, variable.Parent.ID)) - } - - return nil - }) - if err != nil { - return nil, err - } - - variable.ID = uuid.NewV4().String() - - variablej, err := json.Marshal(variable) - if err != nil { - return nil, errors.Wrapf(err, "failed to marshal variable") - } - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypePut, - DataType: string(types.ConfigTypeVariable), - ID: variable.ID, - Data: variablej, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return variable, err -} - -func (s *CommandHandler) DeleteVariable(ctx context.Context, parentType types.ConfigType, parentRef, variableName string) error { - var variable *types.Variable - - var cgt *datamanager.ChangeGroupsUpdateToken - - // must do all the checks in a single transaction to avoid concurrent changes - err := s.readDB.Do(func(tx *db.Tx) error { - var err error - parentID, err := s.readDB.ResolveConfigID(tx, parentType, parentRef) - if err != nil { - return err - } - - // check variable existance - variable, err = s.readDB.GetVariableByName(tx, parentID, variableName) - if err != nil { - return err - } - if variable == nil { - return util.NewErrBadRequest(errors.Errorf("variable with name %q doesn't exist", variableName)) - } - - // changegroup is the variable id - cgNames := []string{util.EncodeSha256Hex("variableid-" + variable.ID)} - cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) - if err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - - actions := []*datamanager.Action{ - { - ActionType: datamanager.ActionTypeDelete, - DataType: string(types.ConfigTypeVariable), - ID: variable.ID, - }, - } - - _, err = s.dm.WriteWal(ctx, actions, cgt) - return err -} diff --git a/internal/services/configstore/command/org.go b/internal/services/configstore/command/org.go new file mode 100644 index 0000000..e580798 --- /dev/null +++ b/internal/services/configstore/command/org.go @@ -0,0 +1,150 @@ +// 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 command + +import ( + "context" + "encoding/json" + + "github.com/sorintlab/agola/internal/datamanager" + "github.com/sorintlab/agola/internal/db" + "github.com/sorintlab/agola/internal/services/types" + "github.com/sorintlab/agola/internal/util" + + "github.com/pkg/errors" + uuid "github.com/satori/go.uuid" +) + +func (s *CommandHandler) CreateOrg(ctx context.Context, org *types.Organization) (*types.Organization, error) { + if org.Name == "" { + return nil, util.NewErrBadRequest(errors.Errorf("organization name required")) + } + if !util.ValidateName(org.Name) { + return nil, util.NewErrBadRequest(errors.Errorf("invalid organization name %q", org.Name)) + } + + var cgt *datamanager.ChangeGroupsUpdateToken + // changegroup is the org name + cgNames := []string{util.EncodeSha256Hex("orgname-" + org.Name)} + + // must do all the checks 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 util.NewErrBadRequest(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") + } + + pg := &types.ProjectGroup{ + ID: uuid.NewV4().String(), + Parent: types.Parent{ + Type: types.ConfigTypeOrg, + ID: org.ID, + }, + } + pgj, err := json.Marshal(pg) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal project group") + } + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeOrg), + ID: org.ID, + Data: orgj, + }, + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeProjectGroup), + ID: pg.ID, + Data: pgj, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return org, err +} + +func (s *CommandHandler) DeleteOrg(ctx context.Context, orgRef string) error { + var org *types.Organization + var projects []*types.Project + + var cgt *datamanager.ChangeGroupsUpdateToken + + // must do all the checks in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + var err error + // check org existance + org, err = s.readDB.GetOrgByName(tx, orgRef) + if err != nil { + return err + } + if org == nil { + return util.NewErrBadRequest(errors.Errorf("org %q doesn't exist", orgRef)) + } + + // changegroup is the org id + cgNames := []string{util.EncodeSha256Hex("orgid-" + org.ID)} + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + // TODO(sgotti) delete all project groups, projects etc... + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypeDelete, + DataType: string(types.ConfigTypeOrg), + ID: org.ID, + }, + } + // delete all org projects + for _, project := range projects { + actions = append(actions, &datamanager.Action{ + ActionType: datamanager.ActionTypeDelete, + DataType: string(types.ConfigTypeProject), + ID: project.ID, + }) + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return err +} diff --git a/internal/services/configstore/command/project.go b/internal/services/configstore/command/project.go new file mode 100644 index 0000000..628aabe --- /dev/null +++ b/internal/services/configstore/command/project.go @@ -0,0 +1,196 @@ +// 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 command + +import ( + "context" + "encoding/json" + "path" + + "github.com/sorintlab/agola/internal/datamanager" + "github.com/sorintlab/agola/internal/db" + "github.com/sorintlab/agola/internal/services/types" + "github.com/sorintlab/agola/internal/util" + + "github.com/pkg/errors" + uuid "github.com/satori/go.uuid" +) + +func (s *CommandHandler) CreateProject(ctx context.Context, project *types.Project) (*types.Project, error) { + if project.Name == "" { + return nil, util.NewErrBadRequest(errors.Errorf("project name required")) + } + if !util.ValidateName(project.Name) { + return nil, util.NewErrBadRequest(errors.Errorf("invalid project name %q", project.Name)) + } + if project.Parent.ID == "" { + return nil, util.NewErrBadRequest(errors.Errorf("project parent id required")) + } + if project.Parent.Type != types.ConfigTypeProjectGroup { + return nil, util.NewErrBadRequest(errors.Errorf("invalid project parent type %q", project.Parent.Type)) + } + if !types.IsValidVisibility(project.Visibility) { + return nil, util.NewErrBadRequest(errors.Errorf("invalid project visibility")) + } + if !types.IsValidRemoteRepositoryConfigType(project.RemoteRepositoryConfigType) { + return nil, util.NewErrBadRequest(errors.Errorf("invalid project remote repository config type %q", project.RemoteRepositoryConfigType)) + } + if project.RemoteRepositoryConfigType == types.RemoteRepositoryConfigTypeRemoteSource { + if project.RemoteSourceID == "" { + return nil, util.NewErrBadRequest(errors.Errorf("empty remote source id")) + } + if project.LinkedAccountID == "" { + return nil, util.NewErrBadRequest(errors.Errorf("empty linked account id")) + } + if project.RepositoryID == "" { + return nil, util.NewErrBadRequest(errors.Errorf("empty remote repository id")) + } + if project.RepositoryPath == "" { + return nil, util.NewErrBadRequest(errors.Errorf("empty remote repository path")) + } + } + + var cgt *datamanager.ChangeGroupsUpdateToken + + // must do all the checks in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + var err error + group, err := s.readDB.GetProjectGroup(tx, project.Parent.ID) + if err != nil { + return err + } + if group == nil { + return util.NewErrBadRequest(errors.Errorf("project group with id %q doesn't exist", project.Parent.ID)) + } + project.Parent.ID = group.ID + + groupPath, err := s.readDB.GetProjectGroupPath(tx, group) + if err != nil { + return err + } + pp := path.Join(groupPath, project.Name) + + // changegroup is the project path. Use "projectpath" prefix as it must + // cover both projects and projectgroups + cgNames := []string{util.EncodeSha256Hex("projectpath-" + pp)} + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + // check duplicate project name + p, err := s.readDB.GetProjectByName(tx, project.Parent.ID, project.Name) + if err != nil { + return err + } + if p != nil { + return util.NewErrBadRequest(errors.Errorf("project with name %q, path %q already exists", p.Name, pp)) + } + // check duplicate project group name + pg, err := s.readDB.GetProjectGroupByName(tx, project.Parent.ID, project.Name) + if err != nil { + return err + } + if pg != nil { + return util.NewErrBadRequest(errors.Errorf("project group with name %q, path %q already exists", pg.Name, pp)) + } + + if project.RemoteRepositoryConfigType == types.RemoteRepositoryConfigTypeRemoteSource { + // check that the linked account matches the remote source + user, err := s.readDB.GetUserByLinkedAccount(tx, project.LinkedAccountID) + if err != nil { + return errors.Wrapf(err, "failed to get user with linked account id %q", project.LinkedAccountID) + } + if user == nil { + return util.NewErrBadRequest(errors.Errorf("user for linked account %q doesn't exist", project.LinkedAccountID)) + } + la, ok := user.LinkedAccounts[project.LinkedAccountID] + if !ok { + return util.NewErrBadRequest(errors.Errorf("linked account id %q for user %q doesn't exist", project.LinkedAccountID, user.Name)) + } + if la.RemoteSourceID != project.RemoteSourceID { + return util.NewErrBadRequest(errors.Errorf("linked account id %q remote source %q different than project remote source %q", project.LinkedAccountID, la.RemoteSourceID, project.RemoteSourceID)) + } + } + + return nil + }) + if err != nil { + return nil, err + } + + project.ID = uuid.NewV4().String() + project.Parent.Type = types.ConfigTypeProjectGroup + + pcj, err := json.Marshal(project) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal project") + } + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeProject), + ID: project.ID, + Data: pcj, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return project, err +} + +func (s *CommandHandler) DeleteProject(ctx context.Context, projectRef string) error { + var project *types.Project + + var cgt *datamanager.ChangeGroupsUpdateToken + + // must do all the checks in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + var err error + + // check project existance + project, err = s.readDB.GetProject(tx, projectRef) + if err != nil { + return err + } + if project == nil { + return util.NewErrBadRequest(errors.Errorf("project %q doesn't exist", projectRef)) + } + + // changegroup is the project id. + cgNames := []string{util.EncodeSha256Hex(project.ID)} + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + // TODO(sgotti) delete project secrets/variables + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypeDelete, + DataType: string(types.ConfigTypeProject), + ID: project.ID, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return err +} diff --git a/internal/services/configstore/command/projectgroup.go b/internal/services/configstore/command/projectgroup.go new file mode 100644 index 0000000..d3113bc --- /dev/null +++ b/internal/services/configstore/command/projectgroup.go @@ -0,0 +1,104 @@ +// 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 command + +import ( + "context" + "encoding/json" + "path" + + "github.com/sorintlab/agola/internal/datamanager" + "github.com/sorintlab/agola/internal/db" + "github.com/sorintlab/agola/internal/services/types" + "github.com/sorintlab/agola/internal/util" + + "github.com/pkg/errors" + uuid "github.com/satori/go.uuid" +) + +func (s *CommandHandler) CreateProjectGroup(ctx context.Context, projectGroup *types.ProjectGroup) (*types.ProjectGroup, error) { + if projectGroup.Name == "" { + return nil, util.NewErrBadRequest(errors.Errorf("project group name required")) + } + if !util.ValidateName(projectGroup.Name) { + return nil, util.NewErrBadRequest(errors.Errorf("invalid project group name %q", projectGroup.Name)) + } + if projectGroup.Parent.ID == "" { + return nil, util.NewErrBadRequest(errors.Errorf("project group parent id required")) + } + if !types.IsValidVisibility(projectGroup.Visibility) { + return nil, util.NewErrBadRequest(errors.Errorf("invalid project group visibility")) + } + + var cgt *datamanager.ChangeGroupsUpdateToken + + // must do all the checks in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + parentProjectGroup, err := s.readDB.GetProjectGroup(tx, projectGroup.Parent.ID) + if err != nil { + return err + } + if parentProjectGroup == nil { + return util.NewErrBadRequest(errors.Errorf("project group with id %q doesn't exist", projectGroup.Parent.ID)) + } + projectGroup.Parent.ID = parentProjectGroup.ID + + groupPath, err := s.readDB.GetProjectGroupPath(tx, parentProjectGroup) + if err != nil { + return err + } + pp := path.Join(groupPath, projectGroup.Name) + + // changegroup is the projectgroup path. Use "projectpath" prefix as it must + // cover both projects and projectgroups + cgNames := []string{util.EncodeSha256Hex("projectpath-" + pp)} + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + // check duplicate project group name + pg, err := s.readDB.GetProjectGroupByName(tx, projectGroup.Parent.ID, projectGroup.Name) + if err != nil { + return err + } + if pg != nil { + return util.NewErrBadRequest(errors.Errorf("project group with name %q, path %q already exists", pg.Name, pp)) + } + return nil + }) + if err != nil { + return nil, err + } + + projectGroup.ID = uuid.NewV4().String() + projectGroup.Parent.Type = types.ConfigTypeProjectGroup + + pcj, err := json.Marshal(projectGroup) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal projectGroup") + } + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeProjectGroup), + ID: projectGroup.ID, + Data: pcj, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return projectGroup, err +} diff --git a/internal/services/configstore/command/remotesource.go b/internal/services/configstore/command/remotesource.go new file mode 100644 index 0000000..8e4eda1 --- /dev/null +++ b/internal/services/configstore/command/remotesource.go @@ -0,0 +1,150 @@ +// 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 command + +import ( + "context" + "encoding/json" + + "github.com/sorintlab/agola/internal/datamanager" + "github.com/sorintlab/agola/internal/db" + "github.com/sorintlab/agola/internal/services/types" + "github.com/sorintlab/agola/internal/util" + + "github.com/pkg/errors" + uuid "github.com/satori/go.uuid" +) + +func (s *CommandHandler) CreateRemoteSource(ctx context.Context, remoteSource *types.RemoteSource) (*types.RemoteSource, error) { + if remoteSource.Name == "" { + return nil, util.NewErrBadRequest(errors.Errorf("remotesource name required")) + } + if !util.ValidateName(remoteSource.Name) { + return nil, util.NewErrBadRequest(errors.Errorf("invalid remotesource name %q", remoteSource.Name)) + } + + if remoteSource.Name == "" { + return nil, util.NewErrBadRequest(errors.Errorf("remotesource name required")) + } + if remoteSource.APIURL == "" { + return nil, util.NewErrBadRequest(errors.Errorf("remotesource api url required")) + } + if remoteSource.Type == "" { + return nil, util.NewErrBadRequest(errors.Errorf("remotesource type required")) + } + if remoteSource.AuthType == "" { + return nil, util.NewErrBadRequest(errors.Errorf("remotesource auth type required")) + } + + // validate if the remote source type supports the required auth type + if !types.SourceSupportsAuthType(types.RemoteSourceType(remoteSource.Type), types.RemoteSourceAuthType(remoteSource.AuthType)) { + return nil, util.NewErrBadRequest(errors.Errorf("remotesource type %q doesn't support auth type %q", remoteSource.Type, remoteSource.AuthType)) + } + if remoteSource.AuthType == types.RemoteSourceAuthTypeOauth2 { + if remoteSource.Oauth2ClientID == "" { + return nil, util.NewErrBadRequest(errors.Errorf("remotesource oauth2clientid required for auth type %q", types.RemoteSourceAuthTypeOauth2)) + } + if remoteSource.Oauth2ClientSecret == "" { + return nil, util.NewErrBadRequest(errors.Errorf("remotesource oauth2clientsecret required for auth type %q", types.RemoteSourceAuthTypeOauth2)) + } + } + + var cgt *datamanager.ChangeGroupsUpdateToken + // changegroup is the remotesource name + cgNames := []string{util.EncodeSha256Hex("remotesourcename-" + remoteSource.Name)} + + // must do all the checks 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 remoteSource name + u, err := s.readDB.GetRemoteSourceByName(tx, remoteSource.Name) + if err != nil { + return err + } + if u != nil { + return util.NewErrBadRequest(errors.Errorf("remoteSource %q already exists", u.Name)) + } + return nil + }) + if err != nil { + return nil, err + } + + remoteSource.ID = uuid.NewV4().String() + + rsj, err := json.Marshal(remoteSource) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal remotesource") + } + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeRemoteSource), + ID: remoteSource.ID, + Data: rsj, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return remoteSource, err +} + +func (s *CommandHandler) DeleteRemoteSource(ctx context.Context, remoteSourceName string) error { + var remoteSource *types.RemoteSource + + var cgt *datamanager.ChangeGroupsUpdateToken + + // changegroup is the remotesource id + cgNames := []string{util.EncodeSha256Hex("remotesourceid-" + remoteSource.ID)} + + // must do all the checks 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 remoteSource existance + remoteSource, err = s.readDB.GetRemoteSourceByName(tx, remoteSourceName) + if err != nil { + return err + } + if remoteSource == nil { + return util.NewErrBadRequest(errors.Errorf("remotesource %q doesn't exist", remoteSourceName)) + } + return nil + }) + if err != nil { + return err + } + + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypeDelete, + DataType: string(types.ConfigTypeRemoteSource), + ID: remoteSource.ID, + }, + } + + // changegroup is all the remote sources + _, err = s.dm.WriteWal(ctx, actions, cgt) + return err +} diff --git a/internal/services/configstore/command/secret.go b/internal/services/configstore/command/secret.go new file mode 100644 index 0000000..e3555bd --- /dev/null +++ b/internal/services/configstore/command/secret.go @@ -0,0 +1,153 @@ +// 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 command + +import ( + "context" + "encoding/json" + + "github.com/sorintlab/agola/internal/datamanager" + "github.com/sorintlab/agola/internal/db" + "github.com/sorintlab/agola/internal/services/types" + "github.com/sorintlab/agola/internal/util" + + "github.com/pkg/errors" + uuid "github.com/satori/go.uuid" +) + +func (s *CommandHandler) CreateSecret(ctx context.Context, secret *types.Secret) (*types.Secret, error) { + if secret.Name == "" { + return nil, util.NewErrBadRequest(errors.Errorf("secret name required")) + } + if !util.ValidateName(secret.Name) { + return nil, util.NewErrBadRequest(errors.Errorf("invalid secret name %q", secret.Name)) + } + if secret.Type != types.SecretTypeInternal { + return nil, util.NewErrBadRequest(errors.Errorf("invalid secret type %q", secret.Type)) + } + switch secret.Type { + case types.SecretTypeInternal: + if len(secret.Data) == 0 { + return nil, util.NewErrBadRequest(errors.Errorf("empty secret data")) + } + } + if secret.Parent.Type == "" { + return nil, util.NewErrBadRequest(errors.Errorf("secret parent type required")) + } + if secret.Parent.ID == "" { + return nil, util.NewErrBadRequest(errors.Errorf("secret parentid required")) + } + if secret.Parent.Type != types.ConfigTypeProject && secret.Parent.Type != types.ConfigTypeProjectGroup { + return nil, util.NewErrBadRequest(errors.Errorf("invalid secret parent type %q", secret.Parent.Type)) + } + + var cgt *datamanager.ChangeGroupsUpdateToken + // changegroup is the secret name + cgNames := []string{util.EncodeSha256Hex("secretname-" + secret.Name)} + + // must do all the checks 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 + } + + parentID, err := s.readDB.ResolveConfigID(tx, secret.Parent.Type, secret.Parent.ID) + if err != nil { + return err + } + secret.Parent.ID = parentID + + // check duplicate secret name + s, err := s.readDB.GetSecretByName(tx, secret.Parent.ID, secret.Name) + if err != nil { + return err + } + if s != nil { + return util.NewErrBadRequest(errors.Errorf("secret with name %q for %s with id %q already exists", secret.Name, secret.Parent.Type, secret.Parent.ID)) + } + + return nil + }) + if err != nil { + return nil, err + } + + secret.ID = uuid.NewV4().String() + + secretj, err := json.Marshal(secret) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal secret") + } + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeSecret), + ID: secret.ID, + Data: secretj, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return secret, err +} + +func (s *CommandHandler) DeleteSecret(ctx context.Context, parentType types.ConfigType, parentRef, secretName string) error { + var secret *types.Secret + + var cgt *datamanager.ChangeGroupsUpdateToken + + // must do all the checks in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + var err error + parentID, err := s.readDB.ResolveConfigID(tx, parentType, parentRef) + if err != nil { + return err + } + + // check secret existance + secret, err = s.readDB.GetSecretByName(tx, parentID, secretName) + if err != nil { + return err + } + if secret == nil { + return util.NewErrBadRequest(errors.Errorf("secret with name %q doesn't exist", secretName)) + } + + // changegroup is the secret id + cgNames := []string{util.EncodeSha256Hex("secretid-" + secret.ID)} + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypeDelete, + DataType: string(types.ConfigTypeSecret), + ID: secret.ID, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return err +} diff --git a/internal/services/configstore/command/user.go b/internal/services/configstore/command/user.go new file mode 100644 index 0000000..9ba30ff --- /dev/null +++ b/internal/services/configstore/command/user.go @@ -0,0 +1,626 @@ +// 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 command + +import ( + "context" + "encoding/json" + "time" + + "github.com/sorintlab/agola/internal/datamanager" + "github.com/sorintlab/agola/internal/db" + "github.com/sorintlab/agola/internal/services/types" + "github.com/sorintlab/agola/internal/util" + + "github.com/pkg/errors" + uuid "github.com/satori/go.uuid" +) + +type CreateUserRequest struct { + UserName string + + CreateUserLARequest *CreateUserLARequest +} + +func (s *CommandHandler) CreateUser(ctx context.Context, req *CreateUserRequest) (*types.User, error) { + if req.UserName == "" { + return nil, util.NewErrBadRequest(errors.Errorf("user name required")) + } + if !util.ValidateName(req.UserName) { + return nil, util.NewErrBadRequest(errors.Errorf("invalid user name %q", req.UserName)) + } + + var cgt *datamanager.ChangeGroupsUpdateToken + // changegroup is the username (and in future the email) to ensure no + // concurrent user creation/modification using the same name + cgNames := []string{util.EncodeSha256Hex("username-" + req.UserName)} + var rs *types.RemoteSource + + // must do all the checks 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 user name + u, err := s.readDB.GetUserByName(tx, req.UserName) + if err != nil { + return err + } + if u != nil { + return util.NewErrBadRequest(errors.Errorf("user with name %q already exists", u.Name)) + } + + if req.CreateUserLARequest != nil { + rs, err = s.readDB.GetRemoteSourceByName(tx, req.CreateUserLARequest.RemoteSourceName) + if err != nil { + return err + } + if rs == nil { + return util.NewErrBadRequest(errors.Errorf("remote source %q doesn't exist", req.CreateUserLARequest.RemoteSourceName)) + } + user, err := s.readDB.GetUserByLinkedAccountRemoteUserIDandSource(tx, req.CreateUserLARequest.RemoteUserID, rs.ID) + if err != nil { + return errors.Wrapf(err, "failed to get user for remote user id %q and remote source %q", req.CreateUserLARequest.RemoteUserID, rs.ID) + } + if user != nil { + return util.NewErrBadRequest(errors.Errorf("user for remote user id %q for remote source %q already exists", req.CreateUserLARequest.RemoteUserID, req.CreateUserLARequest.RemoteSourceName)) + } + } + return nil + }) + if err != nil { + return nil, err + } + + user := &types.User{ + ID: uuid.NewV4().String(), + Name: req.UserName, + } + if req.CreateUserLARequest != nil { + if user.LinkedAccounts == nil { + user.LinkedAccounts = make(map[string]*types.LinkedAccount) + } + + la := &types.LinkedAccount{ + ID: uuid.NewV4().String(), + RemoteSourceID: rs.ID, + RemoteUserID: req.CreateUserLARequest.RemoteUserID, + RemoteUserName: req.CreateUserLARequest.RemoteUserName, + UserAccessToken: req.CreateUserLARequest.UserAccessToken, + Oauth2AccessToken: req.CreateUserLARequest.Oauth2AccessToken, + Oauth2RefreshToken: req.CreateUserLARequest.Oauth2RefreshToken, + Oauth2AccessTokenExpiresAt: req.CreateUserLARequest.Oauth2AccessTokenExpiresAt, + } + + user.LinkedAccounts[la.ID] = la + } + + userj, err := json.Marshal(user) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal user") + } + + pg := &types.ProjectGroup{ + ID: uuid.NewV4().String(), + Parent: types.Parent{ + Type: types.ConfigTypeUser, + ID: user.ID, + }, + } + pgj, err := json.Marshal(pg) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal project group") + } + + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeUser), + ID: user.ID, + Data: userj, + }, + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeProjectGroup), + ID: pg.ID, + Data: pgj, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return user, err +} + +func (s *CommandHandler) DeleteUser(ctx context.Context, userRef string) error { + var user *types.User + + var cgt *datamanager.ChangeGroupsUpdateToken + // must do all the checks in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + var err error + + // check user existance + user, err = s.readDB.GetUser(tx, userRef) + if err != nil { + return err + } + if user == nil { + return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", userRef)) + } + + // changegroup is the userid + cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)} + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypeDelete, + DataType: string(types.ConfigTypeUser), + ID: user.ID, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return err +} + +type UpdateUserRequest struct { + UserRef string + + UserName string +} + +func (s *CommandHandler) UpdateUser(ctx context.Context, req *UpdateUserRequest) (*types.User, error) { + var cgt *datamanager.ChangeGroupsUpdateToken + + cgNames := []string{} + var user *types.User + + // must do all the checks in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + var err error + user, err = s.readDB.GetUser(tx, req.UserRef) + if err != nil { + return err + } + if user == nil { + return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", req.UserRef)) + } + + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + if req.UserName != "" { + // check duplicate user name + u, err := s.readDB.GetUserByName(tx, req.UserName) + if err != nil { + return err + } + if u != nil { + return util.NewErrBadRequest(errors.Errorf("user with name %q already exists", u.Name)) + } + // changegroup is the username (and in future the email) to ensure no + // concurrent user creation/modification using the same name + cgNames = append(cgNames, util.EncodeSha256Hex("username-"+req.UserName)) + } + + return nil + }) + if err != nil { + return nil, err + } + + if req.UserName != "" { + user.Name = req.UserName + } + + userj, err := json.Marshal(user) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal user") + } + + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeUser), + ID: user.ID, + Data: userj, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return user, err +} + +type CreateUserLARequest struct { + UserRef string + + RemoteSourceName string + RemoteUserID string + RemoteUserName string + UserAccessToken string + Oauth2AccessToken string + Oauth2RefreshToken string + Oauth2AccessTokenExpiresAt time.Time +} + +func (s *CommandHandler) CreateUserLA(ctx context.Context, req *CreateUserLARequest) (*types.LinkedAccount, error) { + if req.UserRef == "" { + return nil, util.NewErrBadRequest(errors.Errorf("user ref required")) + } + if req.RemoteSourceName == "" { + return nil, util.NewErrBadRequest(errors.Errorf("remote source name required")) + } + + var user *types.User + var rs *types.RemoteSource + + var cgt *datamanager.ChangeGroupsUpdateToken + + // must do all the checks in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + var err error + user, err = s.readDB.GetUser(tx, req.UserRef) + if err != nil { + return err + } + if user == nil { + return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", req.UserRef)) + } + + // changegroup is the userid + cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)} + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + rs, err = s.readDB.GetRemoteSourceByName(tx, req.RemoteSourceName) + if err != nil { + return err + } + if rs == nil { + return util.NewErrBadRequest(errors.Errorf("remote source %q doesn't exist", req.RemoteSourceName)) + } + + user, err := s.readDB.GetUserByLinkedAccountRemoteUserIDandSource(tx, req.RemoteUserID, rs.ID) + if err != nil { + return errors.Wrapf(err, "failed to get user for remote user id %q and remote source %q", req.RemoteUserID, rs.ID) + } + if user != nil { + return util.NewErrBadRequest(errors.Errorf("user for remote user id %q for remote source %q already exists", req.RemoteUserID, req.RemoteSourceName)) + } + return nil + }) + if err != nil { + return nil, err + } + + if user.LinkedAccounts == nil { + user.LinkedAccounts = make(map[string]*types.LinkedAccount) + } + + la := &types.LinkedAccount{ + ID: uuid.NewV4().String(), + RemoteSourceID: rs.ID, + RemoteUserID: req.RemoteUserID, + RemoteUserName: req.RemoteUserName, + UserAccessToken: req.UserAccessToken, + Oauth2AccessToken: req.Oauth2AccessToken, + Oauth2RefreshToken: req.Oauth2RefreshToken, + Oauth2AccessTokenExpiresAt: req.Oauth2AccessTokenExpiresAt, + } + + user.LinkedAccounts[la.ID] = la + + userj, err := json.Marshal(user) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal user") + } + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeUser), + ID: user.ID, + Data: userj, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return la, err +} + +func (s *CommandHandler) DeleteUserLA(ctx context.Context, userRef, laID string) error { + if userRef == "" { + return util.NewErrBadRequest(errors.Errorf("user ref required")) + } + if laID == "" { + return util.NewErrBadRequest(errors.Errorf("user linked account id required")) + } + + var user *types.User + + var cgt *datamanager.ChangeGroupsUpdateToken + + // must do all the checks in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + var err error + user, err = s.readDB.GetUser(tx, userRef) + if err != nil { + return err + } + if user == nil { + return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", userRef)) + } + + // changegroup is the userid + cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)} + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + _, ok := user.LinkedAccounts[laID] + if !ok { + return util.NewErrBadRequest(errors.Errorf("linked account id %q for user %q doesn't exist", laID, userRef)) + } + + delete(user.LinkedAccounts, laID) + + userj, err := json.Marshal(user) + if err != nil { + return errors.Wrapf(err, "failed to marshal user") + } + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeUser), + ID: user.ID, + Data: userj, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return err +} + +type UpdateUserLARequest struct { + UserRef string + + LinkedAccountID string + RemoteUserID string + RemoteUserName string + UserAccessToken string + Oauth2AccessToken string + Oauth2RefreshToken string + Oauth2AccessTokenExpiresAt time.Time +} + +func (s *CommandHandler) UpdateUserLA(ctx context.Context, req *UpdateUserLARequest) (*types.LinkedAccount, error) { + if req.UserRef == "" { + return nil, util.NewErrBadRequest(errors.Errorf("user ref required")) + } + + var user *types.User + var rs *types.RemoteSource + + var cgt *datamanager.ChangeGroupsUpdateToken + + // must do all the checks in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + var err error + user, err = s.readDB.GetUser(tx, req.UserRef) + if err != nil { + return err + } + if user == nil { + return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", req.UserRef)) + } + + // changegroup is the userid + cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)} + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + la, ok := user.LinkedAccounts[req.LinkedAccountID] + if !ok { + return util.NewErrBadRequest(errors.Errorf("linked account id %q for user %q doesn't exist", req.LinkedAccountID, user.Name)) + } + + rs, err = s.readDB.GetRemoteSource(tx, la.RemoteSourceID) + if err != nil { + return err + } + if rs == nil { + return util.NewErrBadRequest(errors.Errorf("remote source with id %q doesn't exist", la.RemoteSourceID)) + } + return nil + }) + if err != nil { + return nil, err + } + + la := user.LinkedAccounts[req.LinkedAccountID] + + la.RemoteUserID = req.RemoteUserID + la.RemoteUserName = req.RemoteUserName + la.UserAccessToken = req.UserAccessToken + la.Oauth2AccessToken = req.Oauth2AccessToken + la.Oauth2RefreshToken = req.Oauth2RefreshToken + la.Oauth2AccessTokenExpiresAt = req.Oauth2AccessTokenExpiresAt + + userj, err := json.Marshal(user) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal user") + } + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeUser), + ID: user.ID, + Data: userj, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return la, err +} + +func (s *CommandHandler) CreateUserToken(ctx context.Context, userRef, tokenName string) (string, error) { + if userRef == "" { + return "", util.NewErrBadRequest(errors.Errorf("user ref required")) + } + if tokenName == "" { + return "", util.NewErrBadRequest(errors.Errorf("token name required")) + } + + var user *types.User + + var cgt *datamanager.ChangeGroupsUpdateToken + + // must do all the checks in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + var err error + user, err = s.readDB.GetUser(tx, userRef) + if err != nil { + return err + } + if user == nil { + return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", userRef)) + } + + // changegroup is the userid + cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)} + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return "", err + } + if user.Tokens != nil { + if _, ok := user.Tokens[tokenName]; ok { + return "", util.NewErrBadRequest(errors.Errorf("token %q for user %q already exists", tokenName, userRef)) + } + } + + if user.Tokens == nil { + user.Tokens = make(map[string]string) + } + + token := util.EncodeSha1Hex(uuid.NewV4().String()) + user.Tokens[tokenName] = token + + userj, err := json.Marshal(user) + if err != nil { + return "", errors.Wrapf(err, "failed to marshal user") + } + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeUser), + ID: user.ID, + Data: userj, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return token, err +} + +func (s *CommandHandler) DeleteUserToken(ctx context.Context, userRef, tokenName string) error { + if userRef == "" { + return util.NewErrBadRequest(errors.Errorf("user ref required")) + } + if tokenName == "" { + return util.NewErrBadRequest(errors.Errorf("token name required")) + } + + var user *types.User + + var cgt *datamanager.ChangeGroupsUpdateToken + + // must do all the checks in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + var err error + user, err = s.readDB.GetUser(tx, userRef) + if err != nil { + return err + } + if user == nil { + return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", userRef)) + } + + // changegroup is the userid + cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)} + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + _, ok := user.Tokens[tokenName] + if !ok { + return util.NewErrBadRequest(errors.Errorf("token %q for user %q doesn't exist", tokenName, userRef)) + } + + delete(user.Tokens, tokenName) + + userj, err := json.Marshal(user) + if err != nil { + return errors.Wrapf(err, "failed to marshal user") + } + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeUser), + ID: user.ID, + Data: userj, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return err +} diff --git a/internal/services/configstore/command/variable.go b/internal/services/configstore/command/variable.go new file mode 100644 index 0000000..1091669 --- /dev/null +++ b/internal/services/configstore/command/variable.go @@ -0,0 +1,146 @@ +// 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 command + +import ( + "context" + "encoding/json" + + "github.com/sorintlab/agola/internal/datamanager" + "github.com/sorintlab/agola/internal/db" + "github.com/sorintlab/agola/internal/services/types" + "github.com/sorintlab/agola/internal/util" + + "github.com/pkg/errors" + uuid "github.com/satori/go.uuid" +) + +func (s *CommandHandler) CreateVariable(ctx context.Context, variable *types.Variable) (*types.Variable, error) { + if variable.Name == "" { + return nil, util.NewErrBadRequest(errors.Errorf("variable name required")) + } + if !util.ValidateName(variable.Name) { + return nil, util.NewErrBadRequest(errors.Errorf("invalid variable name %q", variable.Name)) + } + if len(variable.Values) == 0 { + return nil, util.NewErrBadRequest(errors.Errorf("variable values required")) + } + if variable.Parent.Type == "" { + return nil, util.NewErrBadRequest(errors.Errorf("variable parent type required")) + } + if variable.Parent.ID == "" { + return nil, util.NewErrBadRequest(errors.Errorf("variable parent id required")) + } + if variable.Parent.Type != types.ConfigTypeProject && variable.Parent.Type != types.ConfigTypeProjectGroup { + return nil, util.NewErrBadRequest(errors.Errorf("invalid variable parent type %q", variable.Parent.Type)) + } + + var cgt *datamanager.ChangeGroupsUpdateToken + // changegroup is the variable name + cgNames := []string{util.EncodeSha256Hex("variablename-" + variable.Name)} + + // must do all the checks 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 + } + + parentID, err := s.readDB.ResolveConfigID(tx, variable.Parent.Type, variable.Parent.ID) + if err != nil { + return err + } + variable.Parent.ID = parentID + + // check duplicate variable name + s, err := s.readDB.GetVariableByName(tx, variable.Parent.ID, variable.Name) + if err != nil { + return err + } + if s != nil { + return util.NewErrBadRequest(errors.Errorf("variable with name %q for %s with id %q already exists", variable.Name, variable.Parent.Type, variable.Parent.ID)) + } + + return nil + }) + if err != nil { + return nil, err + } + + variable.ID = uuid.NewV4().String() + + variablej, err := json.Marshal(variable) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal variable") + } + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeVariable), + ID: variable.ID, + Data: variablej, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return variable, err +} + +func (s *CommandHandler) DeleteVariable(ctx context.Context, parentType types.ConfigType, parentRef, variableName string) error { + var variable *types.Variable + + var cgt *datamanager.ChangeGroupsUpdateToken + + // must do all the checks in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + var err error + parentID, err := s.readDB.ResolveConfigID(tx, parentType, parentRef) + if err != nil { + return err + } + + // check variable existance + variable, err = s.readDB.GetVariableByName(tx, parentID, variableName) + if err != nil { + return err + } + if variable == nil { + return util.NewErrBadRequest(errors.Errorf("variable with name %q doesn't exist", variableName)) + } + + // changegroup is the variable id + cgNames := []string{util.EncodeSha256Hex("variableid-" + variable.ID)} + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypeDelete, + DataType: string(types.ConfigTypeVariable), + ID: variable.ID, + }, + } + + _, err = s.dm.WriteWal(ctx, actions, cgt) + return err +}