mirror of
https://github.com/harness/drone.git
synced 2025-05-06 17:19:13 +08:00
248 lines
6.8 KiB
Go
248 lines
6.8 KiB
Go
// Copyright 2023 Harness, Inc.
|
|
//
|
|
// 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 importer
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/harness/gitness/git"
|
|
"github.com/harness/gitness/git/sha"
|
|
"github.com/harness/gitness/store/database/dbtx"
|
|
"github.com/harness/gitness/types"
|
|
"github.com/harness/gitness/types/enum"
|
|
|
|
"github.com/drone/go-convert/convert/bitbucket"
|
|
"github.com/drone/go-convert/convert/circle"
|
|
"github.com/drone/go-convert/convert/drone"
|
|
"github.com/drone/go-convert/convert/github"
|
|
"github.com/drone/go-convert/convert/gitlab"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
type pipelineFile struct {
|
|
Name string
|
|
OriginalPath string
|
|
ConvertedPath string
|
|
Content []byte
|
|
}
|
|
|
|
func (r *Repository) processPipelines(ctx context.Context,
|
|
principal *types.Principal,
|
|
repo *types.Repository,
|
|
commitMessage string,
|
|
) error {
|
|
writeParams, err := r.createRPCWriteParams(ctx, principal, repo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pipelineFiles := r.convertPipelines(ctx, repo)
|
|
if len(pipelineFiles) == 0 {
|
|
return nil
|
|
}
|
|
|
|
actions := make([]git.CommitFileAction, len(pipelineFiles))
|
|
for i, file := range pipelineFiles {
|
|
actions[i] = git.CommitFileAction{
|
|
Action: git.CreateAction,
|
|
Path: file.ConvertedPath,
|
|
Payload: file.Content,
|
|
SHA: sha.None,
|
|
}
|
|
}
|
|
|
|
now := time.Now()
|
|
identity := &git.Identity{
|
|
Name: principal.DisplayName,
|
|
Email: principal.Email,
|
|
}
|
|
|
|
_, err = r.git.CommitFiles(ctx, &git.CommitFilesParams{
|
|
WriteParams: writeParams,
|
|
Title: commitMessage,
|
|
Message: "",
|
|
Branch: repo.DefaultBranch,
|
|
NewBranch: repo.DefaultBranch,
|
|
Actions: actions,
|
|
Committer: identity,
|
|
CommitterDate: &now,
|
|
Author: identity,
|
|
AuthorDate: &now,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to commit converted pipeline files: %w", err)
|
|
}
|
|
|
|
nowMilli := now.UnixMilli()
|
|
|
|
err = r.tx.WithTx(ctx, func(ctx context.Context) error {
|
|
for _, p := range pipelineFiles {
|
|
pipeline := &types.Pipeline{
|
|
Description: "",
|
|
RepoID: repo.ID,
|
|
Identifier: p.Name,
|
|
CreatedBy: principal.ID,
|
|
Seq: 0,
|
|
DefaultBranch: repo.DefaultBranch,
|
|
ConfigPath: p.ConvertedPath,
|
|
Created: nowMilli,
|
|
Updated: nowMilli,
|
|
Version: 0,
|
|
}
|
|
|
|
err = r.pipelineStore.Create(ctx, pipeline)
|
|
if err != nil {
|
|
return fmt.Errorf("pipeline creation failed: %w", err)
|
|
}
|
|
|
|
// Try to create a default trigger on pipeline creation.
|
|
// Default trigger operations are set on pull request created, reopened or updated.
|
|
// We log an error on failure but don't fail the op.
|
|
trigger := &types.Trigger{
|
|
Description: "auto-created trigger on pipeline conversion",
|
|
Created: nowMilli,
|
|
Updated: nowMilli,
|
|
PipelineID: pipeline.ID,
|
|
RepoID: pipeline.RepoID,
|
|
CreatedBy: principal.ID,
|
|
Identifier: "default",
|
|
Actions: []enum.TriggerAction{enum.TriggerActionPullReqCreated,
|
|
enum.TriggerActionPullReqReopened, enum.TriggerActionPullReqBranchUpdated},
|
|
Disabled: false,
|
|
Version: 0,
|
|
}
|
|
err = r.triggerStore.Create(ctx, trigger)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create auto trigger on pipeline creation: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}, dbtx.TxDefault)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert pipelines and triggers: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// convertPipelines converts pipelines found in the repository.
|
|
// Note: For GitHub actions, there can be multiple.
|
|
func (r *Repository) convertPipelines(ctx context.Context,
|
|
repo *types.Repository,
|
|
) []pipelineFile {
|
|
const maxSize = 65536
|
|
|
|
match := func(dirPath, regExpDef string) []pipelineFile {
|
|
files, err := r.matchFiles(ctx, repo, repo.DefaultBranch, dirPath, regExpDef, maxSize)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn().Err(err).Msgf("failed to find pipeline file(s) '%s' in '%s'",
|
|
regExpDef, dirPath)
|
|
return nil
|
|
}
|
|
return files
|
|
}
|
|
|
|
if files := match("", ".drone.yml"); len(files) > 0 {
|
|
converted := convertPipelineFiles(ctx, files, func() pipelineConverter { return drone.New() })
|
|
if len(converted) > 0 {
|
|
return converted
|
|
}
|
|
}
|
|
|
|
if files := match("", "bitbucket-pipelines.yml"); len(files) > 0 {
|
|
converted := convertPipelineFiles(ctx, files, func() pipelineConverter { return bitbucket.New() })
|
|
if len(converted) > 0 {
|
|
return converted
|
|
}
|
|
}
|
|
|
|
if files := match("", ".gitlab-ci.yml"); len(files) > 0 {
|
|
converted := convertPipelineFiles(ctx, files, func() pipelineConverter { return gitlab.New() })
|
|
if len(converted) > 0 {
|
|
return converted
|
|
}
|
|
}
|
|
|
|
if files := match(".circleci", "config.yml"); len(files) > 0 {
|
|
converted := convertPipelineFiles(ctx, files, func() pipelineConverter { return circle.New() })
|
|
if len(converted) > 0 {
|
|
return converted
|
|
}
|
|
}
|
|
|
|
filesYML := match(".github/workflows", "*.yml")
|
|
filesYAML := match(".github/workflows", "*.yaml")
|
|
//nolint:gocritic // intended usage
|
|
files := append(filesYML, filesYAML...)
|
|
converted := convertPipelineFiles(ctx, files, func() pipelineConverter { return github.New() })
|
|
if len(converted) > 0 {
|
|
return converted
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type pipelineConverter interface {
|
|
ConvertBytes([]byte) ([]byte, error)
|
|
}
|
|
|
|
func convertPipelineFiles(ctx context.Context,
|
|
files []pipelineFile,
|
|
gen func() pipelineConverter,
|
|
) []pipelineFile {
|
|
const (
|
|
harnessPipelineName = "pipeline"
|
|
harnessPipelineNameOnly = "default-" + harnessPipelineName
|
|
harnessPipelineDir = ".harness"
|
|
harnessPipelineFileOnly = harnessPipelineDir + "/pipeline.yaml"
|
|
)
|
|
|
|
result := make([]pipelineFile, 0, len(files))
|
|
for _, file := range files {
|
|
data, err := gen().ConvertBytes(file.Content)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn().Err(err).Msgf("failed to convert pipeline file %s", file.OriginalPath)
|
|
continue
|
|
}
|
|
|
|
var pipelineName string
|
|
var pipelinePath string
|
|
|
|
if len(files) == 1 {
|
|
pipelineName = harnessPipelineNameOnly
|
|
pipelinePath = harnessPipelineFileOnly
|
|
} else {
|
|
base := path.Base(file.OriginalPath)
|
|
base = strings.TrimSuffix(base, path.Ext(base))
|
|
pipelineName = harnessPipelineName + "-" + base
|
|
pipelinePath = harnessPipelineDir + "/" + base + ".yaml"
|
|
}
|
|
|
|
result = append(result, pipelineFile{
|
|
Name: pipelineName,
|
|
OriginalPath: file.OriginalPath,
|
|
ConvertedPath: pipelinePath,
|
|
Content: data,
|
|
})
|
|
}
|
|
|
|
return result
|
|
}
|