mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-18 06:02:52 +00:00
refactor: replace AWS SDK with a REST client for CodeCommit migration (#39330)
Replace `aws-sdk-go-v2` in the CodeCommit migration with a minimal REST client for the four read-only API calls it uses. Binary shrinks by ~3.1 MB. Mock data was recorded against live from a AWS CodeCommit repo and then anonymized. Also stop storing the repo description as the default branch which fixes below issue. All comments in https://github.com/go-gitea/gitea/issues/34627 are addressed, the merge metadata was fixed earlier in https://github.com/go-gitea/gitea/pull/34645. Fixes: https://github.com/go-gitea/gitea/issues/34627
This commit is contained in:
Generated
-45
File diff suppressed because one or more lines are too long
@@ -111,6 +111,13 @@ gitea-runner exec -W ./.github/workflows/pull-db-tests.yml --event=pull_request
|
||||
gitea-runner exec -W ./.github/workflows/pull-db-tests.yml --event=pull_request --default-actions-url="https://github.com" -i catthehacker/ubuntu:runner-latest -j <job_name>
|
||||
```
|
||||
|
||||
## Recorded API responses
|
||||
|
||||
Repository migration unit and integration tests replay API responses recorded under
|
||||
`_mock_data/`, so they run offline. Setting the environment variables a test reads, usually
|
||||
API credentials, makes it record from the live API instead. To re-record, delete the test's
|
||||
`_mock_data` directory, run the test with those variables set, and update its assertions.
|
||||
|
||||
## End-to-end tests
|
||||
|
||||
End-to-end tests drive a running Gitea instance with [Playwright](https://playwright.dev/):
|
||||
|
||||
@@ -22,8 +22,6 @@ require (
|
||||
github.com/PuerkitoBio/goquery v1.13.0
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0
|
||||
github.com/alecthomas/chroma/v2 v2.27.0
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.20.3
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.42.0
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
github.com/blevesearch/bleve/v2 v2.6.1
|
||||
github.com/bohde/codel v0.2.0
|
||||
@@ -126,10 +124,6 @@ require (
|
||||
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.46.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.2 // indirect
|
||||
github.com/aws/smithy-go v1.28.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.5 // indirect
|
||||
|
||||
@@ -79,18 +79,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aws/aws-sdk-go-v2 v1.46.0 h1:1kt7m/EKcEHt5mlyyxx9cSlMddRPIKbjb6DIQsu4HPk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.46.0/go.mod h1:bttEH6JqnUL8LepvDVfdrds/fZ5bCIxzpe3abyUrhDU=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.20.3 h1:tToOYM/LXev4NpfWlIYGDvBvjHmJ3HXpRU9ppl+pM6k=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.20.3/go.mod h1:wfGneWyncO7p67wqXV2IQhPk14JqIc25woKlaArT3WI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.2 h1:q/PSLGuRWCChWg+dLnb9dWOnrCxJtnboXbBtFoqqRrI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.2/go.mod h1:TD1jvU2LvXkJexct5vBqcd8QlNXh5EmRUeL/Z32p0n4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.2 h1:6fl86IPqKEXoySqiOWdfgbEp9OVbn44zTfEICNEBDhY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.2/go.mod h1:63HDfhFkdzBpI8WGXTSKUHPKS6mqldj4u3LJW7RZtSU=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.42.0 h1:fBLorMzimI2kg6hvhpGwP2C9FOUymYIskbAab1MOFbk=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.42.0/go.mod h1:3VTSZN8rd2CAADjbxv818aep0HS6cuCGdnu4JceGY4A=
|
||||
github.com/aws/smithy-go v1.28.1 h1:R/nXH00c8qcfCzQVELtRw+eLQWtzv+VAIEFJ1/xxXlQ=
|
||||
github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
|
||||
@@ -30,6 +30,8 @@ type MockServerOptions struct {
|
||||
// useful when the client prepends a prefix the real upstream does not use
|
||||
// (e.g. go-github prepends "/api/v3").
|
||||
StripPrefix string
|
||||
// FixtureName overrides the fixture file name, for APIs where method and path don't identify the request.
|
||||
FixtureName func(r *http.Request, body []byte) string
|
||||
}
|
||||
|
||||
// NewMockWebServer returns a test HTTP server that records upstream responses on demand
|
||||
@@ -66,16 +68,22 @@ func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveM
|
||||
}
|
||||
log.Info("mock server: %s %s", r.Method, reqPath)
|
||||
|
||||
reqBody, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
fixturePath := fmt.Sprintf("%s/%s_%s", testDataDir, r.Method, url.QueryEscape(reqPath))
|
||||
if strings.Contains(r.URL.Path, ".git/") {
|
||||
fixturePath = fmt.Sprintf("%s/%s_%s", testDataDir, r.Method, url.QueryEscape(r.URL.Path))
|
||||
}
|
||||
if opt.FixtureName != nil {
|
||||
fixturePath = fmt.Sprintf("%s/%s", testDataDir, opt.FixtureName(r, reqBody))
|
||||
}
|
||||
|
||||
if liveMode {
|
||||
require.NoError(t, os.MkdirAll(testDataDir, 0o755))
|
||||
|
||||
liveURL := liveServerBaseURL + strings.TrimPrefix(reqPath, opt.StripPrefix)
|
||||
req, err := http.NewRequest(r.Method, liveURL, r.Body)
|
||||
req, err := http.NewRequest(r.Method, liveURL, strings.NewReader(string(reqBody)))
|
||||
require.NoError(t, err, "building upstream request to %s", liveURL)
|
||||
for name, values := range r.Header {
|
||||
if strings.EqualFold(name, "accept-encoding") {
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
Content-Length: 577
|
||||
Content-Type: application/x-amz-json-1.1
|
||||
|
||||
{"commentsForPullRequestData":[{"afterCommitId":"43bddef3095d527c6d95d89d0ce992e2e79eeb76","beforeCommitId":"73bb36ac7a26a4d595f5b0bb892aeaa85a078ee7","comments":[{"authorArn":"arn:aws:iam::123456789012:user/gitea-codecommit","callerReactions":[],"clientRequestToken":"00000000-0000-0000-0000-000000000000","commentId":"00000000-0000-0000-0000-000000000000:00000000-0000-0000-0000-000000000000","content":"Test comment","creationDate":1.789574710911E9,"deleted":false,"lastModifiedDate":1.789574710911E9,"reactionCounts":{}}],"pullRequestId":"4","repositoryName":"test_repo"}]}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
Content-Length: 928
|
||||
Content-Type: application/x-amz-json-1.1
|
||||
|
||||
{"pullRequest":{"approvalRules":[],"authorArn":"arn:aws:iam::123456789012:user/gitea-codecommit","clientRequestToken":"00000000-0000-0000-0000-000000000000","creationDate":1.789574708027E9,"description":"Test pull request description","lastActivityDate":1.789574713962E9,"pullRequestId":"4","pullRequestStatus":"CLOSED","pullRequestTargets":[{"destinationCommit":"73bb36ac7a26a4d595f5b0bb892aeaa85a078ee7","destinationReference":"refs/heads/main","mergeBase":"73bb36ac7a26a4d595f5b0bb892aeaa85a078ee7","mergeMetadata":{"isMerged":true,"mergeCommitId":"43bddef3095d527c6d95d89d0ce992e2e79eeb76","mergeOption":"FAST_FORWARD_MERGE","mergedBy":"arn:aws:iam::123456789012:user/gitea-codecommit"},"repositoryName":"test_repo","sourceCommit":"43bddef3095d527c6d95d89d0ce992e2e79eeb76","sourceReference":"refs/heads/feature"}],"revisionId":"0000000000000000000000000000000000000000000000000000000000000000","title":"Test pull request"}}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
Content-Length: 600
|
||||
Content-Type: application/x-amz-json-1.1
|
||||
|
||||
{"repositoryMetadata":{"Arn":"arn:aws:codecommit:us-east-1:123456789012:test_repo","accountId":"123456789012","cloneUrlHttp":"https://git-codecommit.us-east-1.amazonaws.com/v1/repos/test_repo","cloneUrlSsh":"ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/test_repo","creationDate":1.789574698542E9,"defaultBranch":"main","kmsKeyId":"arn:aws:kms:us-east-1:123456789012:key/00000000-0000-0000-0000-000000000000","lastModifiedDate":1.789574713936E9,"repositoryDescription":"Test repository for Gitea migrations","repositoryId":"00000000-0000-0000-0000-000000000000","repositoryName":"test_repo"}}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
Content-Length: 24
|
||||
Content-Type: application/x-amz-json-1.1
|
||||
|
||||
{"pullRequestIds":["4"]}
|
||||
@@ -5,19 +5,22 @@ package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
git_module "gitea.dev/modules/git"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/log"
|
||||
base "gitea.dev/modules/migration"
|
||||
"gitea.dev/modules/structs"
|
||||
|
||||
"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 (
|
||||
@@ -63,12 +66,13 @@ func (c *CodeCommitDownloaderFactory) GitServiceType() structs.GitServiceType {
|
||||
|
||||
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,
|
||||
}),
|
||||
repoName: repoName,
|
||||
baseURL: baseURL,
|
||||
client: getMigrationHTTPClient(),
|
||||
endpoint: "https://codecommit." + region + ".amazonaws.com",
|
||||
region: region,
|
||||
accessKeyID: accessKeyID,
|
||||
secretAccessKey: secretAccessKey,
|
||||
}
|
||||
|
||||
return &downloader
|
||||
@@ -77,7 +81,11 @@ func NewCodeCommitDownloader(_ context.Context, repoName, baseURL, accessKeyID,
|
||||
// CodeCommitDownloader implements a downloader for AWS CodeCommit
|
||||
type CodeCommitDownloader struct {
|
||||
base.NullDownloader
|
||||
codeCommitClient *codecommit.Client
|
||||
client *http.Client
|
||||
endpoint string
|
||||
region string
|
||||
accessKeyID string
|
||||
secretAccessKey string
|
||||
repoName string
|
||||
baseURL string
|
||||
allPullRequestIDs []string
|
||||
@@ -85,42 +93,48 @@ type CodeCommitDownloader struct {
|
||||
|
||||
// GetRepoInfo returns a repository information
|
||||
func (c *CodeCommitDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, error) {
|
||||
output, err := c.codeCommitClient.GetRepository(ctx, &codecommit.GetRepositoryInput{
|
||||
RepositoryName: new(c.repoName),
|
||||
})
|
||||
if err != nil {
|
||||
var output struct {
|
||||
RepositoryMetadata struct {
|
||||
AccountID string `json:"accountId"`
|
||||
CloneURLHTTP string `json:"cloneUrlHttp"`
|
||||
DefaultBranch string `json:"defaultBranch"`
|
||||
RepositoryDescription string `json:"repositoryDescription"`
|
||||
RepositoryName string `json:"repositoryName"`
|
||||
} `json:"repositoryMetadata"`
|
||||
}
|
||||
if err := c.callAPI(ctx, "GetRepository", map[string]string{"repositoryName": c.repoName}, &output); 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
|
||||
return &base.Repository{
|
||||
Name: repoMeta.RepositoryName,
|
||||
Owner: repoMeta.AccountID,
|
||||
IsPrivate: true, // CodeCommit repos are always private
|
||||
CloneURL: repoMeta.CloneURLHTTP,
|
||||
DefaultBranch: repoMeta.DefaultBranch,
|
||||
Description: repoMeta.RepositoryDescription,
|
||||
}, 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
|
||||
)
|
||||
var comments []*base.Comment
|
||||
input := map[string]string{"pullRequestId": strconv.FormatInt(commentable.GetForeignIndex(), 10)}
|
||||
|
||||
for {
|
||||
resp, err := c.codeCommitClient.GetCommentsForPullRequest(ctx, &codecommit.GetCommentsForPullRequestInput{
|
||||
NextToken: nextToken,
|
||||
PullRequestId: new(strconv.FormatInt(commentable.GetForeignIndex(), 10)),
|
||||
})
|
||||
if err != nil {
|
||||
var resp struct {
|
||||
CommentsForPullRequestData []struct {
|
||||
Comments []struct {
|
||||
AuthorArn string `json:"authorArn"`
|
||||
Content string `json:"content"`
|
||||
CreationDate float64 `json:"creationDate"`
|
||||
LastModifiedDate float64 `json:"lastModifiedDate"`
|
||||
} `json:"comments"`
|
||||
} `json:"commentsForPullRequestData"`
|
||||
NextToken string `json:"nextToken"`
|
||||
}
|
||||
if err := c.callAPI(ctx, "GetCommentsForPullRequest", input, &resp); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
@@ -128,19 +142,19 @@ func (c *CodeCommitDownloader) GetComments(ctx context.Context, commentable base
|
||||
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,
|
||||
PosterName: c.getUsernameFromARN(ccComment.AuthorArn),
|
||||
Content: ccComment.Content,
|
||||
Created: time.Unix(int64(ccComment.CreationDate), 0),
|
||||
Updated: time.Unix(int64(ccComment.LastModifiedDate), 0),
|
||||
}
|
||||
comments = append(comments, comment)
|
||||
}
|
||||
}
|
||||
|
||||
nextToken = resp.NextToken
|
||||
if nextToken == nil {
|
||||
if resp.NextToken == "" {
|
||||
break
|
||||
}
|
||||
input["nextToken"] = resp.NextToken
|
||||
}
|
||||
|
||||
return comments, true, nil
|
||||
@@ -159,56 +173,71 @@ func (c *CodeCommitDownloader) GetPullRequests(ctx context.Context, page, perPag
|
||||
|
||||
prs := make([]*base.PullRequest, 0, len(batch))
|
||||
for _, id := range batch {
|
||||
output, err := c.codeCommitClient.GetPullRequest(ctx, &codecommit.GetPullRequestInput{
|
||||
PullRequestId: new(id),
|
||||
})
|
||||
if err != nil {
|
||||
var output struct {
|
||||
PullRequest struct {
|
||||
AuthorArn string `json:"authorArn"`
|
||||
CreationDate float64 `json:"creationDate"`
|
||||
Description string `json:"description"`
|
||||
LastActivityDate float64 `json:"lastActivityDate"`
|
||||
PullRequestID string `json:"pullRequestId"`
|
||||
PullRequestStatus string `json:"pullRequestStatus"`
|
||||
PullRequestTargets []struct {
|
||||
DestinationCommit string `json:"destinationCommit"`
|
||||
DestinationReference string `json:"destinationReference"`
|
||||
MergeMetadata struct {
|
||||
IsMerged bool `json:"isMerged"`
|
||||
MergeCommitID string `json:"mergeCommitId"`
|
||||
} `json:"mergeMetadata"`
|
||||
SourceCommit string `json:"sourceCommit"`
|
||||
SourceReference string `json:"sourceReference"`
|
||||
} `json:"pullRequestTargets"`
|
||||
Title string `json:"title"`
|
||||
} `json:"pullRequest"`
|
||||
}
|
||||
if err := c.callAPI(ctx, "GetPullRequest", map[string]string{"pullRequestId": id}, &output); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
orig := output.PullRequest
|
||||
number, err := strconv.ParseInt(*orig.PullRequestId, 10, 64)
|
||||
number, err := strconv.ParseInt(orig.PullRequestID, 10, 64)
|
||||
if err != nil {
|
||||
log.Error("CodeCommit pull request id is not a number: %s", *orig.PullRequestId)
|
||||
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)
|
||||
log.Error("CodeCommit pull request %s does not contain targets", orig.PullRequestID)
|
||||
continue
|
||||
}
|
||||
target := orig.PullRequestTargets[0]
|
||||
description := ""
|
||||
if orig.Description != nil {
|
||||
description = *orig.Description
|
||||
}
|
||||
lastActivity := time.Unix(int64(orig.LastActivityDate), 0)
|
||||
pr := &base.PullRequest{
|
||||
Number: number,
|
||||
Title: *orig.Title,
|
||||
PosterName: c.getUsernameFromARN(*orig.AuthorArn),
|
||||
Content: description,
|
||||
Title: orig.Title,
|
||||
PosterName: c.getUsernameFromARN(orig.AuthorArn),
|
||||
Content: orig.Description,
|
||||
State: "open",
|
||||
Created: *orig.CreationDate,
|
||||
Updated: *orig.LastActivityDate,
|
||||
Created: time.Unix(int64(orig.CreationDate), 0),
|
||||
Updated: lastActivity,
|
||||
Merged: target.MergeMetadata.IsMerged,
|
||||
Head: base.PullRequestBranch{
|
||||
Ref: strings.TrimPrefix(*target.SourceReference, git_module.BranchPrefix),
|
||||
SHA: *target.SourceCommit,
|
||||
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,
|
||||
Ref: strings.TrimPrefix(target.DestinationReference, git_module.BranchPrefix),
|
||||
SHA: target.DestinationCommit,
|
||||
RepoName: c.repoName,
|
||||
},
|
||||
ForeignIndex: number,
|
||||
}
|
||||
|
||||
if orig.PullRequestStatus == types.PullRequestStatusEnumClosed {
|
||||
if orig.PullRequestStatus == "CLOSED" {
|
||||
pr.State = "closed"
|
||||
pr.Closed = orig.LastActivityDate
|
||||
pr.Closed = &lastActivity
|
||||
}
|
||||
if pr.Merged {
|
||||
pr.MergeCommitSHA = *target.MergeMetadata.MergeCommitId
|
||||
pr.MergedTime = orig.LastActivityDate
|
||||
pr.MergeCommitSHA = target.MergeMetadata.MergeCommitID
|
||||
pr.MergedTime = &lastActivity
|
||||
}
|
||||
|
||||
_ = CheckAndEnsureSafePR(pr, c.baseURL, c)
|
||||
@@ -233,24 +262,22 @@ func (c *CodeCommitDownloader) getAllPullRequestIDs(ctx context.Context) ([]stri
|
||||
return c.allPullRequestIDs, nil
|
||||
}
|
||||
|
||||
var (
|
||||
nextToken *string
|
||||
prIDs []string
|
||||
)
|
||||
var prIDs []string
|
||||
input := map[string]string{"repositoryName": c.repoName}
|
||||
|
||||
for {
|
||||
output, err := c.codeCommitClient.ListPullRequests(ctx, &codecommit.ListPullRequestsInput{
|
||||
RepositoryName: new(c.repoName),
|
||||
NextToken: nextToken,
|
||||
})
|
||||
if err != nil {
|
||||
var output struct {
|
||||
NextToken string `json:"nextToken"`
|
||||
PullRequestIDs []string `json:"pullRequestIds"`
|
||||
}
|
||||
if err := c.callAPI(ctx, "ListPullRequests", input, &output); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prIDs = append(prIDs, output.PullRequestIds...)
|
||||
nextToken = output.NextToken
|
||||
if nextToken == nil {
|
||||
prIDs = append(prIDs, output.PullRequestIDs...)
|
||||
if output.NextToken == "" {
|
||||
break
|
||||
}
|
||||
input["nextToken"] = output.NextToken
|
||||
}
|
||||
|
||||
c.allPullRequestIDs = prIDs
|
||||
@@ -264,3 +291,37 @@ func (c *CodeCommitDownloader) getUsernameFromARN(arn string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *CodeCommitDownloader) callAPI(ctx context.Context, operation string, input map[string]string, output any) error {
|
||||
body, err := json.Marshal(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpoint, strings.NewReader(string(body)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amzDate, target := time.Now().UTC().Format("20060102T150405Z"), "CodeCommit_20150413."+operation
|
||||
scope := amzDate[:8] + "/" + c.region + "/codecommit/aws4_request"
|
||||
canonicalRequest := fmt.Sprintf("POST\n/\n\ncontent-type:application/x-amz-json-1.1\nhost:%s\nx-amz-date:%s\nx-amz-target:%s\n\ncontent-type;host;x-amz-date;x-amz-target\n%x", req.URL.Host, amzDate, target, sha256.Sum256(body))
|
||||
signature := []byte("AWS4" + c.secretAccessKey)
|
||||
for _, data := range []string{amzDate[:8], c.region, "codecommit", "aws4_request", fmt.Sprintf("AWS4-HMAC-SHA256\n%s\n%s\n%x", amzDate, scope, sha256.Sum256([]byte(canonicalRequest)))} { // SigV4 key derivation, the last round signs
|
||||
mac := hmac.New(sha256.New, signature)
|
||||
_, _ = mac.Write([]byte(data))
|
||||
signature = mac.Sum(nil)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-amz-json-1.1")
|
||||
req.Header.Set("X-Amz-Date", amzDate)
|
||||
req.Header.Set("X-Amz-Target", target)
|
||||
req.Header.Set("Authorization", fmt.Sprintf("AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=content-type;host;x-amz-date;x-amz-target, Signature=%x", c.accessKeyID, scope, signature))
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return json.NewDecoder(resp.Body).Decode(output)
|
||||
}
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("CodeCommit %s: %s: %s", operation, resp.Status, respBody)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dev/models/unittest"
|
||||
base "gitea.dev/modules/migration"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCodeCommitDownloadRepo(t *testing.T) {
|
||||
accessKeyID, secretAccessKey := os.Getenv("CODECOMMIT_ACCESS_KEY_ID"), os.Getenv("CODECOMMIT_SECRET_ACCESS_KEY")
|
||||
mockServer := unittest.NewMockWebServer(t, "https://codecommit.us-east-1.amazonaws.com", "_mock_data/TestCodeCommitDownloadRepo", accessKeyID != "", unittest.MockServerOptions{
|
||||
FixtureName: func(r *http.Request, body []byte) string {
|
||||
return r.Header.Get("X-Amz-Target") + "_" + url.QueryEscape(string(body))
|
||||
},
|
||||
})
|
||||
proxyURL, _ := url.Parse(mockServer.URL)
|
||||
|
||||
ctx := t.Context()
|
||||
downloader := NewCodeCommitDownloader(ctx, "test_repo", "https://git-codecommit.us-east-1.amazonaws.com", accessKeyID, secretAccessKey, "us-east-1")
|
||||
downloader.endpoint = "http://codecommit.us-east-1.amazonaws.com"
|
||||
downloader.client = &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}}
|
||||
|
||||
repo, err := downloader.GetRepoInfo(ctx)
|
||||
require.NoError(t, err)
|
||||
assertRepositoryEqual(t, &base.Repository{
|
||||
Name: "test_repo",
|
||||
Owner: "123456789012",
|
||||
IsPrivate: true,
|
||||
Description: "Test repository for Gitea migrations",
|
||||
CloneURL: "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/test_repo",
|
||||
DefaultBranch: "main",
|
||||
}, repo)
|
||||
|
||||
prs, _, err := downloader.GetPullRequests(ctx, 1, 1)
|
||||
require.NoError(t, err)
|
||||
mergedTime := time.Date(2026, time.September, 16, 16, 5, 13, 0, time.UTC)
|
||||
assertPullRequestsEqual(t, []*base.PullRequest{{
|
||||
Number: 4,
|
||||
Title: "Test pull request",
|
||||
PosterName: "gitea-codecommit",
|
||||
Content: "Test pull request description",
|
||||
State: "closed",
|
||||
Created: time.Date(2026, time.September, 16, 16, 5, 8, 0, time.UTC),
|
||||
Updated: mergedTime,
|
||||
Closed: &mergedTime,
|
||||
Merged: true,
|
||||
MergedTime: &mergedTime,
|
||||
MergeCommitSHA: "43bddef3095d527c6d95d89d0ce992e2e79eeb76",
|
||||
Head: base.PullRequestBranch{Ref: "feature", SHA: "43bddef3095d527c6d95d89d0ce992e2e79eeb76", RepoName: "test_repo"},
|
||||
Base: base.PullRequestBranch{Ref: "main", SHA: "73bb36ac7a26a4d595f5b0bb892aeaa85a078ee7", RepoName: "test_repo"},
|
||||
}}, prs)
|
||||
|
||||
comments, _, err := downloader.GetComments(ctx, prs[0])
|
||||
require.NoError(t, err)
|
||||
assertCommentsEqual(t, []*base.Comment{{
|
||||
IssueIndex: 4,
|
||||
PosterName: "gitea-codecommit",
|
||||
Content: "Test comment",
|
||||
Created: time.Date(2026, time.September, 16, 16, 5, 10, 0, time.UTC),
|
||||
Updated: time.Date(2026, time.September, 16, 16, 5, 10, 0, time.UTC),
|
||||
}}, comments)
|
||||
}
|
||||
Reference in New Issue
Block a user