Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
vimystic authored Dec 15, 2024
2 parents e52934a + 92a902e commit bb30174
Show file tree
Hide file tree
Showing 17 changed files with 561 additions and 61 deletions.
19 changes: 11 additions & 8 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,14 @@ jobs:
build-args: VERSION=${{ steps.meta.outputs.version }}

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ fromJSON(steps.meta.outputs.json).tags[0] }}'
format: 'table'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'
run: |
for i in {1..3}; do
if docker run --rm aquasec/trivy:latest image --exit-code 0 --severity CRITICAL,HIGH --ignore-unfixed ${{ fromJSON(steps.meta.outputs.json).tags[0] }}; then
break
elif [ $i -lt 3 ]; then
echo "Retrying in 60 seconds..."
sleep 60
else
exit 1
fi
done
14 changes: 14 additions & 0 deletions api/v1/cosmosfullnode_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,25 @@ const CosmosFullNodeController = "CosmosFullNode"
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

type Ordinals struct {
// start is the number representing the first replica's index. It may be used to number replicas from an alternate index (eg: 1-indexed) over the default 0-indexed names,
// or to orchestrate progressive movement of replicas from one CosmosFullnode spec to another. If set, replica indices will be in the range:
// [.spec.ordinals.start, .spec.ordinals.start + .spec.replicas).
// If unset, defaults to 0. Replica indices will be in the range:
// [0, .spec.replicas).
// +kubebuilder:validation:Minimum:=0
Start int32 `json:"start,omitempty"`
}

// FullNodeSpec defines the desired state of CosmosFullNode
type FullNodeSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file

// Ordinals controls the numbering of replica indices in a CosmosFullnode spec.
// The default ordinals behavior assigns a "0" index to the first replica and increments the index by one for each additional replica requested.
Ordinals Ordinals `json:"ordinals,omitempty"`

