diff --git a/internal/commands/process_repos.go b/internal/commands/process_repos.go index f2bcb83..5ea781c 100644 --- a/internal/commands/process_repos.go +++ b/internal/commands/process_repos.go @@ -78,6 +78,13 @@ func (pr *ProcessRepos) Process(ctx context.Context, repositoryUrls []string) er return } + err = pr.git.CreateBranch(ctx, repo) + if err != nil { + pr.logger.Error("could not create branch", zap.Error(err)) + errChan <- err + return + } + err = pr.action.Run( ctx, area, @@ -116,13 +123,19 @@ func (pr *ProcessRepos) Process(ctx context.Context, repositoryUrls []string) er return nil }, false) - if err != nil { pr.logger.Error("could not run action", zap.Error(err)) errChan <- err return } + err = pr.git.Push(ctx, repo) + if err != nil { + pr.logger.Error("could not push to repo", zap.Error(err)) + errChan <- err + return + } + pr.logger.Debug("processing done", zap.String("path", area.Path), zap.String("repoUrl", repoUrl)) }(ctx, repoUrl) } diff --git a/internal/serverdeps/server_deps.go b/internal/serverdeps/server_deps.go index 845d482..bfa6d39 100644 --- a/internal/serverdeps/server_deps.go +++ b/internal/serverdeps/server_deps.go @@ -38,8 +38,9 @@ func NewServerDeps(logger *zap.Logger) *ServerDeps { } openPGPConfig := &signer.OpenPgpConfig{ - PrivateKeyFilePath: "./examples/private.pgp", + PrivateKeyFilePath: "./example/testkey.private.pgp", PrivateKeyPassword: "somepassword", + PrivateKeyIdentity: "kraken@kasperhermansen.com", } deps.openPGP = signer.NewOpenPGP(logger.With(zap.Namespace("openpgp")), openPGPConfig) @@ -51,7 +52,7 @@ func (deps *ServerDeps) GetStorageService() *storage.Service { } func (deps *ServerDeps) GetGitProvider() *providers.Git { - return providers.NewGit(deps.logger.With(zap.Namespace("gitProvider")), deps.gitCfg) + return providers.NewGit(deps.logger.With(zap.Namespace("gitProvider")), deps.gitCfg, deps.openPGP) } func (deps *ServerDeps) GetAction() *actions.Action { diff --git a/internal/services/providers/git.go b/internal/services/providers/git.go index f15a64c..f4dee7f 100644 --- a/internal/services/providers/git.go +++ b/internal/services/providers/git.go @@ -2,11 +2,14 @@ package providers import ( "context" + "fmt" "time" + "git.front.kjuulh.io/kjuulh/kraken/internal/services/signer" "git.front.kjuulh.io/kjuulh/kraken/internal/services/storage" - "github.com/ProtonMail/go-crypto/openpgp" "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport/http" @@ -20,6 +23,7 @@ import ( type Git struct { logger *zap.Logger gitConfig *GitConfig + openPGP *signer.OpenPGP } type GitRepo struct { @@ -45,8 +49,8 @@ type GitConfig struct { SshPrivateKeyPassword string } -func NewGit(logger *zap.Logger, gitConfig *GitConfig) *Git { - return &Git{logger: logger, gitConfig: gitConfig} +func NewGit(logger *zap.Logger, gitConfig *GitConfig, openPGP *signer.OpenPGP) *Git { + return &Git{logger: logger, gitConfig: gitConfig, openPGP: openPGP} } func (g *Git) Clone(ctx context.Context, storageArea *storage.Area, repoUrl string) (*GitRepo, error) { @@ -70,13 +74,10 @@ func (g *Git) Clone(ctx context.Context, storageArea *storage.Area, repoUrl stri NoCheckout: false, Depth: 1, RecurseSubmodules: 1, - Progress: &zapio.Writer{ - Log: g.logger.With(zap.String("process", "go-git")), - Level: zap.DebugLevel, - }, - Tags: 0, - InsecureSkipTLS: false, - CABundle: []byte{}, + Progress: g.getProgressWriter(), + Tags: 0, + InsecureSkipTLS: false, + CABundle: []byte{}, } repo, err := git.PlainCloneContext(ctx, storageArea.Path, false, &cloneOptions) @@ -89,6 +90,13 @@ func (g *Git) Clone(ctx context.Context, storageArea *storage.Area, repoUrl stri return &GitRepo{repo: repo}, nil } +func (g *Git) getProgressWriter() *zapio.Writer { + return &zapio.Writer{ + Log: g.logger.With(zap.String("process", "go-git")), + Level: zap.DebugLevel, + } +} + func (g *Git) Add(ctx context.Context, storageArea *storage.Area, gitRepo *GitRepo) (*git.Worktree, error) { worktree, err := gitRepo.repo.Worktree() if err != nil { @@ -107,11 +115,71 @@ func (g *Git) Add(ctx context.Context, storageArea *storage.Area, gitRepo *GitRe return nil, err } - g.logger.Info("git status", zap.String("status", status.String())) + g.logger.Debug("git status", zap.String("status", status.String())) return worktree, nil } +func (g *Git) CreateBranch(ctx context.Context, gitRepo *GitRepo) error { + worktree, err := gitRepo.repo.Worktree() + if err != nil { + return err + } + + refSpec := plumbing.NewBranchReferenceName("kraken-apply") + err = gitRepo.repo.CreateBranch(&config.Branch{ + Name: "kraken-apply", + Remote: "origin", + Merge: refSpec, + Rebase: "", + }) + if err != nil { + return fmt.Errorf("could not create branch: %w", err) + } + + err = worktree.Checkout(&git.CheckoutOptions{ + Branch: plumbing.ReferenceName(refSpec.String()), + Create: true, + Force: false, + Keep: false, + }) + if err != nil { + return fmt.Errorf("could not checkout branch: %w", err) + } + + remoteRef := plumbing.NewRemoteReferenceName("origin", "kraken-apply") + ref := plumbing.NewSymbolicReference(refSpec, remoteRef) + err = gitRepo.repo.Storer.SetReference(ref) + if err != nil { + return fmt.Errorf("could not set reference: %w", err) + } + + auth, err := g.GetAuth() + if err != nil { + return err + } + + err = worktree.PullContext(ctx, &git.PullOptions{ + RemoteName: "origin", + ReferenceName: "refs/heads/main", + SingleBranch: true, + Depth: 1, + Auth: auth, + RecurseSubmodules: 1, + Progress: g.getProgressWriter(), + Force: true, + InsecureSkipTLS: false, + CABundle: []byte{}, + }) + if err != nil { + return fmt.Errorf("could not pull from origin: %w", err) + } + + g.logger.Debug("done creating branches") + + return nil +} + func (g *Git) Commit(ctx context.Context, gitRepo *GitRepo) error { worktree, err := gitRepo.repo.Worktree() if err != nil { @@ -122,12 +190,41 @@ func (g *Git) Commit(ctx context.Context, gitRepo *GitRepo) error { All: true, Author: &object.Signature{Name: "kraken", Email: "kraken@kasperhermansen.com", When: time.Now()}, Committer: &object.Signature{Name: "kraken", Email: "kraken@kasperhermansen.com", When: time.Now()}, - SignKey: &openpgp.Entity{}, + SignKey: g.openPGP.SigningKey, }) if err != nil { return err } + g.logger.Debug("done commiting objects") + + return nil +} + +func (g *Git) Push(ctx context.Context, gitRepo *GitRepo) error { + auth, err := g.GetAuth() + if err != nil { + return err + } + + err = gitRepo.repo.PushContext(ctx, &git.PushOptions{ + RemoteName: "origin", + RefSpecs: []config.RefSpec{}, + Auth: auth, + Progress: g.getProgressWriter(), + Prune: false, + Force: false, + InsecureSkipTLS: false, + CABundle: []byte{}, + RequireRemoteRefs: []config.RefSpec{}, + }) + + if err != nil { + return err + } + + g.logger.Debug("done pushing branch") + return nil } diff --git a/internal/services/signer/openpgp.go b/internal/services/signer/openpgp.go index a548c55..0d53fc2 100644 --- a/internal/services/signer/openpgp.go +++ b/internal/services/signer/openpgp.go @@ -2,22 +2,25 @@ package signer import ( "context" + "errors" "os" + "strings" "git.front.kjuulh.io/kjuulh/curre" - "github.com/ProtonMail/gopenpgp/v2/crypto" + "github.com/ProtonMail/go-crypto/openpgp" "go.uber.org/zap" ) type OpenPGP struct { - logger *zap.Logger - PrivateKeyRing *crypto.KeyRing - config *OpenPgpConfig + logger *zap.Logger + SigningKey *openpgp.Entity + config *OpenPgpConfig } type OpenPgpConfig struct { PrivateKeyFilePath string PrivateKeyPassword string + PrivateKeyIdentity string } func NewOpenPGP(logger *zap.Logger, config *OpenPgpConfig) *OpenPGP { @@ -29,26 +32,14 @@ func NewOpenPGP(logger *zap.Logger, config *OpenPgpConfig) *OpenPGP { func NewOpenPGPApp(openPGP *OpenPGP) curre.Component { return curre.NewFunctionalComponent(&curre.FunctionalComponent{ - InitFunc: func(fc *curre.FunctionalComponent, ctx context.Context) error { - - content, err := os.ReadFile(openPGP.config.PrivateKeyFilePath) - if err != nil { - return err - } - privateKeyObj, err := crypto.NewKeyFromArmored(string(content)) - if err != nil { - return err - } - unlockedPrivateKeyRing, err := privateKeyObj.Unlock([]byte(openPGP.config.PrivateKeyPassword)) - if err != nil { - return err - } - privateKeyRing, err := crypto.NewKeyRing(unlockedPrivateKeyRing) + InitFunc: func(_ *curre.FunctionalComponent, ctx context.Context) error { + keyring, err := buildKeyring(ctx, openPGP) if err != nil { + openPGP.logger.Panic("could not build keyring", zap.Error(err)) return err } - openPGP.PrivateKeyRing = privateKeyRing + openPGP.SigningKey = keyring return nil }, @@ -59,5 +50,32 @@ func NewOpenPGPApp(openPGP *OpenPGP) curre.Component { return nil }, }) +} + +func buildKeyring(_ context.Context, openPGP *OpenPGP) (*openpgp.Entity, error) { + content, err := os.ReadFile(openPGP.config.PrivateKeyFilePath) + if err != nil { + return nil, err + } + reader := strings.NewReader(string(content)) + + es, err := openpgp.ReadArmoredKeyRing(reader) + if err != nil { + return nil, err + } + + for _, key := range es { + for k := range key.Identities { + if strings.Contains(k, openPGP.config.PrivateKeyIdentity) { + err = key.PrivateKey.Decrypt([]byte(openPGP.config.PrivateKeyPassword)) + if err != nil { + return nil, err + } + return key, nil + } + } + } + + return nil, errors.New("could not find key matching identity") }