Add gitea provider
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
package gitprovider
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type GiteaProvider struct {
|
||||
protocol string
|
||||
baseUrl string
|
||||
authToken string
|
||||
}
|
||||
|
||||
var _ GitProvider = (*GiteaProvider)(nil)
|
||||
|
||||
func (p *GiteaProvider) FetchAndDeployBranch(owner, repo, branch, destPath string) (*DeployResult, error) {
|
||||
apiUrl := fmt.Sprintf("%s://%s/api/v1/", p.protocol, strings.TrimSuffix(p.baseUrl, "/"))
|
||||
|
||||
result, err := fetchGiteaBranchHead(apiUrl, owner, repo, branch, p.authToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching branch head: %w", err)
|
||||
}
|
||||
|
||||
if err = downloadAndExtractGitea(apiUrl, owner, repo, branch, p.authToken, destPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func fetchGiteaBranchHead(apiUrl, owner, repo, branch, token string) (*DeployResult, error) {
|
||||
url := fmt.Sprintf("%srepos/%s/%s/branches/%s", apiUrl, owner, repo, branch)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
req.Header.Set("Authorization", "token "+token)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching branch: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("Gitea returned %s for %s", resp.Status, url)
|
||||
}
|
||||
|
||||
// GET /repos/{owner}/{repo}/branches/{branch} returns a Branch object
|
||||
// whose commit field is a PayloadCommit.
|
||||
var payload struct {
|
||||
Commit struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
} `json:"commit"`
|
||||
}
|
||||
if err = json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||||
return nil, fmt.Errorf("decoding branch response: %w", err)
|
||||
}
|
||||
|
||||
return &DeployResult{
|
||||
CommitHash: payload.Commit.ID,
|
||||
CommitMessage: strings.SplitN(payload.Commit.Message, "\n", 2)[0],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func downloadAndExtractGitea(apiUrl, owner, repo, branch, token, destDir string) error {
|
||||
// GET /repos/{owner}/{repo}/archive/{sha}.zip
|
||||
archiveURL := fmt.Sprintf("%srepos/%s/%s/archive/%s.zip", apiUrl, owner, repo, branch)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, archiveURL, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
req.Header.Set("Authorization", "token "+token)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching archive: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("Gitea returned %s for %s", resp.Status, archiveURL)
|
||||
}
|
||||
|
||||
storageRoot := filepath.Dir(destDir)
|
||||
|
||||
tmpZip, err := os.CreateTemp(storageRoot, "giteabranch-*.zip")
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating temp zip: %w", err)
|
||||
}
|
||||
defer os.Remove(tmpZip.Name())
|
||||
|
||||
if _, err = io.Copy(tmpZip, resp.Body); err != nil {
|
||||
tmpZip.Close()
|
||||
return fmt.Errorf("writing zip: %w", err)
|
||||
}
|
||||
tmpZip.Close()
|
||||
|
||||
tmpDir, err := os.MkdirTemp(storageRoot, "giteabranch-unpack-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating temp dir: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
if err = unzip(tmpZip.Name(), tmpDir); err != nil {
|
||||
return fmt.Errorf("unzipping: %w", err)
|
||||
}
|
||||
|
||||
// Gitea wraps everything in {repo}-{branch}/ (all lowercase).
|
||||
entries, err := os.ReadDir(tmpDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading temp dir: %w", err)
|
||||
}
|
||||
if len(entries) != 1 || !entries[0].IsDir() {
|
||||
return fmt.Errorf("unexpected archive layout: expected a single root directory")
|
||||
}
|
||||
extractedRoot := filepath.Join(tmpDir, entries[0].Name())
|
||||
|
||||
if err = os.MkdirAll(filepath.Dir(destDir), 0o755); err != nil {
|
||||
return fmt.Errorf("creating destination parent dirs: %w", err)
|
||||
}
|
||||
os.RemoveAll(destDir)
|
||||
|
||||
if err = os.Rename(extractedRoot, destDir); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = copyDir(extractedRoot, destDir); err != nil {
|
||||
return fmt.Errorf("cross-device copy to destination: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user