Skip to content
12 changes: 12 additions & 0 deletions docs/Manual/Deployment/Kubernetes/DeploymentResource.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,18 @@ This setting specifies the name of a kubernetes `Secret` that contains
the license key token used for enterprise images. This value is not used for
the community edition.

### `spec.bootstrap.passwordSecretNames.root: string`

This setting specifies a secret name for the credentials of the root user.

When a deployment is created the operator will setup the root user account
according to the credentials given by the secret. If the secret doesn't exist
the operator creates a secret with a random password.

There are two magic values for the secret name:
- `None` specifies no action. This disables root password randomization. This is the default value. (Thus the root password is empty - not recommended)
- `Auto` specifies automatic name generation, which is `<deploymentname>-root-password`.

### `spec.<group>.count: number`

This setting specifies the number of servers to start for the given group.
Expand Down
136 changes: 136 additions & 0 deletions pkg/apis/deployment/v1alpha/bootstrap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//
// 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
//

package v1alpha

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

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] = PasswordSecretNameAuto
}

// 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)
}
}
4 changes: 4 additions & 0 deletions pkg/apis/deployment/v1alpha/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ const (
ConditionTypeSecretsChanged ConditionType = "SecretsChanged"
// ConditionTypeMemberOfCluster indicates that the member is a known member of the ArangoDB cluster.
ConditionTypeMemberOfCluster ConditionType = "MemberOfCluster"
// ConditionTypeBootstrapCompleted indicates that the initial cluster bootstrap has been completed.
ConditionTypeBootstrapCompleted ConditionType = "BootstrapCompleted"
// ConditionTypeBootstrapSucceded indicates that the initial cluster bootstrap completed successfully.
ConditionTypeBootstrapSucceded ConditionType = "BootstrapSucceded"
// ConditionTypeTerminating indicates that the member is terminating but not yet terminated.
ConditionTypeTerminating ConditionType = "Terminating"
)
Expand Down
7 changes: 7 additions & 0 deletions pkg/apis/deployment/v1alpha/deployment_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ type DeploymentSpec struct {
SyncWorkers ServerGroupSpec `json:"syncworkers"`

Chaos ChaosSpec `json:"chaos"`

Bootstrap BootstrapSpec `json:"bootstrap",omitempty`
}

// Equal compares two DeploymentSpec
Expand Down Expand Up @@ -188,6 +190,7 @@ func (s *DeploymentSpec) SetDefaults(deploymentName string) {
s.SyncMasters.SetDefaults(ServerGroupSyncMasters, s.Sync.IsEnabled(), s.GetMode())
s.SyncWorkers.SetDefaults(ServerGroupSyncWorkers, s.Sync.IsEnabled(), s.GetMode())
s.Chaos.SetDefaults()
s.Bootstrap.SetDefaults(deploymentName)
}

// SetDefaultsFrom fills unspecified fields with a value from given source spec.
Expand Down Expand Up @@ -226,6 +229,7 @@ func (s *DeploymentSpec) SetDefaultsFrom(source DeploymentSpec) {
s.SyncMasters.SetDefaultsFrom(source.SyncMasters)
s.SyncWorkers.SetDefaultsFrom(source.SyncWorkers)
s.Chaos.SetDefaultsFrom(source.Chaos)
s.Bootstrap.SetDefaultsFrom(source.Bootstrap)
}

// Validate the specification.
Expand Down Expand Up @@ -285,6 +289,9 @@ func (s *DeploymentSpec) Validate() error {
if err := s.License.Validate(); err != nil {
return maskAny(errors.Wrap(err, "spec.licenseKey"))
}
if err := s.Bootstrap.Validate(); err != nil {
return maskAny(err)
}
return nil
}

Expand Down
46 changes: 46 additions & 0 deletions pkg/apis/deployment/v1alpha/zz_generated.deepcopy.go

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

Loading