mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-28 12:03:33 +00:00
fix: Equals and Clone for configs (#235)
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -98,6 +99,76 @@ func (c *ConfigMount) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compare compares this ConfigMount with another.
|
||||||
|
// Returns:
|
||||||
|
//
|
||||||
|
// -1 if c < other
|
||||||
|
// 0 if c == other
|
||||||
|
// +1 if c > other
|
||||||
|
func (c *ConfigMount) Compare(other *ConfigMount) int {
|
||||||
|
if c.ConfigName != other.ConfigName {
|
||||||
|
if c.ConfigName < other.ConfigName {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if c.ContainerPath != other.ContainerPath {
|
||||||
|
if c.ContainerPath < other.ContainerPath {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if c.Uid != other.Uid {
|
||||||
|
if c.Uid < other.Uid {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if c.Gid != other.Gid {
|
||||||
|
if c.Gid < other.Gid {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
// Compare Mode (handle nil cases)
|
||||||
|
if c.Mode == nil && other.Mode != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if c.Mode != nil && other.Mode == nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if c.Mode != nil && other.Mode != nil {
|
||||||
|
if *c.Mode < *other.Mode {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if *c.Mode > *other.Mode {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals compares two ConfigMount instances for equality.
|
||||||
|
func (c *ConfigMount) Equals(other *ConfigMount) bool {
|
||||||
|
return c.Compare(other) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConfigMount) Clone() ConfigMount {
|
||||||
|
clone := *c
|
||||||
|
if c.Mode != nil {
|
||||||
|
mode := *c.Mode
|
||||||
|
clone.Mode = &mode
|
||||||
|
}
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortConfigMounts sorts a slice of ConfigMount instances.
|
||||||
|
func sortConfigMounts(mounts []ConfigMount) {
|
||||||
|
sort.Slice(mounts, func(i, j int) bool {
|
||||||
|
return mounts[i].Compare(&mounts[j]) < 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateConfigsAndMounts takes config specs and config mounts and validates that all mounts refer to existing specs
|
// ValidateConfigsAndMounts takes config specs and config mounts and validates that all mounts refer to existing specs
|
||||||
func ValidateConfigsAndMounts(configs []ConfigSpec, mounts []ConfigMount) error {
|
func ValidateConfigsAndMounts(configs []ConfigSpec, mounts []ConfigMount) error {
|
||||||
configMap := make(map[string]struct{})
|
configMap := make(map[string]struct{})
|
||||||
|
|||||||
@@ -290,12 +290,18 @@ func (s *ContainerSpec) Equals(spec ContainerSpec) bool {
|
|||||||
orig := s.SetDefaults()
|
orig := s.SetDefaults()
|
||||||
spec = spec.SetDefaults()
|
spec = spec.SetDefaults()
|
||||||
|
|
||||||
|
// Volumes
|
||||||
slices.Sort(orig.Volumes)
|
slices.Sort(orig.Volumes)
|
||||||
slices.Sort(spec.Volumes)
|
slices.Sort(spec.Volumes)
|
||||||
|
|
||||||
|
// Volume mounts
|
||||||
sortVolumeMounts(orig.VolumeMounts)
|
sortVolumeMounts(orig.VolumeMounts)
|
||||||
sortVolumeMounts(spec.VolumeMounts)
|
sortVolumeMounts(spec.VolumeMounts)
|
||||||
|
|
||||||
|
// Config mounts
|
||||||
|
sortConfigMounts(orig.ConfigMounts)
|
||||||
|
sortConfigMounts(spec.ConfigMounts)
|
||||||
|
|
||||||
return cmp.Equal(orig, spec, cmpopts.EquateEmpty())
|
return cmp.Equal(orig, spec, cmpopts.EquateEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,6 +323,12 @@ func (s *ContainerSpec) Clone() ContainerSpec {
|
|||||||
}
|
}
|
||||||
spec.LogDriver = &logDriver
|
spec.LogDriver = &logDriver
|
||||||
}
|
}
|
||||||
|
if s.Env != nil {
|
||||||
|
spec.Env = make(EnvVars, len(s.Env))
|
||||||
|
for k, v := range s.Env {
|
||||||
|
spec.Env[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
if s.Volumes != nil {
|
if s.Volumes != nil {
|
||||||
spec.Volumes = make([]string, len(s.Volumes))
|
spec.Volumes = make([]string, len(s.Volumes))
|
||||||
copy(spec.Volumes, s.Volumes)
|
copy(spec.Volumes, s.Volumes)
|
||||||
@@ -325,6 +337,12 @@ func (s *ContainerSpec) Clone() ContainerSpec {
|
|||||||
spec.VolumeMounts = make([]VolumeMount, len(s.VolumeMounts))
|
spec.VolumeMounts = make([]VolumeMount, len(s.VolumeMounts))
|
||||||
copy(spec.VolumeMounts, s.VolumeMounts)
|
copy(spec.VolumeMounts, s.VolumeMounts)
|
||||||
}
|
}
|
||||||
|
if s.ConfigMounts != nil {
|
||||||
|
spec.ConfigMounts = make([]ConfigMount, len(s.ConfigMounts))
|
||||||
|
for i, cm := range s.ConfigMounts {
|
||||||
|
spec.ConfigMounts[i] = cm.Clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return spec
|
return spec
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// boolPtr is a convenience function to create a pointer to a uint64 value
|
||||||
|
// TODO: Make this a generic function that works for any type
|
||||||
|
func boolPtr(b bool) *bool {
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
func TestServiceSpec_Validate_CaddyAndPorts(t *testing.T) {
|
func TestServiceSpec_Validate_CaddyAndPorts(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -197,3 +205,78 @@ func TestServiceSpec_Validate_CaddyAndPorts(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContainerSpec_Clone(t *testing.T) {
|
||||||
|
mode := os.FileMode(0o644)
|
||||||
|
original := ContainerSpec{
|
||||||
|
Command: []string{"sh", "-c", "echo hello"},
|
||||||
|
Entrypoint: []string{"/bin/bash"},
|
||||||
|
Env: EnvVars{
|
||||||
|
"FOO": "bar",
|
||||||
|
"BAZ": "qux",
|
||||||
|
},
|
||||||
|
Image: "nginx:latest",
|
||||||
|
Init: boolPtr(true),
|
||||||
|
LogDriver: &LogDriver{
|
||||||
|
Name: "json-file",
|
||||||
|
Options: map[string]string{
|
||||||
|
"max-size": "10m",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Privileged: true,
|
||||||
|
PullPolicy: PullPolicyAlways,
|
||||||
|
Resources: ContainerResources{
|
||||||
|
CPU: 1234,
|
||||||
|
Memory: 2345,
|
||||||
|
MemoryReservation: 3456,
|
||||||
|
},
|
||||||
|
User: "1000:1000",
|
||||||
|
Volumes: []string{"/data", "/config"},
|
||||||
|
VolumeMounts: []VolumeMount{
|
||||||
|
{VolumeName: "data", ContainerPath: "/data"},
|
||||||
|
},
|
||||||
|
ConfigMounts: []ConfigMount{
|
||||||
|
{ConfigName: "app-config", ContainerPath: "/etc/config", Mode: &mode},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cloned := original.Clone()
|
||||||
|
|
||||||
|
// Check ContainerSpec equality
|
||||||
|
assert.True(t, original.Equals(cloned))
|
||||||
|
|
||||||
|
// Verify deep copy by modifying the original
|
||||||
|
stringModified := "modified"
|
||||||
|
original.Command[0] = stringModified
|
||||||
|
original.Entrypoint[0] = stringModified
|
||||||
|
original.Env["FOO"] = stringModified
|
||||||
|
original.LogDriver.Options["max-size"] = stringModified
|
||||||
|
original.Volumes[0] = stringModified
|
||||||
|
original.VolumeMounts[0].ContainerPath = stringModified
|
||||||
|
original.ConfigMounts[0].ContainerPath = stringModified
|
||||||
|
*original.ConfigMounts[0].Mode = 0o755 // Modify the Mode pointer value
|
||||||
|
|
||||||
|
assert.False(t, original.Equals(cloned))
|
||||||
|
// Assert cloned values are unchanged
|
||||||
|
assert.Equal(t, "sh", cloned.Command[0])
|
||||||
|
assert.Equal(t, "/bin/bash", cloned.Entrypoint[0])
|
||||||
|
assert.Equal(t, "bar", cloned.Env["FOO"])
|
||||||
|
assert.Equal(t, "qux", cloned.Env["BAZ"])
|
||||||
|
assert.Equal(t, "nginx:latest", cloned.Image)
|
||||||
|
assert.NotNil(t, cloned.Init)
|
||||||
|
assert.Equal(t, true, *cloned.Init)
|
||||||
|
assert.NotNil(t, cloned.LogDriver)
|
||||||
|
assert.Equal(t, "json-file", cloned.LogDriver.Name)
|
||||||
|
assert.Equal(t, "10m", cloned.LogDriver.Options["max-size"])
|
||||||
|
assert.Equal(t, true, cloned.Privileged)
|
||||||
|
assert.Equal(t, PullPolicyAlways, cloned.PullPolicy)
|
||||||
|
assert.Equal(t, int64(1234), cloned.Resources.CPU)
|
||||||
|
assert.Equal(t, int64(2345), cloned.Resources.Memory)
|
||||||
|
assert.Equal(t, int64(3456), cloned.Resources.MemoryReservation)
|
||||||
|
assert.Equal(t, "1000:1000", cloned.User)
|
||||||
|
assert.Equal(t, "/data", cloned.Volumes[0])
|
||||||
|
assert.Equal(t, "/data", cloned.VolumeMounts[0].ContainerPath)
|
||||||
|
assert.Equal(t, "/etc/config", cloned.ConfigMounts[0].ContainerPath)
|
||||||
|
assert.NotNil(t, cloned.ConfigMounts[0].Mode)
|
||||||
|
assert.Equal(t, os.FileMode(0o644), *cloned.ConfigMounts[0].Mode, "Mode should be deep copied")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user