// 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 action 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 (h *ActionHandler) 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 := h.readDB.Do(func(tx *db.Tx) error { parentProjectGroup, err := h.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 := h.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 = h.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) if err != nil { return err } // check duplicate project group name pg, err := h.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 = h.dm.WriteWal(ctx, actions, cgt) return projectGroup, err }