1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

Using refs in the spec as much as possible to detect unspecified fields

This commit is contained in:
Ewout Prangsma 2018-03-22 17:31:13 +01:00
parent f49f621fc0
commit 1563e9a66a
No known key found for this signature in database
GPG key ID: 4DBAD380D93D0698
23 changed files with 444 additions and 145 deletions

View file

@ -25,12 +25,13 @@ package v1alpha
import ( import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
) )
// AuthenticationSpec holds authentication specific configuration settings // AuthenticationSpec holds authentication specific configuration settings
type AuthenticationSpec struct { type AuthenticationSpec struct {
JWTSecretName string `json:"jwtSecretName,omitempty"` JWTSecretName *string `json:"jwtSecretName,omitempty"`
} }
const ( const (
@ -38,10 +39,18 @@ const (
JWTSecretNameDisabled = "None" JWTSecretNameDisabled = "None"
) )
// GetJWTSecretName returns the value of jwtSecretName.
func (s AuthenticationSpec) GetJWTSecretName() string {
if s.JWTSecretName == nil {
return ""
}
return *s.JWTSecretName
}
// IsAuthenticated returns true if authentication is enabled. // IsAuthenticated returns true if authentication is enabled.
// Returns false other (when JWTSecretName == "None"). // Returns false other (when JWTSecretName == "None").
func (s AuthenticationSpec) IsAuthenticated() bool { func (s AuthenticationSpec) IsAuthenticated() bool {
return s.JWTSecretName != JWTSecretNameDisabled return s.GetJWTSecretName() != JWTSecretNameDisabled
} }
// Validate the given spec // Validate the given spec
@ -50,7 +59,7 @@ func (s AuthenticationSpec) Validate(required bool) error {
return maskAny(errors.Wrap(ValidationError, "JWT secret is required")) return maskAny(errors.Wrap(ValidationError, "JWT secret is required"))
} }
if s.IsAuthenticated() { if s.IsAuthenticated() {
if err := k8sutil.ValidateResourceName(s.JWTSecretName); err != nil { if err := k8sutil.ValidateResourceName(s.GetJWTSecretName()); err != nil {
return maskAny(err) return maskAny(err)
} }
} }
@ -59,8 +68,17 @@ func (s AuthenticationSpec) Validate(required bool) error {
// SetDefaults fills in missing defaults // SetDefaults fills in missing defaults
func (s *AuthenticationSpec) SetDefaults(defaultJWTSecretName string) { func (s *AuthenticationSpec) SetDefaults(defaultJWTSecretName string) {
if s.JWTSecretName == "" { if s.GetJWTSecretName() == "" {
s.JWTSecretName = defaultJWTSecretName // Note that we don't check for nil here, since even a specified, but empty
// string should result in the default value.
s.JWTSecretName = util.String(defaultJWTSecretName)
}
}
// SetDefaultsFrom fills unspecified fields with a value from given source spec.
func (s *AuthenticationSpec) SetDefaultsFrom(source AuthenticationSpec) {
if s.JWTSecretName == nil {
s.JWTSecretName = util.String(source.GetJWTSecretName())
} }
} }
@ -71,7 +89,7 @@ func (s AuthenticationSpec) ResetImmutableFields(fieldPrefix string, target *Aut
var resetFields []string var resetFields []string
if s.IsAuthenticated() != target.IsAuthenticated() { if s.IsAuthenticated() != target.IsAuthenticated() {
// Note: You can change the name, but not from empty to non-empty (or reverse). // Note: You can change the name, but not from empty to non-empty (or reverse).
target.JWTSecretName = s.JWTSecretName target.JWTSecretName = util.StringOrNil(s.JWTSecretName)
resetFields = append(resetFields, fieldPrefix+".jwtSecretName") resetFields = append(resetFields, fieldPrefix+".jwtSecretName")
} }
return resetFields return resetFields

View file

@ -25,23 +25,24 @@ package v1alpha
import ( import (
"testing" "testing"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestAuthenticationSpecValidate(t *testing.T) { func TestAuthenticationSpecValidate(t *testing.T) {
// Valid // Valid
assert.Nil(t, AuthenticationSpec{JWTSecretName: "None"}.Validate(false)) assert.Nil(t, AuthenticationSpec{JWTSecretName: util.String("None")}.Validate(false))
assert.Nil(t, AuthenticationSpec{JWTSecretName: "foo"}.Validate(false)) assert.Nil(t, AuthenticationSpec{JWTSecretName: util.String("foo")}.Validate(false))
assert.Nil(t, AuthenticationSpec{JWTSecretName: "foo"}.Validate(true)) assert.Nil(t, AuthenticationSpec{JWTSecretName: util.String("foo")}.Validate(true))
// Not valid // Not valid
assert.Error(t, AuthenticationSpec{JWTSecretName: "Foo"}.Validate(false)) assert.Error(t, AuthenticationSpec{JWTSecretName: util.String("Foo")}.Validate(false))
} }
func TestAuthenticationSpecIsAuthenticated(t *testing.T) { func TestAuthenticationSpecIsAuthenticated(t *testing.T) {
assert.False(t, AuthenticationSpec{JWTSecretName: "None"}.IsAuthenticated()) assert.False(t, AuthenticationSpec{JWTSecretName: util.String("None")}.IsAuthenticated())
assert.True(t, AuthenticationSpec{JWTSecretName: "foo"}.IsAuthenticated()) assert.True(t, AuthenticationSpec{JWTSecretName: util.String("foo")}.IsAuthenticated())
assert.True(t, AuthenticationSpec{JWTSecretName: ""}.IsAuthenticated()) assert.True(t, AuthenticationSpec{JWTSecretName: util.String("")}.IsAuthenticated())
} }
func TestAuthenticationSpecSetDefaults(t *testing.T) { func TestAuthenticationSpecSetDefaults(t *testing.T) {
@ -50,8 +51,8 @@ func TestAuthenticationSpecSetDefaults(t *testing.T) {
return spec return spec
} }
assert.Equal(t, "test-jwt", def(AuthenticationSpec{}).JWTSecretName) assert.Equal(t, "test-jwt", def(AuthenticationSpec{}).GetJWTSecretName())
assert.Equal(t, "foo", def(AuthenticationSpec{JWTSecretName: "foo"}).JWTSecretName) assert.Equal(t, "foo", def(AuthenticationSpec{JWTSecretName: util.String("foo")}).GetJWTSecretName())
} }
func TestAuthenticationSpecResetImmutableFields(t *testing.T) { func TestAuthenticationSpecResetImmutableFields(t *testing.T) {
@ -63,35 +64,35 @@ func TestAuthenticationSpecResetImmutableFields(t *testing.T) {
}{ }{
// Valid "changes" // Valid "changes"
{ {
AuthenticationSpec{JWTSecretName: "None"}, AuthenticationSpec{JWTSecretName: util.String("None")},
AuthenticationSpec{JWTSecretName: "None"}, AuthenticationSpec{JWTSecretName: util.String("None")},
AuthenticationSpec{JWTSecretName: "None"}, AuthenticationSpec{JWTSecretName: util.String("None")},
nil, nil,
}, },
{ {
AuthenticationSpec{JWTSecretName: "foo"}, AuthenticationSpec{JWTSecretName: util.String("foo")},
AuthenticationSpec{JWTSecretName: "foo"}, AuthenticationSpec{JWTSecretName: util.String("foo")},
AuthenticationSpec{JWTSecretName: "foo"}, AuthenticationSpec{JWTSecretName: util.String("foo")},
nil, nil,
}, },
{ {
AuthenticationSpec{JWTSecretName: "foo"}, AuthenticationSpec{JWTSecretName: util.String("foo")},
AuthenticationSpec{JWTSecretName: "foo2"}, AuthenticationSpec{JWTSecretName: util.String("foo2")},
AuthenticationSpec{JWTSecretName: "foo2"}, AuthenticationSpec{JWTSecretName: util.String("foo2")},
nil, nil,
}, },
// Invalid changes // Invalid changes
{ {
AuthenticationSpec{JWTSecretName: "foo"}, AuthenticationSpec{JWTSecretName: util.String("foo")},
AuthenticationSpec{JWTSecretName: "None"}, AuthenticationSpec{JWTSecretName: util.String("None")},
AuthenticationSpec{JWTSecretName: "foo"}, AuthenticationSpec{JWTSecretName: util.String("foo")},
[]string{"test.jwtSecretName"}, []string{"test.jwtSecretName"},
}, },
{ {
AuthenticationSpec{JWTSecretName: "None"}, AuthenticationSpec{JWTSecretName: util.String("None")},
AuthenticationSpec{JWTSecretName: "foo"}, AuthenticationSpec{JWTSecretName: util.String("foo")},
AuthenticationSpec{JWTSecretName: "None"}, AuthenticationSpec{JWTSecretName: util.String("None")},
[]string{"test.jwtSecretName"}, []string{"test.jwtSecretName"},
}, },
} }

View file

@ -102,6 +102,35 @@ func (s *DeploymentSpec) SetDefaults(deploymentName string) {
s.SyncWorkers.SetDefaults(ServerGroupSyncWorkers, s.Sync.Enabled, s.Mode) s.SyncWorkers.SetDefaults(ServerGroupSyncWorkers, s.Sync.Enabled, s.Mode)
} }
// SetDefaultsFrom fills unspecified fields with a value from given source spec.
func (s *DeploymentSpec) SetDefaultsFrom(source DeploymentSpec) {
if s.Mode == "" {
s.Mode = source.Mode
}
if s.Environment == "" {
s.Environment = source.Environment
}
if s.StorageEngine == "" {
s.StorageEngine = source.StorageEngine
}
if s.Image == "" {
s.Image = source.Image
}
if s.ImagePullPolicy == "" {
s.ImagePullPolicy = source.ImagePullPolicy
}
s.RocksDB.SetDefaultsFrom(source.RocksDB)
s.Authentication.SetDefaultsFrom(source.Authentication)
s.TLS.SetDefaultsFrom(source.TLS)
s.Sync.SetDefaultsFrom(source.Sync)
s.Single.SetDefaultsFrom(source.Single)
s.Agents.SetDefaultsFrom(source.Agents)
s.DBServers.SetDefaultsFrom(source.DBServers)
s.Coordinators.SetDefaultsFrom(source.Coordinators)
s.SyncMasters.SetDefaultsFrom(source.SyncMasters)
s.SyncWorkers.SetDefaultsFrom(source.SyncWorkers)
}
// Validate the specification. // Validate the specification.
// Return errors when validation fails, nil on success. // Return errors when validation fails, nil on success.
func (s *DeploymentSpec) Validate() error { func (s *DeploymentSpec) Validate() error {

View file

@ -47,4 +47,7 @@ type DeploymentStatus struct {
// Plan to update this deployment // Plan to update this deployment
Plan Plan `json:"plan,omitempty"` Plan Plan `json:"plan,omitempty"`
// AcceptedSpec contains the last specification that was accepted by the operator.
AcceptedSpec *DeploymentSpec `json:"accepted-spec,omitempty"`
} }

View file

@ -23,17 +23,26 @@
package v1alpha package v1alpha
import ( import (
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
) )
// MonitoringSpec holds monitoring specific configuration settings // MonitoringSpec holds monitoring specific configuration settings
type MonitoringSpec struct { type MonitoringSpec struct {
TokenSecretName string `json:"tokenSecretName,omitempty"` TokenSecretName *string `json:"tokenSecretName,omitempty"`
}
// GetTokenSecretName returns the value of tokenSecretName.
func (s MonitoringSpec) GetTokenSecretName() string {
if s.TokenSecretName == nil {
return ""
}
return *s.TokenSecretName
} }
// Validate the given spec // Validate the given spec
func (s MonitoringSpec) Validate() error { func (s MonitoringSpec) Validate() error {
if err := k8sutil.ValidateOptionalResourceName(s.TokenSecretName); err != nil { if err := k8sutil.ValidateOptionalResourceName(s.GetTokenSecretName()); err != nil {
return maskAny(err) return maskAny(err)
} }
return nil return nil
@ -43,3 +52,10 @@ func (s MonitoringSpec) Validate() error {
func (s *MonitoringSpec) SetDefaults() { func (s *MonitoringSpec) SetDefaults() {
// Nothing needed // Nothing needed
} }
// SetDefaultsFrom fills unspecified fields with a value from given source spec.
func (s *MonitoringSpec) SetDefaultsFrom(source MonitoringSpec) {
if s.TokenSecretName == nil {
s.TokenSecretName = util.StringOrNil(source.TokenSecretName)
}
}

View file

@ -25,17 +25,19 @@ package v1alpha
import ( import (
"testing" "testing"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestMonitoringSpecValidate(t *testing.T) { func TestMonitoringSpecValidate(t *testing.T) {
// Valid // Valid
assert.Nil(t, MonitoringSpec{TokenSecretName: ""}.Validate()) assert.Nil(t, MonitoringSpec{TokenSecretName: nil}.Validate())
assert.Nil(t, MonitoringSpec{TokenSecretName: "foo"}.Validate()) assert.Nil(t, MonitoringSpec{TokenSecretName: util.String("")}.Validate())
assert.Nil(t, MonitoringSpec{TokenSecretName: "foo"}.Validate()) assert.Nil(t, MonitoringSpec{TokenSecretName: util.String("foo")}.Validate())
assert.Nil(t, MonitoringSpec{TokenSecretName: util.String("foo")}.Validate())
// Not valid // Not valid
assert.Error(t, MonitoringSpec{TokenSecretName: "Foo"}.Validate()) assert.Error(t, MonitoringSpec{TokenSecretName: util.String("Foo")}.Validate())
} }
func TestMonitoringSpecSetDefaults(t *testing.T) { func TestMonitoringSpecSetDefaults(t *testing.T) {
@ -44,6 +46,6 @@ func TestMonitoringSpecSetDefaults(t *testing.T) {
return spec return spec
} }
assert.Equal(t, "", def(MonitoringSpec{}).TokenSecretName) assert.Equal(t, "", def(MonitoringSpec{}).GetTokenSecretName())
assert.Equal(t, "foo", def(MonitoringSpec{TokenSecretName: "foo"}).TokenSecretName) assert.Equal(t, "foo", def(MonitoringSpec{TokenSecretName: util.String("foo")}).GetTokenSecretName())
} }

View file

@ -23,12 +23,27 @@
package v1alpha package v1alpha
import ( import (
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
) )
// RocksDBEncryptionSpec holds rocksdb encryption at rest specific configuration settings // RocksDBEncryptionSpec holds rocksdb encryption at rest specific configuration settings
type RocksDBEncryptionSpec struct { type RocksDBEncryptionSpec struct {
KeySecretName string `json:"keySecretName,omitempty"` KeySecretName *string `json:"keySecretName,omitempty"`
}
// GetKeySecretName returns the value of keySecretName.
func (s RocksDBEncryptionSpec) GetKeySecretName() string {
if s.KeySecretName == nil {
return ""
}
return *s.KeySecretName
}
// IsEncrypted returns true when an encryption key secret name is provided,
// false otherwise.
func (s RocksDBEncryptionSpec) IsEncrypted() bool {
return s.GetKeySecretName() != ""
} }
// RocksDBSpec holds rocksdb specific configuration settings // RocksDBSpec holds rocksdb specific configuration settings
@ -39,12 +54,12 @@ type RocksDBSpec struct {
// IsEncrypted returns true when an encryption key secret name is provided, // IsEncrypted returns true when an encryption key secret name is provided,
// false otherwise. // false otherwise.
func (s RocksDBSpec) IsEncrypted() bool { func (s RocksDBSpec) IsEncrypted() bool {
return s.Encryption.KeySecretName != "" return s.Encryption.IsEncrypted()
} }
// Validate the given spec // Validate the given spec
func (s RocksDBSpec) Validate() error { func (s RocksDBSpec) Validate() error {
if err := k8sutil.ValidateOptionalResourceName(s.Encryption.KeySecretName); err != nil { if err := k8sutil.ValidateOptionalResourceName(s.Encryption.GetKeySecretName()); err != nil {
return maskAny(err) return maskAny(err)
} }
return nil return nil
@ -55,6 +70,13 @@ func (s *RocksDBSpec) SetDefaults() {
// Nothing needed // Nothing needed
} }
// SetDefaultsFrom fills unspecified fields with a value from given source spec.
func (s *RocksDBSpec) SetDefaultsFrom(source RocksDBSpec) {
if s.Encryption.KeySecretName == nil {
s.Encryption.KeySecretName = util.StringOrNil(source.Encryption.KeySecretName)
}
}
// ResetImmutableFields replaces all immutable fields in the given target with values from the source spec. // ResetImmutableFields replaces all immutable fields in the given target with values from the source spec.
// It returns a list of fields that have been reset. // It returns a list of fields that have been reset.
// Field names are relative to given field prefix. // Field names are relative to given field prefix.
@ -62,7 +84,7 @@ func (s RocksDBSpec) ResetImmutableFields(fieldPrefix string, target *RocksDBSpe
var resetFields []string var resetFields []string
if s.IsEncrypted() != target.IsEncrypted() { if s.IsEncrypted() != target.IsEncrypted() {
// Note: You can change the name, but not from empty to non-empty (or reverse). // Note: You can change the name, but not from empty to non-empty (or reverse).
target.Encryption.KeySecretName = s.Encryption.KeySecretName target.Encryption.KeySecretName = util.StringOrNil(s.Encryption.KeySecretName)
resetFields = append(resetFields, fieldPrefix+".encryption.keySecretName") resetFields = append(resetFields, fieldPrefix+".encryption.keySecretName")
} }
return resetFields return resetFields

View file

@ -25,22 +25,23 @@ package v1alpha
import ( import (
"testing" "testing"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestRocksDBSpecValidate(t *testing.T) { func TestRocksDBSpecValidate(t *testing.T) {
// Valid // Valid
assert.Nil(t, RocksDBSpec{}.Validate()) assert.Nil(t, RocksDBSpec{}.Validate())
assert.Nil(t, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: "foo"}}.Validate()) assert.Nil(t, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: util.String("foo")}}.Validate())
// Not valid // Not valid
assert.Error(t, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: "Foo"}}.Validate()) assert.Error(t, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: util.String("Foo")}}.Validate())
} }
func TestRocksDBSpecIsEncrypted(t *testing.T) { func TestRocksDBSpecIsEncrypted(t *testing.T) {
assert.False(t, RocksDBSpec{}.IsEncrypted()) assert.False(t, RocksDBSpec{}.IsEncrypted())
assert.False(t, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: ""}}.IsEncrypted()) assert.False(t, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: util.String("")}}.IsEncrypted())
assert.True(t, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: "foo"}}.IsEncrypted()) assert.True(t, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: util.String("foo")}}.IsEncrypted())
} }
func TestRocksDBSpecSetDefaults(t *testing.T) { func TestRocksDBSpecSetDefaults(t *testing.T) {
@ -49,7 +50,7 @@ func TestRocksDBSpecSetDefaults(t *testing.T) {
return spec return spec
} }
assert.Equal(t, "", def(RocksDBSpec{}).Encryption.KeySecretName) assert.Equal(t, "", def(RocksDBSpec{}).Encryption.GetKeySecretName())
} }
func TestRocksDBSpecResetImmutableFields(t *testing.T) { func TestRocksDBSpecResetImmutableFields(t *testing.T) {
@ -67,23 +68,23 @@ func TestRocksDBSpecResetImmutableFields(t *testing.T) {
nil, nil,
}, },
{ {
RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: "foo"}}, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: util.String("foo")}},
RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: "foo"}}, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: util.String("foo")}},
RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: "foo"}}, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: util.String("foo")}},
nil, nil,
}, },
{ {
RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: "foo"}}, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: util.String("foo")}},
RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: "foo2"}}, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: util.String("foo2")}},
RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: "foo2"}}, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: util.String("foo2")}},
nil, nil,
}, },
// Invalid changes // Invalid changes
{ {
RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: "foo"}}, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: util.String("foo")}},
RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: ""}}, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: util.String("")}},
RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: "foo"}}, RocksDBSpec{Encryption: RocksDBEncryptionSpec{KeySecretName: util.String("foo")}},
[]string{"test.encryption.keySecretName"}, []string{"test.encryption.keySecretName"},
}, },
} }

