Skip to content
Open
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
5 changes: 3 additions & 2 deletions cmd/overlock/environment/environment.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package environment

type Cmd struct {
Create createCmd `cmd:"" help:"Create an Environment"`
Delete deleteCmd `cmd:"" help:"Delete an Environment"`
Create createCmd `cmd:"" help:"Create an Environment"`
Install installCmd `cmd:"" help:"Install engine into an existing cluster without creating one"`
Delete deleteCmd `cmd:"" help:"Delete an Environment"`
// Copy copyCmd `cmd:"" help:"Copy an Environment to another destination context"`
// List listCmd `cmd:"" help:"List of Environments"`
Stop stopCmd `cmd:"" help:"Stop an Environment"`
Expand Down
84 changes: 84 additions & 0 deletions cmd/overlock/environment/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package environment

import (
"context"
"fmt"
"strings"

"go.uber.org/zap"

"github.com/web-seven/overlock/pkg/environment"
)

type installCmd struct {
Name string `arg:"" required:"" help:"Name of environment."`
Context string `required:"" short:"c" help:"Kubernetes context to install the engine into."`
NodeLabel []string `optional:"" help:"Node label in key=value format used as a nodeSelector to schedule the engine onto selected node(s). Can be specified multiple times."`
NodeTaint []string `optional:"" help:"Node taint in key=value:Effect format; a matching toleration is added so the engine can be placed on tainted node(s). Can be specified multiple times."`
Providers []string `optional:"" help:"List of providers to apply to the environment."`
Configurations []string `optional:"" help:"List of configurations to apply to the environment."`
Functions []string `optional:"" help:"List of functions to apply to the environment."`
}

func (c *installCmd) Run(ctx context.Context, logger *zap.SugaredLogger) error {
nodeSelector, err := parseNodeLabels(c.NodeLabel)
if err != nil {
return err
}

tolerations, err := parseNodeTaints(c.NodeTaint)
if err != nil {
return err
}

return environment.
New("", c.Name).
WithContext(c.Context).
WithProviders(c.Providers).
WithConfigurations(c.Configurations).
WithFunctions(c.Functions).
WithNodeSelector(nodeSelector).
WithTolerations(tolerations).
Install(ctx, logger)
}

// parseNodeLabels parses "key=value" strings into a nodeSelector map.
func parseNodeLabels(labels []string) (map[string]interface{}, error) {
if len(labels) == 0 {
return nil, nil
}
nodeSelector := make(map[string]interface{}, len(labels))
for _, label := range labels {
key, value, ok := strings.Cut(label, "=")
if !ok || key == "" || value == "" {
return nil, fmt.Errorf("invalid --node-label %q, expected key=value", label)
}
nodeSelector[key] = value
}
return nodeSelector, nil
}

// parseNodeTaints parses "key=value:Effect" strings into tolerations matching those taints.
func parseNodeTaints(taints []string) ([]interface{}, error) {
if len(taints) == 0 {
return nil, nil
}
tolerations := make([]interface{}, 0, len(taints))
for _, taint := range taints {
keyValue, effect, ok := strings.Cut(taint, ":")
if !ok || effect == "" {
return nil, fmt.Errorf("invalid --node-taint %q, expected key=value:Effect", taint)
}
key, value, ok := strings.Cut(keyValue, "=")
if !ok || key == "" || value == "" {
return nil, fmt.Errorf("invalid --node-taint %q, expected key=value:Effect", taint)
}
tolerations = append(tolerations, map[string]interface{}{
"key": key,
"operator": "Equal",
"value": value,
"effect": effect,
})
}
return tolerations, nil
}
42 changes: 37 additions & 5 deletions pkg/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ type Environment struct {
adminServiceAccountName string
skipNodeSetup bool
maxReconcileRate int
nodeSelector map[string]interface{}
tolerations []interface{}
}

// New Environment entity
Expand Down Expand Up @@ -99,6 +101,20 @@ func (e *Environment) Create(ctx context.Context, logger *zap.SugaredLogger) err
return nil
}

// Install installs the engine into an existing cluster, selected via context,
// without provisioning a new cluster.
func (e *Environment) Install(ctx context.Context, logger *zap.SugaredLogger) error {
if e.context == "" {
return fmt.Errorf("--context is required to install the engine into an existing cluster")
}

if err := e.Setup(ctx, logger); err != nil {
return err
}
logger.Info("Engine installed successfully.")
return nil
}

// Upgrade environment with options or new features
func (e *Environment) Upgrade(ctx context.Context, logger *zap.SugaredLogger) error {
var err error
Expand Down Expand Up @@ -181,9 +197,15 @@ func (e *Environment) Setup(ctx context.Context, logger *zap.SugaredLogger) erro
}
}

// Build engine scope params for chart installation (nil for non-k3s-docker).
// Build engine scope params for chart installation. A user-supplied
// nodeSelector (e.g. via `environment install --node-label/--node-taint`)
// takes precedence over the automatic k3s-docker engine scope.
var nodeSelector map[string]interface{}
if e.engine == "k3s-docker" {
var tolerations []interface{}
if len(e.nodeSelector) > 0 {
nodeSelector = e.nodeSelector
tolerations = e.tolerations
} else if e.engine == "k3s-docker" {
nodeSelector, _ = chart.EngineScopeSelector()
}

Expand All @@ -205,16 +227,16 @@ func (e *Environment) Setup(ctx context.Context, logger *zap.SugaredLogger) erro
for _, ch := range charts {
var scopeParams map[string]any
if nodeSelector != nil {
scopeParams = ch.ScopeParams(nodeSelector, []interface{}{})
scopeParams = ch.ScopeParams(nodeSelector, tolerations)
}
if err := ch.Install(ctx, configClient, scopeParams, logger); err != nil {
return fmt.Errorf("failed to install chart: %w", err)
}
}

// Patch DeploymentRuntimeConfig for provider/function scheduling.
if e.engine == "k3s-docker" {
if err := chart.PatchDefaultRuntimeConfig(configClient, nodeSelector, []interface{}{}, logger); err != nil {
if nodeSelector != nil {
if err := chart.PatchDefaultRuntimeConfig(configClient, nodeSelector, tolerations, logger); err != nil {
logger.Warnf("Failed to patch DeploymentRuntimeConfig: %v", err)
}
}
Expand Down Expand Up @@ -465,6 +487,16 @@ func (e *Environment) WithMaxReconcileRate(rate int) *Environment {
return e
}

func (e *Environment) WithNodeSelector(nodeSelector map[string]interface{}) *Environment {
e.nodeSelector = nodeSelector
return e
}

func (e *Environment) WithTolerations(tolerations []interface{}) *Environment {
e.tolerations = tolerations
return e
}

func SwitchContext(name string) (err error) {
newConfig := clientcmd.GetConfigFromFileOrDie(clientcmd.RecommendedHomeFile)
newConfig.CurrentContext = name
Expand Down
Loading