store checkpoint policy in a git ref · Entire
Store Checkpoint Policy in a Git Ref
24fc4b2·
pfleidi·3w ago·2 files·+210 added/-0 removed
Read and write checkpoint policy commits at refs/entire/policies/checkpoint using the existing checkpoint commit signing path.
Sessions
6d613e0a7f30 View transcript
Changes
2
cmd/entire/cli/checkpointpolicy
Astore.go+116
Astore_test.go+94
package checkpointpolicy
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/entireio/cli/cmd/entire/cli/checkpoint"
"github.com/entireio/cli/cmd/entire/cli/jsonutil"
"github.com/go-git/go-git/v6"
"github.com/go-git/go-git/v6/plumbing"
"github.com/go-git/go-git/v6/plumbing/filemode"
"github.com/go-git/go-git/v6/plumbing/object"
)
const PolicyFileName = "policy.json"
const RefName = plumbing.ReferenceName("refs/entire/policies/checkpoint")
type Source string
const (
SourceDefaults Source = "defaults"
SourceLocal Source = "local"
SourceRemote Source = "remote"
SourceLocalDiverged Source = "local-diverged"
)
type State struct {
Policy Policy
Source Source
Hash plumbing.Hash
RemoteHash plumbing.Hash
Warning string
}
func ReadLocal(ctx context.Context, repo *git.Repository) (State, error) {
ref, err := repo.Reference(RefName, true)
if err != nil {
if errors.Is(err, plumbing.ErrReferenceNotFound) {
return State{Policy: DefaultPolicy(), Source: SourceDefaults}, nil
}
return State{}, fmt.Errorf("read checkpoint policy ref: %w", err)
}
return readFromHash(ctx, repo, ref.Hash(), SourceLocal)
}
func ReadFromRef(ctx context.Context, repo *git.Repository, refName plumbing.ReferenceName, source Source) (State, error) {
ref, err := repo.Reference(refName, true)
if err != nil {
return State{}, fmt.Errorf("read checkpoint policy ref %s: %w", refName, err)
}
return readFromHash(ctx, repo, ref.Hash(), source)
}
func WriteLocal(ctx context.Context, repo *git.Repository, parent plumbing.Hash, policy Policy) (plumbing.Hash, error) {
policy = Normalize(policy)
data, err := jsonutil.MarshalIndentWithNewline(policy, "", " ")
if err != nil {
return plumbing.ZeroHash, fmt.Errorf("marshal checkpoint policy: %w", err)
}
blobHash, err := checkpoint.CreateBlobFromContent(repo, data)
if err != nil {
return plumbing.ZeroHash, err
}
treeHash, err := checkpoint.BuildTreeFromEntries(ctx, repo, map[string]object.TreeEntry{
PolicyFileName: {Name: PolicyFileName, Mode: filemode.Regular, Hash: blobHash},
})
if err != nil {
return plumbing.ZeroHash, fmt.Errorf("build checkpoint policy tree: %w", err)
}
authorName, authorEmail := checkpoint.GetGitAuthorFromRepo(repo)
commitHash, err := checkpoint.CreateCommit(ctx, repo, treeHash, parent, "Update checkpoint policy", authorName, authorEmail)
if err != nil {
return plumbing.ZeroHash, err
}
if err := SetRef(repo, RefName, commitHash); err != nil {
return plumbing.ZeroHash, err
}
return commitHash, nil
}
func SetRef(repo *git.Repository, ref plumbing.ReferenceName, hash plumbing.Hash) error {
if err := repo.Storer.SetReference(plumbing.NewHashReference(ref, hash)); err != nil {
return fmt.Errorf("set checkpoint policy ref %s: %w", ref, err)
}
return nil
}
func readFromHash(ctx context.Context, repo *git.Repository, hash plumbing.Hash, source Source) (State, error) {
if err := ctx.Err(); err != nil {
return State{}, err
}
commit, err := repo.CommitObject(hash)
if err != nil {
return State{}, fmt.Errorf("read checkpoint policy commit: %w", err)
}
tree, err := commit.Tree()
if err != nil {
return State{}, fmt.Errorf("read checkpoint policy tree: %w", err)
}
file, err := tree.File(PolicyFileName)
if err != nil {
return State{}, fmt.Errorf("read %s: %w", PolicyFileName, err)
}
content, err := file.Contents()
if err != nil {
return State{}, fmt.Errorf("read %s contents: %w", PolicyFileName, err)
}
var policy Policy
if err := json.Unmarshal([]byte(content), &policy); err != nil {
return State{}, fmt.Errorf("parse %s: %w", PolicyFileName, err)
}
return State{Policy: Normalize(policy), Source: source, Hash: hash}, nil
}
package checkpointpolicy_test
import (
"context"
"encoding/json"
"testing"
"github.com/entireio/cli/cmd/entire/cli/checkpoint"
"github.com/entireio/cli/cmd/entire/cli/checkpointpolicy"
"github.com/entireio/cli/cmd/entire/cli/testutil"
"github.com/go-git/go-git/v6"
"github.com/go-git/go-git/v6/plumbing"
"github.com/go-git/go-git/v6/plumbing/filemode"
"github.com/go-git/go-git/v6/plumbing/object"
"github.com/stretchr/testify/require"
)
func TestReadLocalPolicyDefaultsWhenRefMissing(t *testing.T) {
t.Parallel()
repo := initPolicyRepo(t)
got, err := checkpointpolicy.ReadLocal(t.Context(), repo)
require.NoError(t, err)
require.Equal(t, checkpointpolicy.SourceDefaults, got.Source)
require.Equal(t, checkpointpolicy.DefaultPolicy(), got.Policy)
require.True(t, got.Hash.IsZero())
}
func TestWriteAndReadLocalPolicy(t *testing.T) {
t.Parallel()
repo := initPolicyRepo(t)
policy := checkpointpolicy.Policy{
CheckpointVersion: checkpoint.CheckpointVersionBranchV1,
CheckpointMinVersion: checkpoint.CheckpointVersionBranchV1,
}
hash, err := checkpointpolicy.WriteLocal(t.Context(), repo, plumbing.ZeroHash, policy)
require.NoError(t, err)
require.False(t, hash.IsZero())
got, err := checkpointpolicy.ReadLocal(t.Context(), repo)
require.NoError(t, err)
require.Equal(t, checkpointpolicy.SourceLocal, got.Source)
require.Equal(t, hash, got.Hash)
require.Equal(t, policy, got.Policy)
}
func TestReadLocalPolicyRejectsMalformedJSON(t *testing.T) {
t.Parallel()
repo := initPolicyRepo(t)
writeRawPolicyCommit(t, repo, []byte(`{"checkpoint_version":`), plumbing.ZeroHash)
_, err := checkpointpolicy.ReadLocal(t.Context(), repo)
require.ErrorContains(t, err, "parse policy.json")
}
func TestReadLocalPolicyAllowsUnsupportedPolicy(t *testing.T) {
t.Parallel()
repo := initPolicyRepo(t)
policy := checkpointpolicy.Policy{
CheckpointVersion: "refs-v1",
CheckpointMinVersion: "refs-v1",
}
data, err := json.Marshal(policy)
require.NoError(t, err)
hash := writeRawPolicyCommit(t, repo, data, plumbing.ZeroHash)
func initPolicyRepo(t *testing.T) *git.Repository {
t.Helper()
dir := t.TempDir()
testutil.InitRepo(t, dir)
repo, err := git.PlainOpen(dir)
require.NoError(t, err)
return repo
}
func writeRawPolicyCommit(t *testing.T, repo *git.Repository, data []byte, parent plumbing.Hash) plumbing.Hash {
t.Helper()
blobHash, err := checkpoint.CreateBlobFromContent(repo, data)
require.NoError(t, err)
treeHash, err := checkpoint.BuildTreeFromEntries(context.Background(), repo, map[string]object.TreeEntry{
checkpointpolicy.PolicyFileName: {Name: checkpointpolicy.PolicyFileName, Mode: filemode.Regular, Hash: blobHash},
})
require.NoError(t, err)
commitHash, err := checkpoint.CreateCommit(context.Background(), repo, treeHash, parent, "raw policy", "Test", "test@example.com")
require.NoError(t, err)
require.NoError(t, checkpointpolicy.SetRef(repo, checkpointpolicy.RefName, commitHash))
return commitHash
}