// Number of replicas to create.
// Individual replicas have a consistent identity.
// +kubebuilder:validation:Minimum:=0
Expand Down
16 changes: 16 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions config/crd/bases/cosmos.strange.love_cosmosfullnodes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,22 @@ spec:
Example: cosmos-1
Used for debugging.
type: object
ordinals:
description: |-
Ordinals controls the numbering of replica indices in a CosmosFullnode spec.
The default ordinals behavior assigns a "0" index to the first replica and increments the index by one for each additional replica requested.
properties:
start:
description: |-
start is the number representing the first replica's index. It may be used to number replicas from an alternate index (eg: 1-indexed) over the default 0-indexed names,
or to orchestrate progressive movement of replicas from one CosmosFullnode spec to another. If set, replica indices will be in the range:
[.spec.ordinals.start, .spec.ordinals.start + .spec.replicas).
If unset, defaults to 0. Replica indices will be in the range:
[0, .spec.replicas).
format: int32
minimum: 0
type: integer
type: object
podTemplate:
description: |-
Template applied to all pods.
Expand Down
2 changes: 1 addition & 1 deletion internal/fullnode/build_pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func BuildPods(crd *cosmosv1.CosmosFullNode, cksums ConfigChecksums) ([]diff.Res
pods []diff.Resource[*corev1.Pod]
)
candidates := podCandidates(crd)
for i := int32(0); i < crd.Spec.Replicas; i++ {
for i := crd.Spec.Ordinals.Start; i < crd.Spec.Ordinals.Start+crd.Spec.Replicas; i++ {
pod, err := builder.WithOrdinal(i).Build()
if err != nil {
return nil, err
Expand Down
137 changes: 129 additions & 8 deletions internal/fullnode/build_pods_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,127 @@ import (
func TestBuildPods(t *testing.T) {
t.Parallel()

t.Run("happy path", func(t *testing.T) {
t.Run("happy path with starting ordinal", func(t *testing.T) {
crd := &cosmosv1.CosmosFullNode{
ObjectMeta: metav1.ObjectMeta{
Name: "agoric",
Namespace: "test",
},
Spec: cosmosv1.FullNodeSpec{
Replicas: 5,
ChainSpec: cosmosv1.ChainSpec{Network: "devnet"},
PodTemplate: cosmosv1.PodSpec{
Image: "busybox:latest",
},
InstanceOverrides: nil,
Ordinals: cosmosv1.Ordinals{
Start: 2,
},
},
}

cksums := make(ConfigChecksums)
for i := 0; i < int(crd.Spec.Replicas); i++ {
cksums[client.ObjectKey{Namespace: crd.Namespace, Name: fmt.Sprintf("agoric-%d", i+int(crd.Spec.Ordinals.Start))}] = strconv.Itoa(i + int(crd.Spec.Ordinals.Start))
}

pods, err := BuildPods(crd, cksums)
require.NoError(t, err)
require.Equal(t, 5, len(pods))

for i, r := range pods {
expectedOrdinal := crd.Spec.Ordinals.Start + int32(i)
require.Equal(t, int64(expectedOrdinal), r.Ordinal(), i)
require.NotEmpty(t, r.Revision(), i)
require.Equal(t, strconv.Itoa(int(expectedOrdinal)), r.Object().Annotations["cosmos.strange.love/config-checksum"])
}

want := lo.Map([]int{2, 3, 4, 5, 6}, func(i int, _ int) string {
return fmt.Sprintf("agoric-%d", i)
})
got := lo.Map(pods, func(pod diff.Resource[*corev1.Pod], _ int) string { return pod.Object().Name })
require.Equal(t, want, got)

pod, err := NewPodBuilder(crd).WithOrdinal(crd.Spec.Ordinals.Start).Build()
require.NoError(t, err)
require.Equal(t, pod.Spec, pods[0].Object().Spec)
})

t.Run("instance overrides with starting ordinal", func(t *testing.T) {
const (
image = "agoric:latest"
overrideImage = "some_image:custom"
overridePod = "agoric-7"
)
crd := &cosmosv1.CosmosFullNode{
ObjectMeta: metav1.ObjectMeta{
Name: "agoric",
},
Spec: cosmosv1.FullNodeSpec{
Replicas: 6,
PodTemplate: cosmosv1.PodSpec{
Image: image,
},
InstanceOverrides: map[string]cosmosv1.InstanceOverridesSpec{
"agoric-4": {DisableStrategy: ptr(cosmosv1.DisablePod)},
"agoric-6": {DisableStrategy: ptr(cosmosv1.DisableAll)},
overridePod: {Image: overrideImage},
},
Ordinals: cosmosv1.Ordinals{
Start: 2,
},
},
}

pods, err := BuildPods(crd, nil)
require.NoError(t, err)
require.Equal(t, 4, len(pods))

want := lo.Map([]int{2, 3, 5, 7}, func(i int, _ int) string {
return fmt.Sprintf("agoric-%d", i)
})
got := lo.Map(pods, func(pod diff.Resource[*corev1.Pod], _ int) string { return pod.Object().Name })
require.Equal(t, want, got)
for _, pod := range pods {
image := pod.Object().Spec.Containers[0].Image
if pod.Object().Name == overridePod {
require.Equal(t, overrideImage, image)
} else {
require.Equal(t, image, image)
}
}
})

t.Run("scheduled volume snapshot pod candidate with starting ordinal", func(t *testing.T) {
crd := &cosmosv1.CosmosFullNode{
ObjectMeta: metav1.ObjectMeta{
Name: "agoric",
},
Spec: cosmosv1.FullNodeSpec{
Replicas: 6,
Ordinals: cosmosv1.Ordinals{Start: 2},
},
Status: cosmosv1.FullNodeStatus{
ScheduledSnapshotStatus: map[string]cosmosv1.FullNodeSnapshotStatus{
"some.scheduled.snapshot.1": {PodCandidate: "agoric-3"},
"some.scheduled.snapshot.2": {PodCandidate: "agoric-4"},
"some.scheduled.snapshot.ignored": {PodCandidate: "agoric-99"},
},
},
}

pods, err := BuildPods(crd, nil)
require.NoError(t, err)
require.Equal(t, 4, len(pods))

want := lo.Map([]int{2, 5, 6, 7}, func(i int, _ int) string {
return fmt.Sprintf("agoric-%d", i)
})
got := lo.Map(pods, func(pod diff.Resource[*corev1.Pod], _ int) string { return pod.Object().Name })
require.Equal(t, want, got)
})

t.Run("happy path without starting ordinal", func(t *testing.T) {
crd := &cosmosv1.CosmosFullNode{
ObjectMeta: metav1.ObjectMeta{
Name: "agoric",
Expand All @@ -35,31 +155,32 @@ func TestBuildPods(t *testing.T) {

cksums := make(ConfigChecksums)
for i := 0; i < int(crd.Spec.Replicas); i++ {
cksums[client.ObjectKey{Namespace: crd.Namespace, Name: fmt.Sprintf("agoric-%d", i)}] = strconv.Itoa(i)
cksums[client.ObjectKey{Namespace: crd.Namespace, Name: fmt.Sprintf("agoric-%d", i+int(crd.Spec.Ordinals.Start))}] = strconv.Itoa(i + int(crd.Spec.Ordinals.Start))
}

pods, err := BuildPods(crd, cksums)
require.NoError(t, err)
require.Equal(t, 5, len(pods))

for i, r := range pods {
require.Equal(t, int64(i), r.Ordinal(), i)
expectedOrdinal := crd.Spec.Ordinals.Start + int32(i)
require.Equal(t, int64(expectedOrdinal), r.Ordinal(), i)
require.NotEmpty(t, r.Revision(), i)
require.Equal(t, strconv.Itoa(i), r.Object().Annotations["cosmos.strange.love/config-checksum"])
require.Equal(t, strconv.Itoa(int(expectedOrdinal)), r.Object().Annotations["cosmos.strange.love/config-checksum"])
}

want := lo.Map([]int{0, 1, 2, 3, 4}, func(_ int, i int) string {
want := lo.Map([]int{0, 1, 2, 3, 4}, func(i int, _ int) string {
return fmt.Sprintf("agoric-%d", i)
})
got := lo.Map(pods, func(pod diff.Resource[*corev1.Pod], _ int) string { return pod.Object().Name })
require.Equal(t, want, got)

pod, err := NewPodBuilder(crd).WithOrdinal(0).Build()
pod, err := NewPodBuilder(crd).WithOrdinal(crd.Spec.Ordinals.Start).Build()
require.NoError(t, err)
require.Equal(t, pod.Spec, pods[0].Object().Spec)
})

t.Run("instance overrides", func(t *testing.T) {
t.Run("instance overrides without starting ordinal", func(t *testing.T) {
const (
image = "agoric:latest"
overrideImage = "some_image:custom"
Expand Down Expand Up @@ -101,7 +222,7 @@ func TestBuildPods(t *testing.T) {
}
})

t.Run("scheduled volume snapshot pod candidate", func(t *testing.T) {
t.Run("scheduled volume snapshot pod candidate without starting ordinal", func(t *testing.T) {
crd := &cosmosv1.CosmosFullNode{
ObjectMeta: metav1.ObjectMeta{
Name: "agoric",
Expand Down
7 changes: 4 additions & 3 deletions internal/fullnode/configmap_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ const (
func BuildConfigMaps(crd *cosmosv1.CosmosFullNode, peers Peers) ([]diff.Resource[*corev1.ConfigMap], error) {
var (
buf = bufPool.Get().(*bytes.Buffer)
cms = make([]diff.Resource[*corev1.ConfigMap], crd.Spec.Replicas)
cms = make([]diff.Resource[*corev1.ConfigMap], 0, crd.Spec.Replicas)
)
defer bufPool.Put(buf)
defer buf.Reset()
startOrdinal := crd.Spec.Ordinals.Start

for i := int32(0); i < crd.Spec.Replicas; i++ {
for i := startOrdinal; i < startOrdinal+crd.Spec.Replicas; i++ {
data := make(map[string]string)
instance := instanceName(crd, i)
if err := addConfigToml(buf, data, crd, instance, peers); err != nil {
Expand Down Expand Up @@ -75,7 +76,7 @@ func BuildConfigMaps(crd *cosmosv1.CosmosFullNode, peers Peers) ([]diff.Resource
)
cm.Data = data
kube.NormalizeMetadata(&cm.ObjectMeta)
cms[i] = diff.Adapt(&cm, i)
cms = append(cms, diff.Adapt(&cm, int(i-startOrdinal)))
}

return cms, nil
Expand Down
58 changes: 53 additions & 5 deletions internal/fullnode/configmap_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,25 @@ func TestBuildConfigMaps(t *testing.T) {
crd.Namespace = "test"
crd.Spec.PodTemplate.Image = "agoric:v6.0.0"
crd.Spec.ChainSpec.Network = "testnet"
//Default starting ordinal is 0

cms, err := BuildConfigMaps(&crd, nil)
require.NoError(t, err)
require.Equal(t, 3, len(cms))
require.Equal(t, crd.Spec.Replicas, int32(len(cms)))

require.Equal(t, int64(0), cms[0].Ordinal())
require.Equal(t, crd.Spec.Ordinals.Start, int32(cms[0].Ordinal())+crd.Spec.Ordinals.Start)
require.NotEmpty(t, cms[0].Revision())

cm := cms[0].Object()
require.Equal(t, "agoric-0", cm.Name)
require.Equal(t, fmt.Sprintf("agoric-%d", crd.Spec.Ordinals.Start), cm.Name)
require.Equal(t, "test", cm.Namespace)
require.Nil(t, cm.Immutable)

wantLabels := map[string]string{
"app.kubernetes.io/created-by": "cosmos-operator",
"app.kubernetes.io/component": "CosmosFullNode",
"app.kubernetes.io/name": "agoric",
"app.kubernetes.io/instance": "agoric-0",
"app.kubernetes.io/instance": fmt.Sprintf("%s-%d", crd.Name, crd.Spec.Ordinals.Start),
"app.kubernetes.io/version": "v6.0.0",
"cosmos.strange.love/network": "testnet",
"cosmos.strange.love/type": "FullNode",
Expand All @@ -69,7 +70,54 @@ func TestBuildConfigMaps(t *testing.T) {
require.Equal(t, wantLabels, cm.Labels)

cm = cms[1].Object()
require.Equal(t, "agoric-1", cm.Name)
require.Equal(t, fmt.Sprintf("%s-%d", crd.Name, crd.Spec.Ordinals.Start+1), cm.Name)

require.NotEmpty(t, cms[0].Object().Data)
require.Equal(t, cms[0].Object().Data, cms[1].Object().Data)

crd.Spec.Type = cosmosv1.FullNode
cms2, err := BuildConfigMaps(&crd, nil)

require.NoError(t, err)
require.Equal(t, cms, cms2)
})

t.Run("happy path with non 0 starting ordinal", func(t *testing.T) {
crd := defaultCRD()
crd.Spec.Replicas = 3
crd.Name = "agoric"
crd.Namespace = "test"
crd.Spec.PodTemplate.Image = "agoric:v6.0.0"
crd.Spec.ChainSpec.Network = "testnet"
crd.Spec.Ordinals.Start = 2

cms, err := BuildConfigMaps(&crd, nil)
require.NoError(t, err)
require.Equal(t, crd.Spec.Replicas, int32(len(cms)))

require.Equal(t, crd.Spec.Ordinals.Start, int32(cms[0].Ordinal())+crd.Spec.Ordinals.Start)
require.NotEmpty(t, cms[0].Revision())

cm := cms[0].Object()
require.Equal(t, fmt.Sprintf("agoric-%d", crd.Spec.Ordinals.Start), cm.Name)
require.Equal(t, "test", cm.Namespace)
require.Nil(t, cm.Immutable)

wantLabels := map[string]string{
"app.kubernetes.io/created-by": "cosmos-operator",
"app.kubernetes.io/component": "CosmosFullNode",
"app.kubernetes.io/name": "agoric",
"app.kubernetes.io/instance": fmt.Sprintf("%s-%d", crd.Name, crd.Spec.Ordinals.Start),
"app.kubernetes.io/version": "v6.0.0",
"cosmos.strange.love/network": "testnet",
"cosmos.strange.love/type": "FullNode",
}
require.Empty(t, cm.Annotations)

require.Equal(t, wantLabels, cm.Labels)

cm = cms[1].Object()
require.Equal(t, fmt.Sprintf("%s-%d", crd.Name, crd.Spec.Ordinals.Start+1), cm.Name)

require.NotEmpty(t, cms[0].Object().Data)
require.Equal(t, cms[0].Object().Data, cms[1].Object().Data)
Expand Down
Loading

0 comments on commit bb30174

Please sign in to comment.