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

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
}