mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
fix: Equals and Clone for configs (#235)
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@@ -98,6 +99,76 @@ func (c *ConfigMount) Validate() error {
|
||||
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
|
||||
func ValidateConfigsAndMounts(configs []ConfigSpec, mounts []ConfigMount) error {
|
||||
configMap := make(map[string]struct{})
|
||||
|
||||
@@ -290,12 +290,18 @@ func (s *ContainerSpec) Equals(spec ContainerSpec) bool {
|
||||
orig := s.SetDefaults()
|
||||
spec = spec.SetDefaults()
|
||||
|
||||
// Volumes
|
||||
slices.Sort(orig.Volumes)
|
||||
slices.Sort(spec.Volumes)
|
||||
|
||||
// Volume mounts
|
||||
sortVolumeMounts(orig.VolumeMounts)
|
||||
sortVolumeMounts(spec.VolumeMounts)
|
||||
|
||||
// Config mounts
|
||||
sortConfigMounts(orig.ConfigMounts)
|
||||
sortConfigMounts(spec.ConfigMounts)
|
||||
|
||||
return cmp.Equal(orig, spec, cmpopts.EquateEmpty())
|
||||
}
|
||||
|
||||
@@ -317,6 +323,12 @@ func (s *ContainerSpec) Clone() ContainerSpec {
|
||||
}
|
||||
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 {
|
||||
spec.Volumes = make([]string, len(s.Volumes))
|
||||
copy(spec.Volumes, s.Volumes)
|
||||
@@ -325,6 +337,12 @@ func (s *ContainerSpec) Clone() ContainerSpec {
|
||||
spec.VolumeMounts = make([]VolumeMount, len(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
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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) {
|
||||
tests := []struct {
|
||||
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