1
0
mirror of synced 2026-02-07 03:05:56 +00:00

Move bootstrap package from internal to pkg

Signed-off-by: Philip Laine <philip.laine@gmail.com>
This commit is contained in:
Philip Laine
2022-08-18 21:50:43 +02:00
parent e7847b75db
commit 0343575146
15 changed files with 17 additions and 17 deletions

View File

@@ -0,0 +1,296 @@
/*
Copyright 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gogit
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
gogit "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/fluxcd/flux2/pkg/bootstrap/git"
)
type GoGit struct {
path string
auth transport.AuthMethod
repository *gogit.Repository
}
type CommitOptions struct {
GpgKeyPath string
GpgKeyPassphrase string
KeyID string
}
func New(path string, auth transport.AuthMethod) *GoGit {
return &GoGit{
path: path,
auth: auth,
}
}
func (g *GoGit) Init(url, branch string) (bool, error) {
if g.repository != nil {
return false, nil
}
r, err := gogit.PlainInit(g.path, false)
if err != nil {
return false, err
}
if _, err = r.CreateRemote(&config.RemoteConfig{
Name: gogit.DefaultRemoteName,
URLs: []string{url},
}); err != nil {
return false, err
}
branchRef := plumbing.NewBranchReferenceName(branch)
if err = r.CreateBranch(&config.Branch{
Name: branch,
Remote: gogit.DefaultRemoteName,
Merge: branchRef,
}); err != nil {
return false, err
}
// PlainInit assumes the initial branch to always be master, we can
// overwrite this by setting the reference of the Storer to a new
// symbolic reference (as there are no commits yet) that points
// the HEAD to our new branch.
if err = r.Storer.SetReference(plumbing.NewSymbolicReference(plumbing.HEAD, branchRef)); err != nil {
return false, err
}
g.repository = r
return true, nil
}
func (g *GoGit) Clone(ctx context.Context, url, branch string, caBundle []byte) (bool, error) {
branchRef := plumbing.NewBranchReferenceName(branch)
r, err := gogit.PlainCloneContext(ctx, g.path, false, &gogit.CloneOptions{
URL: url,
Auth: g.auth,
RemoteName: gogit.DefaultRemoteName,
ReferenceName: branchRef,
SingleBranch: true,
NoCheckout: false,
Progress: nil,
Tags: gogit.NoTags,
CABundle: caBundle,
})
if err != nil {
if err == transport.ErrEmptyRemoteRepository || isRemoteBranchNotFoundErr(err, branchRef.String()) {
return g.Init(url, branch)
}
return false, err
}
g.repository = r
return true, nil
}
func (g *GoGit) Write(path string, reader io.Reader) error {
if g.repository == nil {
return git.ErrNoGitRepository
}
wt, err := g.repository.Worktree()
if err != nil {
return err
}
f, err := wt.Filesystem.Create(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, reader)
return err
}
func (g *GoGit) Commit(message git.Commit, opts ...git.Option) (string, error) {
if g.repository == nil {
return "", git.ErrNoGitRepository
}
wt, err := g.repository.Worktree()
if err != nil {
return "", err
}
status, err := wt.Status()
if err != nil {
return "", err
}
// apply the options
options := &git.CommitOptions{}
for _, opt := range opts {
opt.ApplyToCommit(options)
}
// go-git has [a bug](https://github.com/go-git/go-git/issues/253)
// whereby it thinks broken symlinks to absolute paths are
// modified. There's no circumstance in which we want to commit a
// change to a broken symlink: so, detect and skip those.
var changed bool
for file, _ := range status {
abspath := filepath.Join(g.path, file)
info, err := os.Lstat(abspath)
if err != nil {
return "", fmt.Errorf("checking if %s is a symlink: %w", file, err)
}
if info.Mode()&os.ModeSymlink > 0 {
// symlinks are OK; broken symlinks are probably a result
// of the bug mentioned above, but not of interest in any
// case.
if _, err := os.Stat(abspath); os.IsNotExist(err) {
continue
}
}
_, _ = wt.Add(file)
changed = true
}
if !changed {
head, err := g.repository.Head()
if err != nil {
return "", err
}
return head.Hash().String(), git.ErrNoStagedFiles
}
commitOpts := &gogit.CommitOptions{
Author: &object.Signature{
Name: message.Name,
Email: message.Email,
When: time.Now(),
},
}
if options.GPGSigningInfo != nil {
entity, err := getOpenPgpEntity(*options.GPGSigningInfo)
if err != nil {
return "", err
}
commitOpts.SignKey = entity
}
commit, err := wt.Commit(message.Message, commitOpts)
if err != nil {
return "", err
}
return commit.String(), nil
}
func (g *GoGit) Push(ctx context.Context, caBundle []byte) error {
if g.repository == nil {
return git.ErrNoGitRepository
}
return g.repository.PushContext(ctx, &gogit.PushOptions{
RemoteName: gogit.DefaultRemoteName,
Auth: g.auth,
Progress: nil,
CABundle: caBundle,
})
}
func (g *GoGit) Status() (bool, error) {
if g.repository == nil {
return false, git.ErrNoGitRepository
}
wt, err := g.repository.Worktree()
if err != nil {
return false, err
}
status, err := wt.Status()
if err != nil {
return false, err
}
return status.IsClean(), nil
}
func (g *GoGit) Head() (string, error) {
if g.repository == nil {
return "", git.ErrNoGitRepository
}
head, err := g.repository.Head()
if err != nil {
return "", err
}
return head.Hash().String(), nil
}
func (g *GoGit) Path() string {
return g.path
}
func isRemoteBranchNotFoundErr(err error, ref string) bool {
return strings.Contains(err.Error(), fmt.Sprintf("couldn't find remote ref %q", ref))
}
func getOpenPgpEntity(info git.GPGSigningInfo) (*openpgp.Entity, error) {
r, err := os.Open(info.KeyRingPath)
if err != nil {
return nil, fmt.Errorf("unable to open GPG key ring: %w", err)
}
entityList, err := openpgp.ReadKeyRing(r)
if err != nil {
return nil, err
}
if len(entityList) == 0 {
return nil, fmt.Errorf("empty GPG key ring")
}
var entity *openpgp.Entity
if info.KeyID != "" {
for _, ent := range entityList {
if ent.PrimaryKey.KeyIdString() == info.KeyID {
entity = ent
}
}
if entity == nil {
return nil, fmt.Errorf("no GPG private key matching key id '%s' found", info.KeyID)
}
} else {
entity = entityList[0]
}
err = entity.PrivateKey.Decrypt([]byte(info.Passphrase))
if err != nil {
return nil, fmt.Errorf("unable to decrypt GPG private key: %w", err)
}
return entity, nil
}

View File

@@ -0,0 +1,67 @@
//go:build unit
// +build unit
package gogit
import (
"testing"
"github.com/fluxcd/flux2/pkg/bootstrap/git"
)
func TestGetOpenPgpEntity(t *testing.T) {
tests := []struct {
name string
keyPath string
passphrase string
id string
expectErr bool
}{
{
name: "no default key id given",
keyPath: "testdata/private.key",
passphrase: "flux",
id: "",
expectErr: false,
},
{
name: "key id given",
keyPath: "testdata/private.key",
passphrase: "flux",
id: "0619327DBD777415",
expectErr: false,
},
{
name: "wrong key id",
keyPath: "testdata/private.key",
passphrase: "flux",
id: "0619327DBD777416",
expectErr: true,
},
{
name: "wrong password",
keyPath: "testdata/private.key",
passphrase: "fluxe",
id: "0619327DBD777415",
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gpgInfo := git.GPGSigningInfo{
KeyRingPath: tt.keyPath,
Passphrase: tt.passphrase,
KeyID: tt.id,
}
_, err := getOpenPgpEntity(gpgInfo)
if err != nil && !tt.expectErr {
t.Errorf("unexpected error: %s", err)
}
if err == nil && tt.expectErr {
t.Errorf("expected error when %s", tt.name)
}
})
}
}

Binary file not shown.