Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Change Log

## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A)
- Add v2alpha1 API for ArangoDeployment and ArangoDeploymentReplication

## [1.1.2](https://github.com/arangodb/kube-arangodb/tree/1.1.2) (2020-11-11)
- Fix Bootstrap phase and move it under Plan
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ update-generated:
"all" \
"github.com/arangodb/kube-arangodb/pkg/generated" \
"github.com/arangodb/kube-arangodb/pkg/apis" \
"deployment:v1 replication:v1 storage:v1alpha backup:v1" \
"deployment:v1 replication:v1 storage:v1alpha backup:v1 deployment:v2alpha1 replication:v2alpha1" \
--go-header-file "./tools/codegen/boilerplate.go.txt" \
$(VERIFYARGS)
GOPATH=$(GOBUILDDIR) $(VENDORDIR)/k8s.io/code-generator/generate-groups.sh \
Expand Down
93 changes: 93 additions & 0 deletions pkg/apis/deployment/v2alpha1/authentication_spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// DISCLAIMER
//
// Copyright 2020 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 v2alpha1

import (
"github.com/pkg/errors"

"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
)

// AuthenticationSpec holds authentication specific configuration settings
type AuthenticationSpec struct {
JWTSecretName *string `json:"jwtSecretName,omitempty"`
}

const (
// JWTSecretNameDisabled is the value of JWTSecretName to use for disabling authentication.
JWTSecretNameDisabled = "None"
)

// GetJWTSecretName returns the value of jwtSecretName.
func (s AuthenticationSpec) GetJWTSecretName() string {
return util.StringOrDefault(s.JWTSecretName)
}

// IsAuthenticated returns true if authentication is enabled.
// Returns false other (when JWTSecretName == "None").
func (s AuthenticationSpec) IsAuthenticated() bool {
return s.GetJWTSecretName() != JWTSecretNameDisabled
}

// Validate the given spec
func (s AuthenticationSpec) Validate(required bool) error {
if required && !s.IsAuthenticated() {
return maskAny(errors.Wrap(ValidationError, "JWT secret is required"))
}
if s.IsAuthenticated() {
if err := k8sutil.ValidateResourceName(s.GetJWTSecretName()); err != nil {
return maskAny(err)
}
}
return nil
}

// SetDefaults fills in missing defaults
func (s *AuthenticationSpec) SetDefaults(defaultJWTSecretName string) {
if s.GetJWTSecretName() == "" {
// 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.NewString(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.NewStringOrNil(source.JWTSecretName)
}
}

// 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.
// Field names are relative to given field prefix.
func (s AuthenticationSpec) ResetImmutableFields(fieldPrefix string, target *AuthenticationSpec) []string {
var resetFields []string
if s.IsAuthenticated() != target.IsAuthenticated() {
// Note: You can change the name, but not from empty to non-empty (or reverse).
target.JWTSecretName = util.NewStringOrNil(s.JWTSecretName)
resetFields = append(resetFields, fieldPrefix+".jwtSecretName")
}
return resetFields
}
105 changes: 105 additions & 0 deletions pkg/apis/deployment/v2alpha1/authentication_spec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//
// DISCLAIMER
//
// Copyright 2020 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 v2alpha1

import (
"testing"

"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/stretchr/testify/assert"
)

func TestAuthenticationSpecValidate(t *testing.T) {
// Valid
assert.Nil(t, AuthenticationSpec{JWTSecretName: util.NewString("None")}.Validate(false))
assert.Nil(t, AuthenticationSpec{JWTSecretName: util.NewString("foo")}.Validate(false))
assert.Nil(t, AuthenticationSpec{JWTSecretName: util.NewString("foo")}.Validate(true))

// Not valid
assert.Error(t, AuthenticationSpec{JWTSecretName: util.NewString("Foo")}.Validate(false))
}

func TestAuthenticationSpecIsAuthenticated(t *testing.T) {
assert.False(t, AuthenticationSpec{JWTSecretName: util.NewString("None")}.IsAuthenticated())
assert.True(t, AuthenticationSpec{JWTSecretName: util.NewString("foo")}.IsAuthenticated())
assert.True(t, AuthenticationSpec{JWTSecretName: util.NewString("")}.IsAuthenticated())
}

func TestAuthenticationSpecSetDefaults(t *testing.T) {
def := func(spec AuthenticationSpec) AuthenticationSpec {
spec.SetDefaults("test-jwt")
return spec
}

assert.Equal(t, "test-jwt", def(AuthenticationSpec{}).GetJWTSecretName())
assert.Equal(t, "foo", def(AuthenticationSpec{JWTSecretName: util.NewString("foo")}).GetJWTSecretName())
}