View file

@ -26,20 +26,38 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
"github.com/arangodb/kube-arangodb/pkg/util"
) )
// ServerGroupSpec contains the specification for all servers in a specific group (e.g. all agents) // ServerGroupSpec contains the specification for all servers in a specific group (e.g. all agents)
type ServerGroupSpec struct { type ServerGroupSpec struct {
// Count holds the requested number of servers // Count holds the requested number of servers
Count int `json:"count,omitempty"` Count *int `json:"count,omitempty"`
// Args holds additional commandline arguments // Args holds additional commandline arguments
Args []string `json:"args,omitempty"` Args []string `json:"args,omitempty"`
// StorageClassName specifies the classname for storage of the servers. // StorageClassName specifies the classname for storage of the servers.
StorageClassName string `json:"storageClassName,omitempty"` StorageClassName *string `json:"storageClassName,omitempty"`
// Resources holds resource requests & limits // Resources holds resource requests & limits
Resources v1.ResourceRequirements `json:"resource,omitempty"` Resources v1.ResourceRequirements `json:"resource,omitempty"`
} }
// GetCount returns the value of count.
func (s ServerGroupSpec) GetCount() int {
if s.Count == nil {
return 0
}
return *s.Count
}
// GetStorageClassName returns the value of count.
func (s ServerGroupSpec) GetStorageClassName() string {
if s.StorageClassName == nil {
return ""
}
return *s.StorageClassName
}
// Validate the given group spec // Validate the given group spec
func (s ServerGroupSpec) Validate(group ServerGroup, used bool, mode DeploymentMode, env Environment) error { func (s ServerGroupSpec) Validate(group ServerGroup, used bool, mode DeploymentMode, env Environment) error {
if used { if used {
@ -56,13 +74,13 @@ func (s ServerGroupSpec) Validate(group ServerGroup, used bool, mode DeploymentM
minCount = 2 minCount = 2
} }
} }
if s.Count < minCount { if s.GetCount() < minCount {
return maskAny(errors.Wrapf(ValidationError, "Invalid count value %d. Expected >= %d", s.Count, minCount)) return maskAny(errors.Wrapf(ValidationError, "Invalid count value %d. Expected >= %d", s.Count, minCount))
} }
if s.Count > 1 && group == ServerGroupSingle && mode == DeploymentModeSingle { if s.GetCount() > 1 && group == ServerGroupSingle && mode == DeploymentModeSingle {
return maskAny(errors.Wrapf(ValidationError, "Invalid count value %d. Expected 1", s.Count)) return maskAny(errors.Wrapf(ValidationError, "Invalid count value %d. Expected 1", s.Count))
} }
} else if s.Count != 0 { } else if s.GetCount() != 0 {
return maskAny(errors.Wrapf(ValidationError, "Invalid count value %d for un-used group. Expected 0", s.Count)) return maskAny(errors.Wrapf(ValidationError, "Invalid count value %d for un-used group. Expected 0", s.Count))
} }
return nil return nil
@ -70,16 +88,16 @@ func (s ServerGroupSpec) Validate(group ServerGroup, used bool, mode DeploymentM
// SetDefaults fills in missing defaults // SetDefaults fills in missing defaults
func (s *ServerGroupSpec) SetDefaults(group ServerGroup, used bool, mode DeploymentMode) { func (s *ServerGroupSpec) SetDefaults(group ServerGroup, used bool, mode DeploymentMode) {
if s.Count == 0 && used { if s.GetCount() == 0 && used {
switch group { switch group {
case ServerGroupSingle: case ServerGroupSingle:
if mode == DeploymentModeSingle { if mode == DeploymentModeSingle {
s.Count = 1 // Single server s.Count = util.Int(1) // Single server
} else { } else {
s.Count = 2 // Resilient single s.Count = util.Int(2) // Resilient single
} }
default: default:
s.Count = 3 s.Count = util.Int(3)
} }
} }
if _, found := s.Resources.Requests[v1.ResourceStorage]; !found { if _, found := s.Resources.Requests[v1.ResourceStorage]; !found {
@ -93,18 +111,32 @@ func (s *ServerGroupSpec) SetDefaults(group ServerGroup, used bool, mode Deploym
} }
} }
// SetDefaultsFrom fills unspecified fields with a value from given source spec.
func (s *ServerGroupSpec) SetDefaultsFrom(source ServerGroupSpec) {
if s.Count == nil {
s.Count = util.IntOrNil(source.Count)
}
if s.Args == nil {
s.Args = source.Args
}
if s.StorageClassName == nil {
s.StorageClassName = util.StringOrNil(source.StorageClassName)
}
// TODO Resources
}
// ResetImmutableFields replaces all immutable fields in the given target with values from the source spec. // ResetImmutableFields replaces all immutable fields in the given target with values from the source spec.
// It returns a list of fields that have been reset. // It returns a list of fields that have been reset.
func (s ServerGroupSpec) ResetImmutableFields(group ServerGroup, fieldPrefix string, target *ServerGroupSpec) []string { func (s ServerGroupSpec) ResetImmutableFields(group ServerGroup, fieldPrefix string, target *ServerGroupSpec) []string {
var resetFields []string var resetFields []string
if group == ServerGroupAgents { if group == ServerGroupAgents {
if s.Count != target.Count { if s.GetCount() != target.GetCount() {
target.Count = s.Count target.Count = util.IntOrNil(s.Count)
resetFields = append(resetFields, fieldPrefix+".count") resetFields = append(resetFields, fieldPrefix+".count")
} }
} }
if s.StorageClassName != target.StorageClassName { if s.GetStorageClassName() != target.GetStorageClassName() {
target.StorageClassName = s.StorageClassName target.StorageClassName = util.StringOrNil(s.StorageClassName)
resetFields = append(resetFields, fieldPrefix+".storageClassName") resetFields = append(resetFields, fieldPrefix+".storageClassName")
} }
return resetFields return resetFields

View file

@ -73,6 +73,19 @@ func (s *SyncSpec) SetDefaults(defaultImage string, defaulPullPolicy v1.PullPoli
s.Monitoring.SetDefaults() s.Monitoring.SetDefaults()
} }
// SetDefaultsFrom fills unspecified fields with a value from given source spec.
func (s *SyncSpec) SetDefaultsFrom(source SyncSpec) {
if s.Image == "" {
s.Image = source.Image
}
if s.ImagePullPolicy == "" {
s.ImagePullPolicy = source.ImagePullPolicy
}
s.Authentication.SetDefaultsFrom(source.Authentication)
s.TLS.SetDefaultsFrom(source.TLS)
s.Monitoring.SetDefaultsFrom(source.Monitoring)
}
// ResetImmutableFields replaces all immutable fields in the given target with values from the source spec. // ResetImmutableFields replaces all immutable fields in the given target with values from the source spec.
// It returns a list of fields that have been reset. // It returns a list of fields that have been reset.
// Field names are relative to given field prefix. // Field names are relative to given field prefix.

View file

@ -25,24 +25,26 @@ package v1alpha
import ( import (
"testing" "testing"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
) )
func TestSyncSpecValidate(t *testing.T) { func TestSyncSpecValidate(t *testing.T) {
// Valid // Valid
auth := AuthenticationSpec{JWTSecretName: "foo"} auth := AuthenticationSpec{JWTSecretName: util.String("foo")}
tls := TLSSpec{CASecretName: util.String("None")}
assert.Nil(t, SyncSpec{Image: "foo", Authentication: auth}.Validate(DeploymentModeSingle)) assert.Nil(t, SyncSpec{Image: "foo", Authentication: auth}.Validate(DeploymentModeSingle))
assert.Nil(t, SyncSpec{Image: "foo", Authentication: auth}.Validate(DeploymentModeResilientSingle)) assert.Nil(t, SyncSpec{Image: "foo", Authentication: auth}.Validate(DeploymentModeResilientSingle))
assert.Nil(t, SyncSpec{Image: "foo", Authentication: auth}.Validate(DeploymentModeCluster)) assert.Nil(t, SyncSpec{Image: "foo", Authentication: auth}.Validate(DeploymentModeCluster))
assert.Nil(t, SyncSpec{Image: "foo", Authentication: auth, Enabled: true}.Validate(DeploymentModeCluster)) assert.Nil(t, SyncSpec{Image: "foo", Authentication: auth, TLS: tls, Enabled: true}.Validate(DeploymentModeCluster))
// Not valid // Not valid
assert.Error(t, SyncSpec{Image: "", Authentication: auth}.Validate(DeploymentModeSingle)) assert.Error(t, SyncSpec{Image: "", Authentication: auth}.Validate(DeploymentModeSingle))
assert.Error(t, SyncSpec{Image: "", Authentication: auth}.Validate(DeploymentModeResilientSingle)) assert.Error(t, SyncSpec{Image: "", Authentication: auth}.Validate(DeploymentModeResilientSingle))
assert.Error(t, SyncSpec{Image: "", Authentication: auth}.Validate(DeploymentModeCluster)) assert.Error(t, SyncSpec{Image: "", Authentication: auth}.Validate(DeploymentModeCluster))
assert.Error(t, SyncSpec{Image: "foo", Authentication: auth, Enabled: true}.Validate(DeploymentModeSingle)) assert.Error(t, SyncSpec{Image: "foo", Authentication: auth, TLS: tls, Enabled: true}.Validate(DeploymentModeSingle))
assert.Error(t, SyncSpec{Image: "foo", Authentication: auth, Enabled: true}.Validate(DeploymentModeResilientSingle)) assert.Error(t, SyncSpec{Image: "foo", Authentication: auth, TLS: tls, Enabled: true}.Validate(DeploymentModeResilientSingle))
} }
func TestSyncSpecSetDefaults(t *testing.T) { func TestSyncSpecSetDefaults(t *testing.T) {
@ -58,8 +60,8 @@ func TestSyncSpecSetDefaults(t *testing.T) {
assert.Equal(t, "foo", def(SyncSpec{Image: "foo"}).Image) assert.Equal(t, "foo", def(SyncSpec{Image: "foo"}).Image)
assert.Equal(t, v1.PullAlways, def(SyncSpec{}).ImagePullPolicy) assert.Equal(t, v1.PullAlways, def(SyncSpec{}).ImagePullPolicy)
assert.Equal(t, v1.PullNever, def(SyncSpec{ImagePullPolicy: v1.PullNever}).ImagePullPolicy) assert.Equal(t, v1.PullNever, def(SyncSpec{ImagePullPolicy: v1.PullNever}).ImagePullPolicy)
assert.Equal(t, "test-jwt", def(SyncSpec{}).Authentication.JWTSecretName) assert.Equal(t, "test-jwt", def(SyncSpec{}).Authentication.GetJWTSecretName())
assert.Equal(t, "foo", def(SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "foo"}}).Authentication.JWTSecretName) assert.Equal(t, "foo", def(SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("foo")}}).Authentication.GetJWTSecretName())
} }
func TestSyncSpecResetImmutableFields(t *testing.T) { func TestSyncSpecResetImmutableFields(t *testing.T) {
@ -95,35 +97,35 @@ func TestSyncSpecResetImmutableFields(t *testing.T) {
nil, nil,
}, },
{ {
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "None"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("None")}},
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "None"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("None")}},
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "None"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("None")}},
nil, nil,
}, },
{ {
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "foo"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("foo")}},
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "foo"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("foo")}},
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "foo"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("foo")}},
nil, nil,
}, },
{ {
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "foo"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("foo")}},
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "foo2"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("foo2")}},
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "foo2"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("foo2")}},
nil, nil,
}, },
// Invalid changes // Invalid changes
{ {
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "foo"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("foo")}},
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "None"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("None")}},
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "foo"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("foo")}},
[]string{"test.auth.jwtSecretName"}, []string{"test.auth.jwtSecretName"},
}, },
{ {
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "None"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("None")}},
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "foo"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("foo")}},
SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: "None"}}, SyncSpec{Authentication: AuthenticationSpec{JWTSecretName: util.String("None")}},
[]string{"test.auth.jwtSecretName"}, []string{"test.auth.jwtSecretName"},
}, },
} }

