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
1 change: 1 addition & 0 deletions cmd/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func init() {
kubernetesCreateCmd.Flags().StringVarP(&cniPlugin, "cni-plugin", "p", "flannel", "optional, possible options: flannel,cilium.")
kubernetesCreateCmd.Flags().StringVarP(&clusterType, "cluster-type", "", "k3s", "optional, possible options: k3s,talos.")
kubernetesCreateCmd.Flags().StringVar(&volumeType, "volume-type", "", "optional, volume-type name as returned by 'civo volumetypes ls'")
kubernetesCreateCmd.Flags().BoolVar(&logsCollectorEnabled, "logs-collector-enabled", true, "optional, disabling this will prevent you from seeing logs in the Civo platform dashboard, but it will save node resources. Disabled by default on small and xsmall standard nodes")

kubernetesRenameCmd.Flags().StringVarP(&kubernetesNewName, "name", "n", "", "the new name for the cluster.")

Expand Down
11 changes: 11 additions & 0 deletions cmd/kubernetes/kubernetes_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var numTargetNodes int
var rulesFirewall string
var waitKubernetes, saveConfigKubernetes, mergeConfigKubernetes, switchConfigKubernetes, createFirewall bool
var kubernetesVersion, targetNodesSize, clusterName, clusterType, applications, removeapplications, networkID, existingFirewall, cniPlugin, volumeType string
var logsCollectorEnabled bool
var kubernetesCluster *civogo.KubernetesCluster

var kubernetesCreateCmdExample = `civo kubernetes create CLUSTER_NAME [flags]
Expand Down Expand Up @@ -140,6 +141,16 @@ var kubernetesCreateCmd = &cobra.Command{
CNIPlugin: cni,
}

logsCollectorEnabledValue, logsCollectorMessage := utility.ResolveLogsCollectorEnabled(
targetNodesSize,
cmd.Flags().Changed("logs-collector-enabled"),
logsCollectorEnabled,
)
configKubernetes.LogsCollectorEnabled = logsCollectorEnabledValue
if logsCollectorMessage != "" {
utility.Info(logsCollectorMessage)
}

if rulesFirewall != "default" && !createFirewall {
utility.Error("You can't use --firewall-rules without --create-firewall flag")
os.Exit(1)
Expand Down
53 changes: 53 additions & 0 deletions utility/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,56 @@ func SizeType(size string) string {
return "Instance"
}
}

// IsStandardSmallOrXSmallKubeSize returns true when the given node size is a
// "standard" tier (the "g4s" family) small or xsmall Kubernetes node, e.g.
// "g4s.kube.small" or "g4s.kube.xsmall". This works dynamically off the size
// name so it doesn't need updating if new standard node sizes are added -
// only small/xsmall standard nodes qualify, matching the resource-saving
// default requested for those specific sizes.
func IsStandardSmallOrXSmallKubeSize(size string) bool {
if !strings.HasPrefix(size, "g4s.") {
return false
}

parts := strings.Split(size, ".")
if len(parts) == 0 {
return false
}

switch parts[len(parts)-1] {
case "small", "xsmall":
return true
default:
return false
}
}

// LogsCollectorDisabledMessage is shown to the user when the logs collector
// is automatically disabled on a standard small/xsmall node, so they know why
// and how to override it.
const LogsCollectorDisabledMessage = "Logs collector not installed by default to save resource on small and xsmall standard nodes. If you wish to enable logs collector create the cluster with LogsCollectorEnable set to true"

// ResolveLogsCollectorEnabled works out the value that should be sent to the
// API for a cluster's LogsCollectorEnabled setting, and an informational
// message to display to the user (empty if there's nothing to say).
//
// - If the user explicitly passed --logs-collector-enabled, their choice is
// always respected, regardless of node size.
// - Otherwise, the logs collector is disabled by default on standard small
// and xsmall nodes to save node resources.
// - Otherwise, nil is returned so the API's own default applies (currently
// enabled).
func ResolveLogsCollectorEnabled(size string, explicitlySet bool, value bool) (enabled *bool, message string) {
if explicitlySet {
v := value
return &v, ""
}

if IsStandardSmallOrXSmallKubeSize(size) {
v := false
return &v, LogsCollectorDisabledMessage
}

return nil, ""
}
106 changes: 106 additions & 0 deletions utility/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,109 @@ func TestIsAppCompatibleWithClusterType(t *testing.T) {
})
}
}

func TestIsStandardSmallOrXSmallKubeSize(t *testing.T) {
tests := []struct {
name string
size string
want bool
}{
{name: "standard xsmall", size: "g4s.kube.xsmall", want: true},
{name: "standard small", size: "g4s.kube.small", want: true},
{name: "standard medium is not small/xsmall", size: "g4s.kube.medium", want: false},
{name: "standard large is not small/xsmall", size: "g4s.kube.large", want: false},
{name: "performance small is not standard tier", size: "g4p.kube.small", want: false},
{name: "ram-optimized small is not standard tier", size: "g4m.kube.small", want: false},
{name: "cpu-optimized small is not standard tier", size: "g4c.kube.small", want: false},
{name: "legacy non-kube instance size", size: "g3.xsmall", want: false},
{name: "empty size", size: "", want: false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsStandardSmallOrXSmallKubeSize(tt.size); got != tt.want {
t.Errorf("IsStandardSmallOrXSmallKubeSize(%q) = %v, want %v", tt.size, got, tt.want)
}
})
}
}

func TestResolveLogsCollectorEnabled(t *testing.T) {
tests := []struct {
name string
size string
explicitlySet bool
value bool
wantEnabled *bool
wantMessage string
}{
{
name: "explicit true is respected even on xsmall",
size: "g4s.kube.xsmall",
explicitlySet: true,
value: true,
wantEnabled: boolPtr(true),
wantMessage: "",
},
{
name: "explicit false is respected even on a large node",
size: "g4s.kube.large",
explicitlySet: true,
value: false,
wantEnabled: boolPtr(false),
wantMessage: "",
},
{
name: "auto-disabled on standard small when not explicit",
size: "g4s.kube.small",
explicitlySet: false,
value: true,
wantEnabled: boolPtr(false),
wantMessage: LogsCollectorDisabledMessage,
},
{
name: "auto-disabled on standard xsmall when not explicit",
size: "g4s.kube.xsmall",
explicitlySet: false,
value: true,
wantEnabled: boolPtr(false),
wantMessage: LogsCollectorDisabledMessage,
},
{
name: "left to API default on standard medium when not explicit",
size: "g4s.kube.medium",
explicitlySet: false,
value: true,
wantEnabled: nil,
wantMessage: "",
},
{
name: "performance-tier small is not auto-disabled",
size: "g4p.kube.small",
explicitlySet: false,
value: true,
wantEnabled: nil,
wantMessage: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotEnabled, gotMessage := ResolveLogsCollectorEnabled(tt.size, tt.explicitlySet, tt.value)

if (gotEnabled == nil) != (tt.wantEnabled == nil) {
t.Fatalf("ResolveLogsCollectorEnabled(%q, %v, %v) enabled = %v, want %v", tt.size, tt.explicitlySet, tt.value, gotEnabled, tt.wantEnabled)
}
if gotEnabled != nil && *gotEnabled != *tt.wantEnabled {
t.Errorf("ResolveLogsCollectorEnabled(%q, %v, %v) enabled = %v, want %v", tt.size, tt.explicitlySet, tt.value, *gotEnabled, *tt.wantEnabled)
}
if gotMessage != tt.wantMessage {
t.Errorf("ResolveLogsCollectorEnabled(%q, %v, %v) message = %q, want %q", tt.size, tt.explicitlySet, tt.value, gotMessage, tt.wantMessage)
}
})
}
}

func boolPtr(b bool) *bool {
return &b
}
Loading