From 9ef39af7010caca3967447388df21a4a70d016b9 Mon Sep 17 00:00:00 2001 From: Juan Antonio Osorio Date: Wed, 31 Jul 2024 15:06:57 +0300 Subject: [PATCH] Add initial structures for entity checkpoints (#4049) Signed-off-by: Juan Antonio Osorio --- internal/entities/checkpoints/checkpoints.go | 82 ++++++++++++ .../entities/checkpoints/checkpoints_test.go | 122 ++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 internal/entities/checkpoints/checkpoints.go create mode 100644 internal/entities/checkpoints/checkpoints_test.go diff --git a/internal/entities/checkpoints/checkpoints.go b/internal/entities/checkpoints/checkpoints.go new file mode 100644 index 0000000000..9adddc24b9 --- /dev/null +++ b/internal/entities/checkpoints/checkpoints.go @@ -0,0 +1,82 @@ +// Copyright 2024 Stacklok, Inc. +// +// 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 checkpoints contains logic relating to checkpoint management for entities +package checkpoints + +import "time" + +// V1 is the version string for the v1 format. +const V1 = "v1" + +// CheckpointEnvelopeV1 is the top-level structure for a checkpoint +// in the v1 format. +type CheckpointEnvelopeV1 struct { + Version string `json:"version" yaml:"version"` + Checkpoint CheckpointV1 `json:"checkpoint" yaml:"checkpoint"` +} + +// CheckpointV1 is the structure for a checkpoint in the v1 format. +type CheckpointV1 struct { + // Timestamp is the time that the checkpoint was verified. + Timestamp time.Time `json:"timestamp" yaml:"timestamp"` + + // CommitHash is the hash of the commit that the checkpoint is for. + CommitHash *string `json:"commitHash,omitempty" yaml:"commitHash,omitempty"` + + // Branch is the branch of the commit that the checkpoint is for. + Branch *string `json:"branch,omitempty" yaml:"branch,omitempty"` + + // Version is the version of the entity that the checkpoint is for. + // This may be a container image tag, a git tag, or some other version. + Version *string `json:"version,omitempty" yaml:"version,omitempty"` + + // Digest is the digest of the entity that the checkpoint is for. + // This may be a container image digest, or some other digest. + Digest *string `json:"digest,omitempty" yaml:"digest,omitempty"` +} + +// NewCheckpointV1 creates a new CheckpointV1 with the given timestamp. +func NewCheckpointV1(timestamp time.Time) *CheckpointEnvelopeV1 { + return &CheckpointEnvelopeV1{ + Version: V1, + Checkpoint: CheckpointV1{ + Timestamp: timestamp, + }, + } +} + +// WithCommitHash sets the commit hash on the checkpoint. +func (c *CheckpointEnvelopeV1) WithCommitHash(commitHash string) *CheckpointEnvelopeV1 { + c.Checkpoint.CommitHash = &commitHash + return c +} + +// WithBranch sets the branch on the checkpoint. +func (c *CheckpointEnvelopeV1) WithBranch(branch string) *CheckpointEnvelopeV1 { + c.Checkpoint.Branch = &branch + return c +} + +// WithVersion sets the version on the checkpoint. +func (c *CheckpointEnvelopeV1) WithVersion(version string) *CheckpointEnvelopeV1 { + c.Checkpoint.Version = &version + return c +} + +// WithDigest sets the digest on the checkpoint. +func (c *CheckpointEnvelopeV1) WithDigest(digest string) *CheckpointEnvelopeV1 { + c.Checkpoint.Digest = &digest + return c +} diff --git a/internal/entities/checkpoints/checkpoints_test.go b/internal/entities/checkpoints/checkpoints_test.go new file mode 100644 index 0000000000..4094d4d0d7 --- /dev/null +++ b/internal/entities/checkpoints/checkpoints_test.go @@ -0,0 +1,122 @@ +// Copyright 2024 Stacklok, Inc. +// +// 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 checkpoints contains logic relating to checkpoint management for entities +package checkpoints + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCheckpointEnvelopeV1_MarshalJSON(t *testing.T) { + t.Parallel() + + // Create a fixed timestamp for consistent test results + timestamp := time.Date(2023, 7, 31, 12, 0, 0, 0, time.UTC) + + // Test cases + tests := []struct { + name string + input *CheckpointEnvelopeV1 + expected string + }{ + { + name: "all fields set", + input: NewCheckpointV1(timestamp). + WithCommitHash("abc123"). + WithBranch("main"). + WithVersion("v1.0.0"). + WithDigest("sha256:xyz"), + expected: `{"version":"v1","checkpoint":{"timestamp":"2023-07-31T12:00:00Z","commitHash":"abc123","branch":"main","version":"v1.0.0","digest":"sha256:xyz"}}`, + }, + { + name: "optional fields omitted", + input: NewCheckpointV1(timestamp), + expected: `{"version":"v1","checkpoint":{"timestamp":"2023-07-31T12:00:00Z"}}`, + }, + { + name: "commit-related fields set", + input: NewCheckpointV1(timestamp). + WithCommitHash("abc123"). + WithBranch("main"), + expected: `{"version":"v1","checkpoint":{"timestamp":"2023-07-31T12:00:00Z","commitHash":"abc123","branch":"main"}}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + // Marshal the input to JSON + output, err := json.Marshal(tt.input) + require.NoError(t, err) + + assert.Equal(t, string(output), tt.expected) + }) + } +} + +func TestCheckpointEnvelopeV1_UnmarshalJson(t *testing.T) { + t.Parallel() + + // Create a fixed timestamp for consistent test results + timestamp := time.Date(2023, 7, 31, 12, 0, 0, 0, time.UTC) + + // Test cases + tests := []struct { + name string + input string + expected *CheckpointEnvelopeV1 + }{ + { + name: "all fields set", + input: `{"version":"v1","checkpoint":{"timestamp":"2023-07-31T12:00:00Z","commitHash":"abc123","branch":"main","version":"v1.0.0","digest":"sha256:xyz"}}`, + expected: NewCheckpointV1(timestamp). + WithCommitHash("abc123"). + WithBranch("main"). + WithVersion("v1.0.0"). + WithDigest("sha256:xyz"), + }, + { + name: "optional fields omitted", + input: `{"version":"v1","checkpoint":{"timestamp":"2023-07-31T12:00:00Z"}}`, + expected: NewCheckpointV1(timestamp), + }, + { + name: "commit-related fields set", + input: `{"version":"v1","checkpoint":{"timestamp":"2023-07-31T12:00:00Z","commitHash":"abc123","branch":"main"}}`, + expected: NewCheckpointV1(timestamp). + WithCommitHash("abc123"). + WithBranch("main"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + // Unmarshal the input to a CheckpointEnvelopeV1 + var output CheckpointEnvelopeV1 + err := json.Unmarshal([]byte(tt.input), &output) + require.NoError(t, err) + + assert.Equal(t, output, *tt.expected) + }) + } +}