Skip to content

Commit eff3a82

Browse files
committed
Phase 4 (continued): Archive upload orchestration + GHES version checker
- Archive uploader (pkg/archive/uploader.go): Priority-based routing — GitHub-owned > AWS > Azure > error. Validates credentials and dispatches to appropriate storage client. 10 tests. - GHES version checker (pkg/ghes/version.go): Determines if GHES instance requires blob storage credentials (< 3.8.0) or can use GitHub-owned storage (>= 3.8.0). Local semver parsing. 7 tests.
1 parent 809342f commit eff3a82

4 files changed

Lines changed: 648 additions & 0 deletions

File tree

pkg/archive/uploader.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Package archive provides orchestration for uploading migration archives
2+
// to the appropriate storage backend.
3+
package archive
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"io"
9+
10+
"github.com/github/gh-gei/pkg/logger"
11+
)
12+
13+
// AzureUploader uploads an archive to Azure Blob Storage.
14+
type AzureUploader interface {
15+
Upload(ctx context.Context, fileName string, content io.Reader, size int64) (string, error)
16+
}
17+
18+
// AWSUploader uploads an archive to AWS S3.
19+
type AWSUploader interface {
20+
Upload(ctx context.Context, bucket, key string, data io.Reader) (string, error)
21+
}
22+
23+
// GitHubUploader uploads an archive to GitHub-owned storage.
24+
type GitHubUploader interface {
25+
Upload(ctx context.Context, orgDatabaseID, archiveName string, content io.ReadSeeker, size int64) (string, error)
26+
}
27+
28+
// OrgIDResolver resolves an organization login to its database ID.
29+
type OrgIDResolver interface {
30+
GetOrganizationDatabaseId(ctx context.Context, org string) (string, error)
31+
}
32+
33+
// Uploader coordinates archive uploads across storage backends.
34+
// Priority follows the C# original: GitHub-owned > AWS S3 > Azure Blob.
35+
type Uploader struct {
36+
azure AzureUploader
37+
aws AWSUploader
38+
awsBucket string
39+
github GitHubUploader
40+
orgIDResolver OrgIDResolver
41+
logger *logger.Logger
42+
}
43+
44+
// UploaderOption configures an Uploader.
45+
type UploaderOption func(*Uploader)
46+
47+
// WithAzure configures Azure Blob Storage as an upload backend.
48+
func WithAzure(a AzureUploader) UploaderOption {
49+
return func(u *Uploader) {
50+
u.azure = a
51+
}
52+
}
53+
54+
// WithAWS configures AWS S3 as an upload backend.
55+
func WithAWS(a AWSUploader, bucket string) UploaderOption {
56+
return func(u *Uploader) {
57+
u.aws = a
58+
u.awsBucket = bucket
59+
}
60+
}
61+
62+
// WithGitHub configures GitHub-owned storage as an upload backend.
63+
func WithGitHub(g GitHubUploader, resolver OrgIDResolver) UploaderOption {
64+
return func(u *Uploader) {
65+
u.github = g
66+
u.orgIDResolver = resolver
67+
}
68+
}
69+
70+
// WithLogger sets the logger for upload progress messages.
71+
func WithLogger(l *logger.Logger) UploaderOption {
72+
return func(u *Uploader) {
73+
u.logger = l
74+
}
75+
}
76+
77+
// NewUploader creates an Uploader with the given options.
78+
func NewUploader(opts ...UploaderOption) *Uploader {
79+
u := &Uploader{}
80+
for _, opt := range opts {
81+
opt(u)
82+
}
83+
return u
84+
}
85+
86+
// Upload uploads the archive to the highest-priority configured backend.
87+
// Priority: GitHub-owned storage > AWS S3 > Azure Blob Storage.
88+
// Returns the URL of the uploaded archive.
89+
func (u *Uploader) Upload(ctx context.Context, targetOrg, fileName string, content io.ReadSeeker, size int64) (string, error) {
90+
if u.github != nil {
91+
return u.uploadToGitHub(ctx, targetOrg, fileName, content, size)
92+
}
93+
94+
if u.aws != nil {
95+
return u.uploadToAWS(ctx, fileName, content)
96+
}
97+
98+
if u.azure != nil {
99+
return u.uploadToAzure(ctx, fileName, content, size)
100+
}
101+
102+
return "", fmt.Errorf("no upload destination configured; provide Azure, AWS, or GitHub-owned storage credentials")
103+
}
104+
105+
func (u *Uploader) uploadToGitHub(ctx context.Context, targetOrg, fileName string, content io.ReadSeeker, size int64) (string, error) {
106+
u.logInfo("Uploading archive %s to GitHub-owned storage", fileName)
107+
108+
orgDatabaseID, err := u.orgIDResolver.GetOrganizationDatabaseId(ctx, targetOrg)
109+
if err != nil {
110+
return "", fmt.Errorf("resolving org database ID for %q: %w", targetOrg, err)
111+
}
112+
113+
url, err := u.github.Upload(ctx, orgDatabaseID, fileName, content, size)
114+
if err != nil {
115+
return "", err
116+
}
117+
118+
u.logInfo("Upload complete")
119+
return url, nil
120+
}
121+
122+
func (u *Uploader) uploadToAWS(ctx context.Context, fileName string, content io.Reader) (string, error) {
123+
u.logInfo("Uploading archive %s to AWS", fileName)
124+
125+
url, err := u.aws.Upload(ctx, u.awsBucket, fileName, content)
126+
if err != nil {
127+
return "", err
128+
}
129+
130+
u.logInfo("Upload complete")
131+
return url, nil
132+
}
133+
134+
func (u *Uploader) uploadToAzure(ctx context.Context, fileName string, content io.Reader, size int64) (string, error) {
135+
u.logInfo("Uploading archive %s to Azure Blob Storage", fileName)
136+
137+
url, err := u.azure.Upload(ctx, fileName, content, size)
138+
if err != nil {
139+
return "", err
140+
}
141+
142+
u.logInfo("Upload complete")
143+
return url, nil
144+
}
145+
146+
func (u *Uploader) logInfo(format string, args ...interface{}) {
147+
if u.logger != nil {
148+
u.logger.Info(format, args...)
149+
}
150+
}

0 commit comments

Comments
 (0)