| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- // Copyright 2024 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package migrations
-
- import (
- "context"
- "errors"
- "net/url"
- "strconv"
- "strings"
-
- git_module "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/log"
- base "code.gitea.io/gitea/modules/migration"
- "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
-
- "github.com/aws/aws-sdk-go-v2/credentials"
- "github.com/aws/aws-sdk-go-v2/service/codecommit"
- "github.com/aws/aws-sdk-go-v2/service/codecommit/types"
- )
-
- var (
- _ base.Downloader = &CodeCommitDownloader{}
- _ base.DownloaderFactory = &CodeCommitDownloaderFactory{}
- )
-
- func init() {
- RegisterDownloaderFactory(&CodeCommitDownloaderFactory{})
- }
-
- // CodeCommitDownloaderFactory defines a codecommit downloader factory
- type CodeCommitDownloaderFactory struct{}
-
- // New returns a Downloader related to this factory according MigrateOptions
- func (c *CodeCommitDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
- u, err := url.Parse(opts.CloneAddr)
- if err != nil {
- return nil, err
- }
-
- hostElems := strings.Split(u.Host, ".")
- if len(hostElems) != 4 {
- return nil, errors.New("cannot get the region from clone URL")
- }
- region := hostElems[1]
-
- pathElems := strings.Split(u.Path, "/")
- if len(pathElems) == 0 {
- return nil, errors.New("cannot get the repo name from clone URL")
- }
- repoName := pathElems[len(pathElems)-1]
-
- baseURL := u.Scheme + "://" + u.Host
-
- return NewCodeCommitDownloader(ctx, repoName, baseURL, opts.AWSAccessKeyID, opts.AWSSecretAccessKey, region), nil
- }
-
- // GitServiceType returns the type of git service
- func (c *CodeCommitDownloaderFactory) GitServiceType() structs.GitServiceType {
- return structs.CodeCommitService
- }
-
- func NewCodeCommitDownloader(_ context.Context, repoName, baseURL, accessKeyID, secretAccessKey, region string) *CodeCommitDownloader {
- downloader := CodeCommitDownloader{
- repoName: repoName,
- baseURL: baseURL,
- codeCommitClient: codecommit.New(codecommit.Options{
- Credentials: credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, ""),
- Region: region,
- }),
- }
-
- return &downloader
- }
-
- // CodeCommitDownloader implements a downloader for AWS CodeCommit
- type CodeCommitDownloader struct {
- base.NullDownloader
- codeCommitClient *codecommit.Client
- repoName string
- baseURL string
- allPullRequestIDs []string
- }
-
- // GetRepoInfo returns a repository information
- func (c *CodeCommitDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, error) {
- output, err := c.codeCommitClient.GetRepository(ctx, &codecommit.GetRepositoryInput{
- RepositoryName: util.ToPointer(c.repoName),
- })
- if err != nil {
- return nil, err
- }
- repoMeta := output.RepositoryMetadata
-
- repo := &base.Repository{
- Name: *repoMeta.RepositoryName,
- Owner: *repoMeta.AccountId,
- IsPrivate: true, // CodeCommit repos are always private
- CloneURL: *repoMeta.CloneUrlHttp,
- }
- if repoMeta.DefaultBranch != nil {
- repo.DefaultBranch = *repoMeta.DefaultBranch
- }
- if repoMeta.RepositoryDescription != nil {
- repo.DefaultBranch = *repoMeta.RepositoryDescription
- }
- return repo, nil
- }
-
- // GetComments returns comments of an issue or PR
- func (c *CodeCommitDownloader) GetComments(ctx context.Context, commentable base.Commentable) ([]*base.Comment, bool, error) {
- var (
- nextToken *string
- comments []*base.Comment
- )
-
- for {
- resp, err := c.codeCommitClient.GetCommentsForPullRequest(ctx, &codecommit.GetCommentsForPullRequestInput{
- NextToken: nextToken,
- PullRequestId: util.ToPointer(strconv.FormatInt(commentable.GetForeignIndex(), 10)),
- })
- if err != nil {
- return nil, false, err
- }
-
- for _, prComment := range resp.CommentsForPullRequestData {
- for _, ccComment := range prComment.Comments {
- comment := &base.Comment{
- IssueIndex: commentable.GetForeignIndex(),
- PosterName: c.getUsernameFromARN(*ccComment.AuthorArn),
- Content: *ccComment.Content,
- Created: *ccComment.CreationDate,
- Updated: *ccComment.LastModifiedDate,
- }
- comments = append(comments, comment)
- }
- }
-
- nextToken = resp.NextToken
- if nextToken == nil {
- break
- }
- }
-
- return comments, true, nil
- }
-
- // GetPullRequests returns pull requests according page and perPage
- func (c *CodeCommitDownloader) GetPullRequests(ctx context.Context, page, perPage int) ([]*base.PullRequest, bool, error) {
- allPullRequestIDs, err := c.getAllPullRequestIDs(ctx)
- if err != nil {
- return nil, false, err
- }
-
- startIndex := (page - 1) * perPage
- endIndex := min(page*perPage, len(allPullRequestIDs))
- batch := allPullRequestIDs[startIndex:endIndex]
-
- prs := make([]*base.PullRequest, 0, len(batch))
- for _, id := range batch {
- output, err := c.codeCommitClient.GetPullRequest(ctx, &codecommit.GetPullRequestInput{
- PullRequestId: util.ToPointer(id),
- })
- if err != nil {
- return nil, false, err
- }
- orig := output.PullRequest
- number, err := strconv.ParseInt(*orig.PullRequestId, 10, 64)
- if err != nil {
- log.Error("CodeCommit pull request id is not a number: %s", *orig.PullRequestId)
- continue
- }
- if len(orig.PullRequestTargets) == 0 {
- log.Error("CodeCommit pull request does not contain targets", *orig.PullRequestId)
- continue
- }
- target := orig.PullRequestTargets[0]
- description := ""
- if orig.Description != nil {
- description = *orig.Description
- }
- pr := &base.PullRequest{
- Number: number,
- Title: *orig.Title,
- PosterName: c.getUsernameFromARN(*orig.AuthorArn),
- Content: description,
- State: "open",
- Created: *orig.CreationDate,
- Updated: *orig.LastActivityDate,
- Merged: target.MergeMetadata.IsMerged,
- Head: base.PullRequestBranch{
- Ref: strings.TrimPrefix(*target.SourceReference, git_module.BranchPrefix),
- SHA: *target.SourceCommit,
- RepoName: c.repoName,
- },
- Base: base.PullRequestBranch{
- Ref: strings.TrimPrefix(*target.DestinationReference, git_module.BranchPrefix),
- SHA: *target.DestinationCommit,
- RepoName: c.repoName,
- },
- ForeignIndex: number,
- }
-
- if orig.PullRequestStatus == types.PullRequestStatusEnumClosed {
- pr.State = "closed"
- pr.Closed = orig.LastActivityDate
- }
- if pr.Merged {
- pr.MergeCommitSHA = *target.MergeMetadata.MergeCommitId
- pr.MergedTime = orig.LastActivityDate
- }
-
- _ = CheckAndEnsureSafePR(pr, c.baseURL, c)
- prs = append(prs, pr)
- }
-
- return prs, len(prs) < perPage, nil
- }
-
- // FormatCloneURL add authentication into remote URLs
- func (c *CodeCommitDownloader) FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error) {
- u, err := url.Parse(remoteAddr)
- if err != nil {
- return "", err
- }
- u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword)
- return u.String(), nil
- }
-
- func (c *CodeCommitDownloader) getAllPullRequestIDs(ctx context.Context) ([]string, error) {
- if len(c.allPullRequestIDs) > 0 {
- return c.allPullRequestIDs, nil
- }
-
- var (
- nextToken *string
- prIDs []string
- )
-
- for {
- output, err := c.codeCommitClient.ListPullRequests(ctx, &codecommit.ListPullRequestsInput{
- RepositoryName: util.ToPointer(c.repoName),
- NextToken: nextToken,
- })
- if err != nil {
- return nil, err
- }
- prIDs = append(prIDs, output.PullRequestIds...)
- nextToken = output.NextToken
- if nextToken == nil {
- break
- }
- }
-
- c.allPullRequestIDs = prIDs
- return c.allPullRequestIDs, nil
- }
-
- func (c *CodeCommitDownloader) getUsernameFromARN(arn string) string {
- parts := strings.Split(arn, "/")
- if len(parts) > 0 {
- return parts[len(parts)-1]
- }
- return ""
- }
|