View file

@ -27,6 +27,7 @@ import (
"net" "net"
"time" "time"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
"github.com/arangodb/kube-arangodb/pkg/util/validation" "github.com/arangodb/kube-arangodb/pkg/util/validation"
) )
@ -37,9 +38,9 @@ const (
// TLSSpec holds TLS specific configuration settings // TLSSpec holds TLS specific configuration settings
type TLSSpec struct { type TLSSpec struct {
CASecretName string `json:"caSecretName,omitempty"` CASecretName *string `json:"caSecretName,omitempty"`
AltNames []string `json:"altNames,omitempty"` AltNames []string `json:"altNames,omitempty"`
TTL time.Duration `json:"ttl,omitempty"` TTL *time.Duration `json:"ttl,omitempty"`
} }
const ( const (
@ -47,14 +48,35 @@ const (
CASecretNameDisabled = "None" CASecretNameDisabled = "None"
) )
// IsSecure returns true when a CA secret has been set, false otherwise. // GetCASecretName returns the value of caSecretName.
func (s TLSSpec) IsSecure() bool { func (s TLSSpec) GetCASecretName() string {
return s.CASecretName != CASecretNameDisabled if s.CASecretName == nil {
return ""
}
return *s.CASecretName
} }
// GetAltNames splits the list of AltNames into DNS names, IP addresses & email addresses. // GetAltNames returns the value of altNames.
func (s TLSSpec) GetAltNames() []string {
return s.AltNames
}
// GetTTL returns the value of ttl.
func (s TLSSpec) GetTTL() time.Duration {
if s.TTL == nil {
return time.Duration(0)
}
return *s.TTL
}
// IsSecure returns true when a CA secret has been set, false otherwise.
func (s TLSSpec) IsSecure() bool {
return s.GetCASecretName() != CASecretNameDisabled
}
// GetParsedAltNames splits the list of AltNames into DNS names, IP addresses & email addresses.
// When an entry is not valid for any of those categories, an error is returned. // When an entry is not valid for any of those categories, an error is returned.
func (s TLSSpec) GetAltNames() (dnsNames, ipAddresses, emailAddresses []string, err error) { func (s TLSSpec) GetParsedAltNames() (dnsNames, ipAddresses, emailAddresses []string, err error) {
for _, name := range s.AltNames { for _, name := range s.AltNames {
if net.ParseIP(name) != nil { if net.ParseIP(name) != nil {
ipAddresses = append(ipAddresses, name) ipAddresses = append(ipAddresses, name)
@ -72,10 +94,10 @@ func (s TLSSpec) GetAltNames() (dnsNames, ipAddresses, emailAddresses []string,
// Validate the given spec // Validate the given spec
func (s TLSSpec) Validate() error { func (s TLSSpec) Validate() error {
if s.IsSecure() { if s.IsSecure() {
if err := k8sutil.ValidateOptionalResourceName(s.CASecretName); err != nil { if err := k8sutil.ValidateResourceName(s.GetCASecretName()); err != nil {
return maskAny(err) return maskAny(err)
} }
if _, _, _, err := s.GetAltNames(); err != nil { if _, _, _, err := s.GetParsedAltNames(); err != nil {
return maskAny(err) return maskAny(err)
} }
} }
@ -84,10 +106,27 @@ func (s TLSSpec) Validate() error {
// SetDefaults fills in missing defaults // SetDefaults fills in missing defaults
func (s *TLSSpec) SetDefaults(defaultCASecretName string) { func (s *TLSSpec) SetDefaults(defaultCASecretName string) {
if s.CASecretName == "" { if s.GetCASecretName() == "" {
s.CASecretName = defaultCASecretName // Note that we don't check for nil here, since even a specified, but empty
// string should result in the default value.
s.CASecretName = util.String(defaultCASecretName)
} }
if s.TTL == 0 { if s.GetTTL() == 0 {
s.TTL = defaultTLSTTL // Note that we don't check for nil here, since even a specified, but zero
// should result in the default value.
s.TTL = util.Duration(defaultTLSTTL)
}
}
// SetDefaultsFrom fills unspecified fields with a value from given source spec.
func (s *TLSSpec) SetDefaultsFrom(source TLSSpec) {
if s.CASecretName == nil {
s.CASecretName = util.String(source.GetCASecretName())
}
if s.AltNames == nil {
s.AltNames = source.AltNames
}
if s.TTL == nil {
s.TTL = util.Duration(source.GetTTL())
} }
} }

View file

@ -26,27 +26,29 @@ import (
"testing" "testing"
"time" "time"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestTLSSpecValidate(t *testing.T) { func TestTLSSpecValidate(t *testing.T) {
// Valid // Valid
assert.Nil(t, TLSSpec{CASecretName: ""}.Validate()) assert.Nil(t, TLSSpec{CASecretName: util.String("foo")}.Validate())
assert.Nil(t, TLSSpec{CASecretName: "foo"}.Validate()) assert.Nil(t, TLSSpec{CASecretName: util.String("None")}.Validate())
assert.Nil(t, TLSSpec{CASecretName: "None"}.Validate()) assert.Nil(t, TLSSpec{CASecretName: util.String("None"), AltNames: []string{}}.Validate())
assert.Nil(t, TLSSpec{AltNames: []string{}}.Validate()) assert.Nil(t, TLSSpec{CASecretName: util.String("None"), AltNames: []string{"foo"}}.Validate())
assert.Nil(t, TLSSpec{AltNames: []string{"foo"}}.Validate()) assert.Nil(t, TLSSpec{CASecretName: util.String("None"), AltNames: []string{"email@example.com", "127.0.0.1"}}.Validate())
assert.Nil(t, TLSSpec{AltNames: []string{"email@example.com", "127.0.0.1"}}.Validate())
// Not valid // Not valid
assert.Error(t, TLSSpec{CASecretName: "Foo"}.Validate()) assert.Error(t, TLSSpec{CASecretName: nil}.Validate())
assert.Error(t, TLSSpec{AltNames: []string{"@@"}}.Validate()) assert.Error(t, TLSSpec{CASecretName: util.String("")}.Validate())
assert.Error(t, TLSSpec{CASecretName: util.String("Foo")}.Validate())
assert.Error(t, TLSSpec{CASecretName: util.String("foo"), AltNames: []string{"@@"}}.Validate())
} }
func TestTLSSpecIsSecure(t *testing.T) { func TestTLSSpecIsSecure(t *testing.T) {
assert.True(t, TLSSpec{CASecretName: ""}.IsSecure()) assert.True(t, TLSSpec{CASecretName: util.String("")}.IsSecure())
assert.True(t, TLSSpec{CASecretName: "foo"}.IsSecure()) assert.True(t, TLSSpec{CASecretName: util.String("foo")}.IsSecure())
assert.False(t, TLSSpec{CASecretName: "None"}.IsSecure()) assert.False(t, TLSSpec{CASecretName: util.String("None")}.IsSecure())
} }
func TestTLSSpecSetDefaults(t *testing.T) { func TestTLSSpecSetDefaults(t *testing.T) {
@ -55,10 +57,10 @@ func TestTLSSpecSetDefaults(t *testing.T) {
return spec return spec
} }
assert.Equal(t, "", def(TLSSpec{}).CASecretName) assert.Equal(t, "", def(TLSSpec{}).GetCASecretName())
assert.Equal(t, "foo", def(TLSSpec{CASecretName: "foo"}).CASecretName) assert.Equal(t, "foo", def(TLSSpec{CASecretName: util.String("foo")}).GetCASecretName())
assert.Len(t, def(TLSSpec{}).AltNames, 0) assert.Len(t, def(TLSSpec{}).AltNames, 0)
assert.Len(t, def(TLSSpec{AltNames: []string{"foo.local"}}).AltNames, 1) assert.Len(t, def(TLSSpec{AltNames: []string{"foo.local"}}).AltNames, 1)
assert.Equal(t, defaultTLSTTL, def(TLSSpec{}).TTL) assert.Equal(t, defaultTLSTTL, def(TLSSpec{}).GetTTL())
assert.Equal(t, time.Hour, def(TLSSpec{TTL: time.Hour}).TTL) assert.Equal(t, time.Hour, def(TLSSpec{TTL: util.Duration(time.Hour)}).GetTTL())
} }

View file

@ -25,6 +25,8 @@
package v1alpha package v1alpha
import ( import (
time "time"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime" runtime "k8s.io/apimachinery/pkg/runtime"
) )
@ -198,6 +200,15 @@ func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
} }
if in.AcceptedSpec != nil {
in, out := &in.AcceptedSpec, &out.AcceptedSpec
if *in == nil {
*out = nil
} else {
*out = new(DeploymentSpec)
(*in).DeepCopyInto(*out)
}
}
return return
} }
@ -401,11 +412,29 @@ func (in *SyncSpec) DeepCopy() *SyncSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSSpec) DeepCopyInto(out *TLSSpec) { func (in *TLSSpec) DeepCopyInto(out *TLSSpec) {
*out = *in *out = *in
if in.CASecretName != nil {
in, out := &in.CASecretName, &out.CASecretName
if *in == nil {
*out = nil
} else {
*out = new(string)
**out = **in
}
}
if in.AltNames != nil { if in.AltNames != nil {
in, out := &in.AltNames, &out.AltNames in, out := &in.AltNames, &out.AltNames
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.TTL != nil {
in, out := &in.TTL, &out.TTL
if *in == nil {
*out = nil
} else {
*out = new(time.Duration)
**out = **in
}
}
return return
} }

View file

@ -72,7 +72,7 @@ type deploymentEvent struct {
} }
const ( const (
deploymentEventQueueSize = 100 deploymentEventQueueSize = 256
minInspectionInterval = time.Second // Ensure we inspect the generated resources no less than with this interval minInspectionInterval = time.Second // Ensure we inspect the generated resources no less than with this interval
maxInspectionInterval = time.Minute // Ensure we inspect the generated resources no less than with this interval maxInspectionInterval = time.Minute // Ensure we inspect the generated resources no less than with this interval
) )
@ -108,6 +108,10 @@ func New(config Config, deps Dependencies, apiObject *api.ArangoDeployment) (*De
eventsCli: deps.KubeCli.Core().Events(apiObject.GetNamespace()), eventsCli: deps.KubeCli.Core().Events(apiObject.GetNamespace()),
clientCache: newClientCache(deps.KubeCli, apiObject), clientCache: newClientCache(deps.KubeCli, apiObject),
} }
if d.status.AcceptedSpec == nil {
// We've validated the spec, so let's use it from now.
d.status.AcceptedSpec = apiObject.Spec.DeepCopy()
}
go d.run() go d.run()
go d.listenForPodEvents() go d.listenForPodEvents()
@ -284,10 +288,14 @@ func (d *Deployment) handleArangoDeploymentUpdatedEvent(event *deploymentEvent)
return maskAny(err) return maskAny(err)
} }
specBefore := d.apiObject.Spec
if d.status.AcceptedSpec != nil {
specBefore = *d.status.AcceptedSpec
}
newAPIObject := current.DeepCopy() newAPIObject := current.DeepCopy()
newAPIObject.Spec.SetDefaults(newAPIObject.GetName()) newAPIObject.Spec.SetDefaultsFrom(specBefore)
newAPIObject.Status = d.status newAPIObject.Status = d.status
resetFields := d.apiObject.Spec.ResetImmutableFields(&newAPIObject.Spec) resetFields := specBefore.ResetImmutableFields(&newAPIObject.Spec)
if len(resetFields) > 0 { if len(resetFields) > 0 {
log.Debug().Strs("fields", resetFields).Msg("Found modified immutable fields") log.Debug().Strs("fields", resetFields).Msg("Found modified immutable fields")
} }
@ -311,6 +319,11 @@ func (d *Deployment) handleArangoDeploymentUpdatedEvent(event *deploymentEvent)
if err := d.updateCRSpec(newAPIObject.Spec); err != nil { if err := d.updateCRSpec(newAPIObject.Spec); err != nil {
return maskAny(fmt.Errorf("failed to update ArangoDeployment spec: %v", err)) return maskAny(fmt.Errorf("failed to update ArangoDeployment spec: %v", err))
} }
// Save updated accepted spec
d.status.AcceptedSpec = newAPIObject.Spec.DeepCopy()
if err := d.updateCRStatus(); err != nil {
return maskAny(fmt.Errorf("failed to update ArangoDeployment status: %v", err))
}
// Trigger inspect // Trigger inspect
d.inspectTrigger.Trigger() d.inspectTrigger.Trigger()

View file

@ -40,7 +40,7 @@ func (d *Deployment) createInitialMembers(apiObject *api.ArangoDeployment) error
// Go over all groups and create members // Go over all groups and create members
if err := apiObject.ForeachServerGroup(func(group api.ServerGroup, spec api.ServerGroupSpec, status *api.MemberStatusList) error { if err := apiObject.ForeachServerGroup(func(group api.ServerGroup, spec api.ServerGroupSpec, status *api.MemberStatusList) error {
for len(*status) < spec.Count { for len(*status) < spec.GetCount() {
if err := d.createMember(group, apiObject); err != nil { if err := d.createMember(group, apiObject); err != nil {
return maskAny(err) return maskAny(err)
} }

View file

@ -69,13 +69,13 @@ func createPlan(log zerolog.Logger, currentPlan api.Plan, spec api.DeploymentSpe
// Never scale down // Never scale down
case api.DeploymentModeResilientSingle: case api.DeploymentModeResilientSingle:
// Only scale singles // Only scale singles
plan = append(plan, createScalePlan(log, status.Members.Single, api.ServerGroupSingle, spec.Single.Count)...) plan = append(plan, createScalePlan(log, status.Members.Single, api.ServerGroupSingle, spec.Single.GetCount())...)
case api.DeploymentModeCluster: case api.DeploymentModeCluster:
// Scale dbservers, coordinators, syncmasters & syncworkers // Scale dbservers, coordinators, syncmasters & syncworkers
plan = append(plan, createScalePlan(log, status.Members.DBServers, api.ServerGroupDBServers, spec.DBServers.Count)...) plan = append(plan, createScalePlan(log, status.Members.DBServers, api.ServerGroupDBServers, spec.DBServers.GetCount())...)
plan = append(plan, createScalePlan(log, status.Members.Coordinators, api.ServerGroupCoordinators, spec.Coordinators.Count)...) plan = append(plan, createScalePlan(log, status.Members.Coordinators, api.ServerGroupCoordinators, spec.Coordinators.GetCount())...)
plan = append(plan, createScalePlan(log, status.Members.SyncMasters, api.ServerGroupSyncMasters, spec.SyncMasters.Count)...) plan = append(plan, createScalePlan(log, status.Members.SyncMasters, api.ServerGroupSyncMasters, spec.SyncMasters.GetCount())...)
plan = append(plan, createScalePlan(log, status.Members.SyncWorkers, api.ServerGroupSyncWorkers, spec.SyncWorkers.Count)...) plan = append(plan, createScalePlan(log, status.Members.SyncWorkers, api.ServerGroupSyncWorkers, spec.SyncWorkers.GetCount())...)
} }
// Return plan // Return plan

View file

@ -139,7 +139,7 @@ func createArangodArgs(apiObject metav1.Object, deplSpec api.DeploymentSpec, gro
optionPair{"--cluster.my-id", id}, optionPair{"--cluster.my-id", id},
optionPair{"--agency.activate", "true"}, optionPair{"--agency.activate", "true"},
optionPair{"--agency.my-address", myTCPURL}, optionPair{"--agency.my-address", myTCPURL},
optionPair{"--agency.size", strconv.Itoa(deplSpec.Agents.Count)}, optionPair{"--agency.size", strconv.Itoa(deplSpec.Agents.GetCount())},
optionPair{"--agency.supervision", "true"}, optionPair{"--agency.supervision", "true"},
optionPair{"--foxx.queues", "false"}, optionPair{"--foxx.queues", "false"},
optionPair{"--server.statistics", "false"}, optionPair{"--server.statistics", "false"},
@ -242,7 +242,7 @@ func (d *Deployment) createLivenessProbe(apiObject *api.ArangoDeployment, group
return nil, nil return nil, nil
case api.ServerGroupSyncMasters, api.ServerGroupSyncWorkers: case api.ServerGroupSyncMasters, api.ServerGroupSyncWorkers:
authorization := "" authorization := ""
if apiObject.Spec.Sync.Monitoring.TokenSecretName != "" { if apiObject.Spec.Sync.Monitoring.GetTokenSecretName() != "" {
// Use monitoring token // Use monitoring token
token, err := d.getSyncMonitoringToken(apiObject) token, err := d.getSyncMonitoringToken(apiObject)
if err != nil { if err != nil {
@ -344,14 +344,14 @@ func (d *Deployment) ensurePods(apiObject *api.ArangoDeployment) error {
} }
rocksdbEncryptionSecretName := "" rocksdbEncryptionSecretName := ""
if apiObject.Spec.RocksDB.IsEncrypted() { if apiObject.Spec.RocksDB.IsEncrypted() {
rocksdbEncryptionSecretName = apiObject.Spec.RocksDB.Encryption.KeySecretName rocksdbEncryptionSecretName = apiObject.Spec.RocksDB.Encryption.GetKeySecretName()
if err := k8sutil.ValidateEncryptionKeySecret(kubecli.CoreV1(), rocksdbEncryptionSecretName, ns); err != nil { if err := k8sutil.ValidateEncryptionKeySecret(kubecli.CoreV1(), rocksdbEncryptionSecretName, ns); err != nil {
return maskAny(errors.Wrapf(err, "RocksDB encryption key secret validation failed")) return maskAny(errors.Wrapf(err, "RocksDB encryption key secret validation failed"))
} }
} }
if apiObject.Spec.IsAuthenticated() { if apiObject.Spec.IsAuthenticated() {
env[constants.EnvArangodJWTSecret] = k8sutil.EnvValue{ env[constants.EnvArangodJWTSecret] = k8sutil.EnvValue{
SecretName: apiObject.Spec.Authentication.JWTSecretName, SecretName: apiObject.Spec.Authentication.GetJWTSecretName(),
SecretKey: constants.SecretKeyJWT, SecretKey: constants.SecretKeyJWT,
} }
} }

View file

@ -38,7 +38,7 @@ func (d *Deployment) ensurePVCs(apiObject *api.ArangoDeployment) error {
if err := apiObject.ForeachServerGroup(func(group api.ServerGroup, spec api.ServerGroupSpec, status *api.MemberStatusList) error { if err := apiObject.ForeachServerGroup(func(group api.ServerGroup, spec api.ServerGroupSpec, status *api.MemberStatusList) error {
for _, m := range *status { for _, m := range *status {
if m.PersistentVolumeClaimName != "" { if m.PersistentVolumeClaimName != "" {
storageClassName := spec.StorageClassName storageClassName := spec.GetStorageClassName()
role := group.AsRole() role := group.AsRole()
resources := spec.Resources resources := spec.Resources
if err := k8sutil.CreatePersistentVolumeClaim(kubecli, m.PersistentVolumeClaimName, deploymentName, ns, storageClassName, role, resources, owner); err != nil { if err := k8sutil.CreatePersistentVolumeClaim(kubecli, m.PersistentVolumeClaimName, deploymentName, ns, storageClassName, role, resources, owner); err != nil {

View file

@ -36,7 +36,7 @@ import (
// createSecrets creates all secrets needed to run the given deployment // createSecrets creates all secrets needed to run the given deployment
func (d *Deployment) createSecrets(apiObject *api.ArangoDeployment) error { func (d *Deployment) createSecrets(apiObject *api.ArangoDeployment) error {
if apiObject.Spec.IsAuthenticated() { if apiObject.Spec.IsAuthenticated() {
if err := d.ensureJWTSecret(apiObject.Spec.Authentication.JWTSecretName); err != nil { if err := d.ensureJWTSecret(apiObject.Spec.Authentication.GetJWTSecretName()); err != nil {
return maskAny(err) return maskAny(err)
} }
} }
@ -88,7 +88,7 @@ func (d *Deployment) ensureJWTSecret(secretName string) error {
func (d *Deployment) ensureCACertificateSecret(spec api.TLSSpec) error { func (d *Deployment) ensureCACertificateSecret(spec api.TLSSpec) error {
kubecli := d.deps.KubeCli kubecli := d.deps.KubeCli
ns := d.apiObject.GetNamespace() ns := d.apiObject.GetNamespace()
if _, err := kubecli.CoreV1().Secrets(ns).Get(spec.CASecretName, metav1.GetOptions{}); k8sutil.IsNotFound(err) { if _, err := kubecli.CoreV1().Secrets(ns).Get(spec.GetCASecretName(), metav1.GetOptions{}); k8sutil.IsNotFound(err) {
// Secret not found, create it // Secret not found, create it
owner := d.apiObject.AsOwner() owner := d.apiObject.AsOwner()
deploymentName := d.apiObject.GetName() deploymentName := d.apiObject.GetName()
@ -112,7 +112,7 @@ func (d *Deployment) getJWTSecret(apiObject *api.ArangoDeployment) (string, erro
return "", nil return "", nil
} }
kubecli := d.deps.KubeCli kubecli := d.deps.KubeCli
secretName := apiObject.Spec.Authentication.JWTSecretName secretName := apiObject.Spec.Authentication.GetJWTSecretName()
s, err := k8sutil.GetJWTSecret(kubecli.CoreV1(), secretName, apiObject.GetNamespace()) s, err := k8sutil.GetJWTSecret(kubecli.CoreV1(), secretName, apiObject.GetNamespace())
if err != nil { if err != nil {
d.deps.Log.Debug().Err(err).Str("secret-name", secretName).Msg("Failed to get JWT secret") d.deps.Log.Debug().Err(err).Str("secret-name", secretName).Msg("Failed to get JWT secret")
@ -124,7 +124,7 @@ func (d *Deployment) getJWTSecret(apiObject *api.ArangoDeployment) (string, erro
// getSyncJWTSecret loads the JWT secret used for syncmasters from a Secret configured in apiObject.Spec.Sync.Authentication.JWTSecretName. // getSyncJWTSecret loads the JWT secret used for syncmasters from a Secret configured in apiObject.Spec.Sync.Authentication.JWTSecretName.
func (d *Deployment) getSyncJWTSecret(apiObject *api.ArangoDeployment) (string, error) { func (d *Deployment) getSyncJWTSecret(apiObject *api.ArangoDeployment) (string, error) {
kubecli := d.deps.KubeCli kubecli := d.deps.KubeCli
secretName := apiObject.Spec.Sync.Authentication.JWTSecretName secretName := apiObject.Spec.Sync.Authentication.GetJWTSecretName()
s, err := k8sutil.GetJWTSecret(kubecli.CoreV1(), secretName, apiObject.GetNamespace()) s, err := k8sutil.GetJWTSecret(kubecli.CoreV1(), secretName, apiObject.GetNamespace())
if err != nil { if err != nil {
d.deps.Log.Debug().Err(err).Str("secret-name", secretName).Msg("Failed to get sync JWT secret") d.deps.Log.Debug().Err(err).Str("secret-name", secretName).Msg("Failed to get sync JWT secret")
@ -136,7 +136,7 @@ func (d *Deployment) getSyncJWTSecret(apiObject *api.ArangoDeployment) (string,
// getSyncMonitoringToken loads the token secret used for monitoring sync masters & workers. // getSyncMonitoringToken loads the token secret used for monitoring sync masters & workers.
func (d *Deployment) getSyncMonitoringToken(apiObject *api.ArangoDeployment) (string, error) { func (d *Deployment) getSyncMonitoringToken(apiObject *api.ArangoDeployment) (string, error) {
kubecli := d.deps.KubeCli kubecli := d.deps.KubeCli
secretName := apiObject.Spec.Sync.Monitoring.TokenSecretName secretName := apiObject.Spec.Sync.Monitoring.GetTokenSecretName()
s, err := kubecli.CoreV1().Secrets(apiObject.GetNamespace()).Get(secretName, metav1.GetOptions{}) s, err := kubecli.CoreV1().Secrets(apiObject.GetNamespace()).Get(secretName, metav1.GetOptions{})
if err != nil { if err != nil {
d.deps.Log.Debug().Err(err).Str("secret-name", secretName).Msg("Failed to get monitoring token secret") d.deps.Log.Debug().Err(err).Str("secret-name", secretName).Msg("Failed to get monitoring token secret")

View file

@ -44,8 +44,8 @@ const (
// createCACertificate creates a CA certificate and stores it in a secret with name // createCACertificate creates a CA certificate and stores it in a secret with name
// specified in the given spec. // specified in the given spec.
func createCACertificate(log zerolog.Logger, cli v1.CoreV1Interface, spec api.TLSSpec, deploymentName, namespace string, ownerRef *metav1.OwnerReference) error { func createCACertificate(log zerolog.Logger, cli v1.CoreV1Interface, spec api.TLSSpec, deploymentName, namespace string, ownerRef *metav1.OwnerReference) error {
log = log.With().Str("secret", spec.CASecretName).Logger() log = log.With().Str("secret", spec.GetCASecretName()).Logger()
dnsNames, ipAddresses, emailAddress, err := spec.GetAltNames() dnsNames, ipAddresses, emailAddress, err := spec.GetParsedAltNames()
if err != nil { if err != nil {
log.Debug().Err(err).Msg("Failed to get alternate names") log.Debug().Err(err).Msg("Failed to get alternate names")
return maskAny(err) return maskAny(err)
@ -65,7 +65,7 @@ func createCACertificate(log zerolog.Logger, cli v1.CoreV1Interface, spec api.TL
log.Debug().Err(err).Msg("Failed to create CA certificate") log.Debug().Err(err).Msg("Failed to create CA certificate")
return maskAny(err) return maskAny(err)
} }
if err := k8sutil.CreateCASecret(cli, spec.CASecretName, namespace, cert, priv, ownerRef); err != nil { if err := k8sutil.CreateCASecret(cli, spec.GetCASecretName(), namespace, cert, priv, ownerRef); err != nil {
if k8sutil.IsAlreadyExists(err) { if k8sutil.IsAlreadyExists(err) {
log.Debug().Msg("CA Secret already exists") log.Debug().Msg("CA Secret already exists")
} else { } else {
@ -82,14 +82,14 @@ func createCACertificate(log zerolog.Logger, cli v1.CoreV1Interface, spec api.TL
func createServerCertificate(log zerolog.Logger, cli v1.CoreV1Interface, serverNames []string, spec api.TLSSpec, secretName, namespace string, ownerRef *metav1.OwnerReference) error { func createServerCertificate(log zerolog.Logger, cli v1.CoreV1Interface, serverNames []string, spec api.TLSSpec, secretName, namespace string, ownerRef *metav1.OwnerReference) error {
log = log.With().Str("secret", secretName).Logger() log = log.With().Str("secret", secretName).Logger()
// Load alt names // Load alt names
dnsNames, ipAddresses, emailAddress, err := spec.GetAltNames() dnsNames, ipAddresses, emailAddress, err := spec.GetParsedAltNames()
if err != nil { if err != nil {
log.Debug().Err(err).Msg("Failed to get alternate names") log.Debug().Err(err).Msg("Failed to get alternate names")
return maskAny(err) return maskAny(err)
} }
// Load CA certificate // Load CA certificate
caCert, caKey, err := k8sutil.GetCASecret(cli, spec.CASecretName, namespace) caCert, caKey, err := k8sutil.GetCASecret(cli, spec.GetCASecretName(), namespace)
if err != nil { if err != nil {
log.Debug().Err(err).Msg("Failed to load CA certificate") log.Debug().Err(err).Msg("Failed to load CA certificate")
return maskAny(err) return maskAny(err)
@ -105,7 +105,7 @@ func createServerCertificate(log zerolog.Logger, cli v1.CoreV1Interface, serverN
Hosts: append(append(serverNames, dnsNames...), ipAddresses...), Hosts: append(append(serverNames, dnsNames...), ipAddresses...),
EmailAddresses: emailAddress, EmailAddresses: emailAddress,
ValidFrom: time.Now(), ValidFrom: time.Now(),
ValidFor: spec.TTL, ValidFor: spec.GetTTL(),
IsCA: false, IsCA: false,
ECDSACurve: tlsECDSACurve, ECDSACurve: tlsECDSACurve,
} }

View file

@ -146,7 +146,7 @@ func createArangodClientForDNSName(ctx context.Context, cli corev1.CoreV1Interfa
// Authentication is enabled. // Authentication is enabled.
// Should we skip using it? // Should we skip using it?
if ctx.Value(skipAuthenticationKey{}) == nil { if ctx.Value(skipAuthenticationKey{}) == nil {
s, err := k8sutil.GetJWTSecret(cli, apiObject.Spec.Authentication.JWTSecretName, apiObject.GetNamespace()) s, err := k8sutil.GetJWTSecret(cli, apiObject.Spec.Authentication.GetJWTSecretName(), apiObject.GetNamespace())
if err != nil { if err != nil {
return nil, maskAny(err) return nil, maskAny(err)
} }

77
pkg/util/refs.go Normal file
View file

@ -0,0 +1,77 @@
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//
package util
import "time"
// String returns a reference to a string with given value.
func String(input string) *string {
return &input
}
// StringOrNil returns nil if input is nil, otherwise returns a clone of the given value.
func StringOrNil(input *string) *string {
if input == nil {
return nil
}
return String(*input)
}
// Int returns a reference to an int with given value.
func Int(input int) *int {
return &input
}
// IntOrNil returns nil if input is nil, otherwise returns a clone of the given value.
func IntOrNil(input *int) *int {
if input == nil {
return nil
}
return Int(*input)
}
// Bool returns a reference to a bool with given value.
func Bool(input bool) *bool {
return &input
}
// BoolOrNil returns nil if input is nil, otherwise returns a clone of the given value.
func BoolOrNil(input *bool) *bool {
if input == nil {
return nil
}
return Bool(*input)
}
// Duration returns a reference to a duration with given value.
func Duration(input time.Duration) *time.Duration {
return &input
}
// DurationOrNil returns nil if input is nil, otherwise returns a clone of the given value.
func DurationOrNil(input *time.Duration) *time.Duration {
if input == nil {
return nil
}
return Duration(*input)
}