package main import ( "context" "errors" "fmt" "os" "path/filepath" "strings" "time" "code.gitea.io/sdk/gitea" "git.nevets.tech/Steven/ezconf" "github.com/go-git/go-billy/v5" "github.com/go-git/go-git/v5" gitconf "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/storage/memory" "github.com/google/go-github/v55/github" ) type GitWorkspace struct { Repo *git.Repository Storage *memory.Storage FS billy.Filesystem WorkTree *git.Worktree } type GitSource int const ( Github GitSource = iota Gitlab Gitea Gogs Bitbucket CodeCommit ) var GitSourceName = map[GitSource]string{ Github: "github", Gitlab: "gitlab", Gitea: "gitea", Gogs: "gogs", Bitbucket: "bitbucket", CodeCommit: "code-commit", } func strToGitSource(s string) (GitSource, error) { for k, v := range GitSourceName { if v == s { return k, nil } } return GitSource(0), errors.New("invalid gitsource name") } func createGithubClient() *github.Client { return github.NewClient(nil).WithAuthToken(config.GetAsString("Git.api_token")) } func createGiteaClient() *gitea.Client { client, err := gitea.NewClient(config.GetAsString("Git.server"), gitea.SetToken(config.GetAsString("Git.api_token"))) if err != nil { fmt.Printf("Error connecting to gitea instance: %v\n", err) return nil } return client } func createGithubRepo(domain *Domain, client *github.Client) string { name := domain.name owner := domain.config.GetAsString("Repo.owner") description := domain.description private := true includeAllBranches := false ctx := context.Background() template := &github.TemplateRepoRequest{ Name: name, Owner: &owner, Description: description, Private: &private, IncludeAllBranches: &includeAllBranches, } repo, _, err := client.Repositories.CreateFromTemplate(ctx, config.GetAsString("Git.org_name"), config.GetAsString("Git.template_name"), template) if err != nil { fmt.Println("Error creating repository from template,", err) return "" } return *repo.CloneURL } func createGiteaRepo(domain string, giteaClient *gitea.Client) string { domainConfig, exists := getDomainConfig(domain) if !exists { fmt.Printf("Domain %s config does not exist\n", domain) return "" } //options := gitea.CreateRepoFromTemplateOption{ // Avatar: true, // Description: "Certificates storage for " + domain, // GitContent: true, // GitHooks: true, // Labels: true, // Name: domain + domainConfig.GetAsString("Repo.repo_suffix"), // Owner: config.GetAsString("Git.org_name"), // Private: true, // Topics: true, // Webhooks: true, //} options := gitea.CreateRepoOption{ Name: domain + domainConfig.GetAsString("Repo.repo_suffix"), Description: "Certificate storage for " + domain, Private: true, IssueLabels: "", AutoInit: false, Template: false, Gitignores: "", License: "", Readme: "", DefaultBranch: "master", TrustModel: gitea.TrustModelDefault, } giteaRepo, _, err := giteaClient.CreateOrgRepo(config.GetAsString("Git.org_name"), options) //giteaRepo, _, err := giteaClient.CreateRepoFromTemplate(config.GetAsString("Git.org_name"), config.GetAsString("Git.template_name"), options) if err != nil { fmt.Printf("Error creating repo: %v\n", err) return "" } return giteaRepo.CloneURL } func initRepo(url string, ws *GitWorkspace) error { var err error ws.Repo, err = git.Init(ws.Storage, ws.FS) if err != nil { fmt.Printf("Error initializing local repo: %v\n", err) return err } _, err = ws.Repo.CreateRemote(&gitconf.RemoteConfig{ Name: "origin", URLs: []string{url}, }) if err != nil && !errors.Is(err, git.ErrRemoteExists) { fmt.Printf("Error creating remote origin repo: %v\n", err) return err } ws.WorkTree, err = ws.Repo.Worktree() if err != nil { fmt.Printf("Error getting worktree from local repo: %v\n", err) return err } return nil } func cloneRepo(url string, ws *GitWorkspace) error { creds := &http.BasicAuth{ Username: config.GetAsString("Git.username"), Password: config.GetAsString("Git.api_token"), } var err error ws.Repo, err = git.Clone(ws.Storage, ws.FS, &git.CloneOptions{URL: url, Auth: creds}) if err != nil { fmt.Printf("Error cloning repo: %v\n", err) } ws.WorkTree, err = ws.Repo.Worktree() if err != nil { fmt.Printf("Error getting worktree from cloned repo: %v\n", err) return err } return nil } func addAndPushCerts(domain string, ws *GitWorkspace) error { domainConfig, exists := getDomainConfig(domain) if !exists { fmt.Printf("Domain %s config does not exist\n", domain) return ConfigNotFound } certsDir, err := getDomainCertsDirWConf(domain, domainConfig) if err != nil { if errors.Is(err, ConfigNotFound) { fmt.Printf("Domain %s config not found: %v\n", domain, err) return err } fmt.Printf("Error getting domain %s certs dir: %v\n", domain, err) } certFiles, err := os.ReadDir(certsDir) if err != nil { fmt.Printf("Error reading from directory: %v\n", err) return err } for _, entry := range certFiles { if strings.HasSuffix(entry.Name(), ".crpt") { file, err := ws.FS.Create(entry.Name()) if err != nil { fmt.Printf("Error copying file to memfs: %v\n", err) return err } certFile, err := os.ReadFile(filepath.Join(certsDir, file.Name())) if err != nil { fmt.Printf("Error reading file to memfs: %v\n", err) file.Close() return err } _, err = file.Write(certFile) if err != nil { fmt.Printf("Error writing to memfs: %v\n", err) file.Close() return err } _, err = ws.WorkTree.Add(file.Name()) if err != nil { fmt.Printf("Error adding file %v: %v\n", file.Name(), err) file.Close() return err } err = file.Close() if err != nil { fmt.Printf("Error closing file: %v\n", err) } } } status, err := ws.WorkTree.Status() if err != nil { fmt.Printf("Error getting repo status: %v\n", err) return err } if status.IsClean() { fmt.Printf("Repository is clean, skipping commit...\n") return nil } fmt.Println("Work Tree Status:\n" + status.String()) signature := &object.Signature{ Name: "Cert Manager", Email: config.GetAsString("Certificates.email"), When: time.Now(), } commitHash, err := ws.WorkTree.Commit("Update "+domain+" @ "+time.Now().Format("Mon Jan _2 2006 15:04:05 MST"), &git.CommitOptions{Author: signature, Committer: signature}) if err != nil { fmt.Printf("Error committing certs: %v\n", err) return err } creds := &http.BasicAuth{ Username: config.GetAsString("Git.username"), Password: config.GetAsString("Git.api_token"), } err = ws.Repo.Push(&git.PushOptions{ Auth: creds, Force: true, RemoteName: "origin", RefSpecs: []gitconf.RefSpec{ "refs/heads/master:refs/heads/master", }, }) if err != nil { fmt.Printf("Error pushing to origin: %v\n", err) return err } fmt.Println("Successfully uploaded to " + config.GetAsString("Git.server") + "/" + config.GetAsString("Git.org_name") + "/" + domain + domainConfig.GetAsString("Repo.repo_suffix") + ".git") err = writeCommitHash(commitHash.String(), domainConfig) if err != nil { fmt.Printf("Error writing commit hash: %v\n", err) return err } return nil } func writeCommitHash(hash string, domainConfig *ezconf.Configuration) error { certsDir, err := getDomainCertsDirWOnlyConf(domainConfig) if err != nil { if errors.Is(err, ConfigNotFound) { return err } return err } err = os.WriteFile(filepath.Join(certsDir, "hash"), []byte(hash), 0644) if err != nil { return err } return nil } func getLocalCommitHash(domain string) (string, error) { certsDir, err := getDomainCertsDir(domain) if err != nil { if errors.Is(err, ConfigNotFound) { fmt.Printf("Domain %s config not found: %v\n", domain, err) return "", err } fmt.Printf("Error getting domain %s certs dir: %v\n", domain, err) } data, err := os.ReadFile(filepath.Join(certsDir, "hash")) if err != nil { fmt.Printf("Error reading file for domain %s: %v\n", domain, err) return "", err } return strings.TrimSpace(string(data)), nil } func getRemoteCommitHash(domain string, gitSource GitSource) (string, error) { domainConfig, exists := getDomainConfig(domain) if !exists { fmt.Printf("Domain %s config does not exist\n", domain) return "", ConfigNotFound } switch gitSource { case Gitea: return getRemoteCommitHashGitea(config.GetAsString("Git.org_name"), domain+domainConfig.GetAsString("Repo.repo_suffix"), "master") default: fmt.Printf("Unimplemented git source %v\n", gitSource) return "", errors.New("unimplemented git source") } } func getRemoteCommitHashGitea(org, repo, branchName string) (string, error) { giteaClient := createGiteaClient() branch, _, err := giteaClient.GetRepoBranch(org, repo, branchName) if err != nil { fmt.Printf("Error getting repo branch: %v\n", err) return "", err } //TODO catch repo not found as ErrRepoNotInit return branch.Commit.ID, nil }