func TestAuthenticationSpecResetImmutableFields(t *testing.T) {
tests := []struct {
Original AuthenticationSpec
Target AuthenticationSpec
Expected AuthenticationSpec
Result []string
}{
// Valid "changes"
{
AuthenticationSpec{JWTSecretName: util.NewString("None")},
AuthenticationSpec{JWTSecretName: util.NewString("None")},
AuthenticationSpec{JWTSecretName: util.NewString("None")},
nil,
},
{
AuthenticationSpec{JWTSecretName: util.NewString("foo")},
AuthenticationSpec{JWTSecretName: util.NewString("foo")},
AuthenticationSpec{JWTSecretName: util.NewString("foo")},
nil,
},
{
AuthenticationSpec{JWTSecretName: util.NewString("foo")},
AuthenticationSpec{JWTSecretName: util.NewString("foo2")},
AuthenticationSpec{JWTSecretName: util.NewString("foo2")},
nil,
},

// Invalid changes
{
AuthenticationSpec{JWTSecretName: util.NewString("foo")},
AuthenticationSpec{JWTSecretName: util.NewString("None")},
AuthenticationSpec{JWTSecretName: util.NewString("foo")},
[]string{"test.jwtSecretName"},
},
{
AuthenticationSpec{JWTSecretName: util.NewString("None")},
AuthenticationSpec{JWTSecretName: util.NewString("foo")},
AuthenticationSpec{JWTSecretName: util.NewString("None")},
[]string{"test.jwtSecretName"},
},
}

for _, test := range tests {
result := test.Original.ResetImmutableFields("test", &test.Target)
assert.Equal(t, test.Result, result)
assert.Equal(t, test.Expected, test.Target)
}
}
140 changes: 140 additions & 0 deletions pkg/apis/deployment/v2alpha1/bootstrap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//
// DISCLAIMER
//
// Copyright 2020 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
//

package v2alpha1

import (
"fmt"

"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
)

const (
// UserNameRoot root user name
UserNameRoot = "root"
)

// PasswordSecretName contains user password secret name
type PasswordSecretName string

func (p PasswordSecretName) Get() string {
return string(p)
}

const (
// PasswordSecretNameNone is magic value for no action
PasswordSecretNameNone PasswordSecretName = "None"
// PasswordSecretNameAuto is magic value for autogenerate name
PasswordSecretNameAuto PasswordSecretName = "Auto"
)

// PasswordSecretNameList is a map from username to secretnames
type PasswordSecretNameList map[string]PasswordSecretName

// BootstrapSpec contains information for cluster bootstrapping
type BootstrapSpec struct {
// PasswordSecretNames contains a map of username to password-secret-name
PasswordSecretNames PasswordSecretNameList `json:"passwordSecretNames,omitempty"`
}

// IsNone returns true if p is None or p is empty
func (p PasswordSecretName) IsNone() bool {
return p == PasswordSecretNameNone || p == ""
}

// IsAuto returns true if p is Auto
func (p PasswordSecretName) IsAuto() bool {
return p == PasswordSecretNameAuto
}

// GetSecretName returns the secret name given by the specs. Or None if not set.
func (s PasswordSecretNameList) GetSecretName(user string) PasswordSecretName {
if s != nil {
if secretname, ok := s[user]; ok {
return secretname
}
}
return PasswordSecretNameNone
}

// getSecretNameForUserPassword returns the default secret name for the given user
func getSecretNameForUserPassword(deploymentname, username string) PasswordSecretName {
return PasswordSecretName(k8sutil.FixupResourceName(deploymentname + "-" + username + "-password"))
}

// Validate the specification.
func (b *BootstrapSpec) Validate() error {
for username, secretname := range b.PasswordSecretNames {
// Remove this restriction as soon as we can bootstrap databases
if username != UserNameRoot {
return fmt.Errorf("only username `root` allowed in passwordSecretNames")
}

if secretname.IsNone() {
if username != UserNameRoot {
return fmt.Errorf("magic value None not allowed for %s", username)
}
} else {
if err := k8sutil.ValidateResourceName(string(secretname)); err != nil {
return maskAny(err)
}
}
}

return nil
}

// SetDefaults fills in default values when a field is not specified.
func (b *BootstrapSpec) SetDefaults(deploymentname string) {
if b.PasswordSecretNames == nil {
b.PasswordSecretNames = make(map[string]PasswordSecretName)
}

// If root is not set init with Auto
if _, ok := b.PasswordSecretNames[UserNameRoot]; !ok {
b.PasswordSecretNames[UserNameRoot] = PasswordSecretNameNone
}

// Replace Auto with generated secret name
for user, secretname := range b.PasswordSecretNames {
if secretname.IsAuto() {
b.PasswordSecretNames[user] = getSecretNameForUserPassword(deploymentname, user)
}
}
}

// NewPasswordSecretNameListOrNil returns nil if input is nil, otherwise returns a clone of the given value.
func NewPasswordSecretNameListOrNil(list PasswordSecretNameList) PasswordSecretNameList {
if list == nil {
return nil
}
var newList = make(PasswordSecretNameList)
for k, v := range list {
newList[k] = v
}
return newList
}

// SetDefaultsFrom fills unspecified fields with a value from given source spec.
func (b *BootstrapSpec) SetDefaultsFrom(source BootstrapSpec) {
if b.PasswordSecretNames == nil {
b.PasswordSecretNames = NewPasswordSecretNameListOrNil(source.PasswordSecretNames)
}
}
Loading