mirror of
https://github.com/harness/drone.git
synced 2025-05-07 03:50:23 +08:00
227 lines
6.2 KiB
Go
227 lines
6.2 KiB
Go
// Copyright 2022 Harness Inc. All rights reserved.
|
|
// Use of this source code is governed by the Polyform Free Trial License
|
|
// that can be found in the LICENSE.md file for this repository.
|
|
|
|
package exporter
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/harness/gitness/encrypt"
|
|
"github.com/harness/gitness/internal/api/controller/repo"
|
|
"github.com/harness/gitness/internal/sse"
|
|
"github.com/harness/gitness/types/enum"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/harness/gitness/gitrpc"
|
|
"github.com/harness/gitness/internal/services/job"
|
|
"github.com/harness/gitness/internal/store"
|
|
gitnessurl "github.com/harness/gitness/internal/url"
|
|
"github.com/harness/gitness/types"
|
|
)
|
|
|
|
var (
|
|
// ErrNotFound is returned if no export data was found.
|
|
ErrNotFound = errors.New("export not found")
|
|
)
|
|
|
|
type Repository struct {
|
|
urlProvider *gitnessurl.Provider
|
|
git gitrpc.Interface
|
|
repoStore store.RepoStore
|
|
scheduler *job.Scheduler
|
|
encrypter encrypt.Encrypter
|
|
sseStreamer sse.Streamer
|
|
}
|
|
|
|
type Input struct {
|
|
UID string `json:"uid"`
|
|
ID int64 `json:"id"`
|
|
Description string `json:"description"`
|
|
IsPublic bool `json:"is_public"`
|
|
HarnessCodeInfo HarnessCodeInfo `json:"harness_code_info"`
|
|
}
|
|
|
|
type HarnessCodeInfo struct {
|
|
AccountId string `json:"account_id"`
|
|
ProjectIdentifier string `json:"project_identifier"`
|
|
OrgIdentifier string `json:"org_identifier"`
|
|
Token string `json:"token"`
|
|
}
|
|
|
|
var _ job.Handler = (*Repository)(nil)
|
|
|
|
const (
|
|
exportJobMaxRetries = 1
|
|
exportJobMaxDuration = 45 * time.Minute
|
|
exportRepoJobUid = "export_repo_%d"
|
|
exportSpaceJobUid = "export_space_%d"
|
|
)
|
|
|
|
const jobType = "repository_export"
|
|
|
|
func (r *Repository) Register(executor *job.Executor) error {
|
|
return executor.Register(jobType, r)
|
|
}
|
|
|
|
func (r *Repository) RunManyForSpace(
|
|
ctx context.Context,
|
|
spaceId int64,
|
|
repos []*types.Repository,
|
|
harnessCodeInfo *HarnessCodeInfo,
|
|
) error {
|
|
jobGroupId := getJobGroupId(spaceId)
|
|
jobDefinitions := make([]job.Definition, len(repos))
|
|
for i, repository := range repos {
|
|
repoJobData := Input{
|
|
UID: repository.UID,
|
|
ID: repository.ID,
|
|
Description: repository.Description,
|
|
IsPublic: repository.IsPublic,
|
|
HarnessCodeInfo: *harnessCodeInfo,
|
|
}
|
|
|
|
data, err := json.Marshal(repoJobData)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal job input json: %w", err)
|
|
}
|
|
strData := strings.TrimSpace(string(data))
|
|
encryptedData, err := r.encrypter.Encrypt(strData)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encrypt job input: %w", err)
|
|
}
|
|
|
|
jobUID := fmt.Sprintf(exportRepoJobUid, repository.ID)
|
|
|
|
jobDefinitions[i] = job.Definition{
|
|
UID: jobUID,
|
|
Type: jobType,
|
|
MaxRetries: exportJobMaxRetries,
|
|
Timeout: exportJobMaxDuration,
|
|
Data: base64.StdEncoding.EncodeToString(encryptedData),
|
|
}
|
|
}
|
|
|
|
return r.scheduler.RunJobs(ctx, jobGroupId, jobDefinitions)
|
|
}
|
|
|
|
func getJobGroupId(spaceId int64) string {
|
|
return fmt.Sprintf(exportSpaceJobUid, spaceId)
|
|
}
|
|
|
|
// Handle is repository export background job handler.
|
|
func (r *Repository) Handle(ctx context.Context, data string, _ job.ProgressReporter) (string, error) {
|
|
input, err := r.getJobInput(data)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
harnessCodeInfo := input.HarnessCodeInfo
|
|
client, err := newHarnessCodeClient(r.urlProvider.GetHarnessCodeInternalUrl(), harnessCodeInfo.AccountId, harnessCodeInfo.OrgIdentifier, harnessCodeInfo.ProjectIdentifier, harnessCodeInfo.Token)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
repository, err := r.repoStore.Find(ctx, input.ID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
remoteRepo, err := client.CreateRepo(ctx, repo.CreateInput{
|
|
UID: repository.UID,
|
|
DefaultBranch: repository.DefaultBranch,
|
|
Description: repository.Description,
|
|
IsPublic: repository.IsPublic,
|
|
Readme: false,
|
|
License: "",
|
|
GitIgnore: "",
|
|
})
|
|
if err != nil {
|
|
r.publishSSE(ctx, repository)
|
|
return "", err
|
|
}
|
|
|
|
urlWithToken, err := modifyUrl(remoteRepo.GitURL, harnessCodeInfo.Token)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = r.git.PushRemote(ctx, &gitrpc.PushRemoteParams{
|
|
ReadParams: gitrpc.ReadParams{RepoUID: repository.GitUID},
|
|
RemoteUrl: urlWithToken,
|
|
})
|
|
if err != nil && !strings.Contains(err.Error(), "empty") {
|
|
errDelete := client.DeleteRepo(ctx, remoteRepo.UID)
|
|
if errDelete != nil {
|
|
log.Ctx(ctx).Err(errDelete).Msgf("failed to delete repo '%s' on harness", remoteRepo.UID)
|
|
}
|
|
r.publishSSE(ctx, repository)
|
|
return "", err
|
|
}
|
|
|
|
log.Ctx(ctx).Info().Msgf("completed exporting repository '%s' to harness", repository.UID)
|
|
|
|
r.publishSSE(ctx, repository)
|
|
|
|
return "", nil
|
|
}
|
|
|
|
func (r *Repository) publishSSE(ctx context.Context, repository *types.Repository) {
|
|
err := r.sseStreamer.Publish(ctx, repository.ParentID, enum.SSETypeRepositoryExportCompleted, repository)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn().Err(err).Msg("failed to publish export completion SSE")
|
|
}
|
|
}
|
|
|
|
func (r *Repository) getJobInput(data string) (Input, error) {
|
|
encrypted, err := base64.StdEncoding.DecodeString(data)
|
|
if err != nil {
|
|
return Input{}, fmt.Errorf("failed to base64 decode job input: %w", err)
|
|
}
|
|
|
|
decrypted, err := r.encrypter.Decrypt(encrypted)
|
|
if err != nil {
|
|
return Input{}, fmt.Errorf("failed to decrypt job input: %w", err)
|
|
}
|
|
|
|
var input Input
|
|
|
|
err = json.NewDecoder(strings.NewReader(decrypted)).Decode(&input)
|
|
if err != nil {
|
|
return Input{}, fmt.Errorf("failed to unmarshal job input json: %w", err)
|
|
}
|
|
|
|
return input, nil
|
|
}
|
|
|
|
func (r *Repository) GetProgressForSpace(ctx context.Context, spaceID int64) ([]types.JobProgress, error) {
|
|
spaceId := getJobGroupId(spaceID)
|
|
progress, err := r.scheduler.GetJobProgressForGroup(ctx, spaceId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get job progress for group: %w", err)
|
|
}
|
|
|
|
if len(progress) == 0 {
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
return progress, nil
|
|
}
|
|
|
|
func modifyUrl(u string, token string) (string, error) {
|
|
parsedUrl, err := url.Parse(u)
|
|
if err != nil {
|
|
fmt.Println("Error parsing URL:", err)
|
|
return "", err
|
|
}
|
|
|
|
// Set the username and password in the URL
|
|
parsedUrl.User = url.UserPassword("token", token)
|
|
return parsedUrl.String(), nil
|
|
}
|