128 lines
3.7 KiB
Go
128 lines
3.7 KiB
Go
package common
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/go-git/go-billy/v5"
|
|
"github.com/go-git/go-billy/v5/memfs"
|
|
"github.com/go-git/go-git/v5"
|
|
gitconf "github.com/go-git/go-git/v5/config"
|
|
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
|
"github.com/go-git/go-git/v5/storage/memory"
|
|
)
|
|
|
|
// GitWorkspace is an in-memory git working tree for a single domain's
|
|
// certificate repository. It is the shared primitive that both client and
|
|
// server modes operate on: client mode clones and reads, server mode
|
|
// init/clones and pushes. The struct carries no mode-specific state.
|
|
type GitWorkspace struct {
|
|
Domain string
|
|
URL string
|
|
Storage *memory.Storage
|
|
FS billy.Filesystem
|
|
Repo *git.Repository
|
|
WorkTree *git.Worktree
|
|
}
|
|
|
|
// GitSource identifies a supported git repository host.
|
|
type GitSource int
|
|
|
|
const (
|
|
Github GitSource = iota
|
|
Gitlab
|
|
Gitea
|
|
Gogs
|
|
Bitbucket
|
|
CodeCommit
|
|
)
|
|
|
|
// GitSourceName maps GitSource to the string used in the app config's
|
|
// git.host field.
|
|
var GitSourceName = map[GitSource]string{
|
|
Github: "github",
|
|
Gitlab: "gitlab",
|
|
Gitea: "gitea",
|
|
Gogs: "gogs",
|
|
Bitbucket: "bitbucket",
|
|
CodeCommit: "code-commit",
|
|
}
|
|
|
|
// StrToGitSource parses a config string (e.g. "gitea") into a GitSource.
|
|
func StrToGitSource(s string) (GitSource, error) {
|
|
for k, v := range GitSourceName {
|
|
if v == s {
|
|
return k, nil
|
|
}
|
|
}
|
|
return 0, fmt.Errorf("invalid git source %q", s)
|
|
}
|
|
|
|
// ErrRepoNotFound is returned by RepoProvider implementations when a domain's
|
|
// repository does not exist on the remote. Callers use it to distinguish
|
|
// "repo hasn't been created yet" from transport or auth failures.
|
|
var ErrRepoNotFound = errors.New("repository not found")
|
|
|
|
// NewGitWorkspace returns a workspace with an in-memory filesystem and storage
|
|
// wired up. Call InitRepo (for a brand-new remote) or CloneRepo (for an
|
|
// existing one) to populate Repo and WorkTree.
|
|
func NewGitWorkspace(domain, url string) *GitWorkspace {
|
|
return &GitWorkspace{
|
|
Domain: domain,
|
|
URL: url,
|
|
Storage: memory.NewStorage(),
|
|
FS: memfs.New(),
|
|
}
|
|
}
|
|
|
|
// RepoURL builds the canonical clone URL for a domain's certificate repo. It
|
|
// is the single authoritative place for the "<server>/<org>/<domain><suffix>.git"
|
|
// pattern so callers do not assemble URLs by hand.
|
|
func RepoURL(config *AppConfig, domainConfig *DomainConfig, domain string) string {
|
|
return config.Git.Server + "/" + config.Git.OrgName + "/" + domain + domainConfig.Repo.RepoSuffix + ".git"
|
|
}
|
|
|
|
// InitRepo initializes an empty local repository in ws and registers origin
|
|
// pointed at ws.URL. Use this on the first push for a new domain; use
|
|
// CloneRepo on subsequent runs.
|
|
func InitRepo(ws *GitWorkspace) error {
|
|
repo, err := git.Init(ws.Storage, ws.FS)
|
|
if err != nil {
|
|
return fmt.Errorf("git init: %w", err)
|
|
}
|
|
if _, err := repo.CreateRemote(&gitconf.RemoteConfig{
|
|
Name: "origin",
|
|
URLs: []string{ws.URL},
|
|
}); err != nil && !errors.Is(err, git.ErrRemoteExists) {
|
|
return fmt.Errorf("add remote: %w", err)
|
|
}
|
|
wt, err := repo.Worktree()
|
|
if err != nil {
|
|
return fmt.Errorf("get worktree: %w", err)
|
|
}
|
|
ws.Repo = repo
|
|
ws.WorkTree = wt
|
|
return nil
|
|
}
|
|
|
|
// CloneRepo clones ws.URL into ws using the git credentials from config. It
|
|
// performs no ownership or mode-specific checks: server mode must follow up
|
|
// with server.VerifyOwnership before pushing.
|
|
func CloneRepo(ws *GitWorkspace, config *AppConfig) error {
|
|
auth := &http.BasicAuth{
|
|
Username: config.Git.Username,
|
|
Password: config.Git.APIToken,
|
|
}
|
|
repo, err := git.Clone(ws.Storage, ws.FS, &git.CloneOptions{URL: ws.URL, Auth: auth})
|
|
if err != nil {
|
|
return fmt.Errorf("git clone %s: %w", ws.URL, err)
|
|
}
|
|
wt, err := repo.Worktree()
|
|
if err != nil {
|
|
return fmt.Errorf("get worktree: %w", err)
|
|
}
|
|
ws.Repo = repo
|
|
ws.WorkTree = wt
|
|
return nil
|
|
}
|