agola/internal/services/gateway/command/project.go
Simone Gotti f383a0056d gateway gitsources: use owner id for deploy keys and webhook urls
In this way we could have multiple projects pointing to the same remote
repository and every projects will have its own deploy key and webhook url
2019-02-28 17:19:53 +01:00

191 lines
5.9 KiB
Go

// 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"
"fmt"
"net/url"
"path"
"strings"
"github.com/sorintlab/agola/internal/services/gateway/common"
"github.com/sorintlab/agola/internal/services/types"
"github.com/sorintlab/agola/internal/util"
"github.com/pkg/errors"
)
type CreateProjectRequest struct {
Name string
RemoteSourceName string
RepoURL string
UserID string
OwnerType types.OwnerType
OwnerID string
SkipSSHHostKeyCheck bool
}
func (c *CommandHandler) CreateProject(ctx context.Context, req *CreateProjectRequest) (*types.Project, error) {
if !util.ValidateName(req.Name) {
return nil, errors.Errorf("invalid project name %q", req.Name)
}
u, err := url.Parse(req.RepoURL)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse repo url")
}
repoOwner := strings.TrimPrefix(path.Dir(u.Path), "/")
repoName := path.Base(u.Path)
u.RawQuery = ""
u.Path = ""
host := u.Hostname()
c.log.Infof("repoOwner: %s, repoName: %s", repoOwner, repoName)
cloneURL := fmt.Sprintf("git@%s:%s/%s.git", host, repoOwner, repoName)
c.log.Infof("cloneURL: %s", cloneURL)
c.log.Infof("generating ssh key pairs")
privateKey, _, err := util.GenSSHKeyPair(4096)
if err != nil {
return nil, errors.Wrapf(err, "failed to generate ssh key pair")
}
user, _, err := c.configstoreClient.GetUser(ctx, req.UserID)
if err != nil {
return nil, errors.Wrapf(err, "failed to get user %q", req.UserID)
}
rs, _, err := c.configstoreClient.GetRemoteSourceByName(ctx, req.RemoteSourceName)
if err != nil {
return nil, errors.Wrapf(err, "failed to get remote source %q", req.RemoteSourceName)
}
c.log.Infof("rs: %s", util.Dump(rs))
var la *types.LinkedAccount
for _, v := range user.LinkedAccounts {
if v.RemoteSourceID == rs.ID {
la = v
break
}
}
c.log.Infof("la: %s", util.Dump(la))
if la == nil {
return nil, errors.Errorf("user doesn't have a linked account for remote source %q", rs.Name)
}
p := &types.Project{
Name: req.Name,
OwnerType: types.OwnerTypeUser,
OwnerID: user.ID,
LinkedAccountID: la.ID,
Path: fmt.Sprintf("%s/%s", repoOwner, repoName),
CloneURL: cloneURL,
SkipSSHHostKeyCheck: req.SkipSSHHostKeyCheck,
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 {
return nil, errors.Wrapf(err, "failed to create project")
}
c.log.Infof("project %s created, ID: %s", p.Name, p.ID)
return p, c.SetupProject(ctx, rs, la, &SetupProjectRequest{
Project: p,
RepoOwner: repoOwner,
RepoName: repoName,
})
}
type SetupProjectRequest struct {
Project *types.Project
RepoOwner string
RepoName string
}
func (c *CommandHandler) SetupProject(ctx context.Context, rs *types.RemoteSource, la *types.LinkedAccount, conf *SetupProjectRequest) error {
c.log.Infof("setupproject")
gitsource, err := common.GetGitSource(rs, la)
pubKey, err := util.ExtractPublicKey([]byte(conf.Project.SSHPrivateKey))
if err != nil {
return errors.Wrapf(err, "failed to create gitea client")
}
webhookURL := fmt.Sprintf("%s/webhooks?projectid=%s", c.apiExposedURL, conf.Project.ID)
// generate deploy keys and webhooks containing the agola project id so we
// can have multiple projects referencing the same remote repository and this
// will trigger multiple different runs
deployKeyName := fmt.Sprintf("agola deploy key - %s", conf.Project.ID)
c.log.Infof("creating/updating deploy key: %s", string(pubKey))
if err := gitsource.UpdateDeployKey(conf.RepoOwner, conf.RepoName, deployKeyName, string(pubKey), true); err != nil {
return errors.Wrapf(err, "failed to create deploy key")
}
c.log.Infof("deleting existing webhooks")
if err := gitsource.DeleteRepoWebhook(conf.RepoOwner, conf.RepoName, webhookURL); err != nil {
return errors.Wrapf(err, "failed to delete repository webhook")
}
c.log.Infof("creating webhook to url: %s", webhookURL)
if err := gitsource.CreateRepoWebhook(conf.RepoOwner, conf.RepoName, webhookURL, ""); err != nil {
return errors.Wrapf(err, "failed to create repository webhook")
}
return nil
}
func (c *CommandHandler) ReconfigProject(ctx context.Context, ownerID, projectName string) error {
p, _, err := c.configstoreClient.GetProjectByName(ctx, ownerID, projectName)
if err != nil {
return err
}
user, _, err := c.configstoreClient.GetUserByLinkedAccount(ctx, p.LinkedAccountID)
if err != nil {
return errors.Wrapf(err, "failed to get user with linked account id %q", p.LinkedAccountID)
}
la := user.LinkedAccounts[p.LinkedAccountID]
c.log.Infof("la: %s", util.Dump(la))
if la == nil {
return errors.Errorf("linked account %q in user %q doesn't exist", p.LinkedAccountID, user.UserName)
}
rs, _, err := c.configstoreClient.GetRemoteSource(ctx, la.RemoteSourceID)
if err != nil {
return errors.Wrapf(err, "failed to get remote source %q", la.RemoteSourceID)
}
repoOwner := strings.TrimPrefix(path.Dir(p.Path), "/")
repoName := path.Base(p.Path)
return c.SetupProject(ctx, rs, la, &SetupProjectRequest{
Project: p,
RepoOwner: repoOwner,
RepoName: repoName,
})
}