diff --git a/docs/deployment/schedulers/k3s.md b/docs/deployment/schedulers/k3s.md index 457ade4d4..207562fa6 100644 --- a/docs/deployment/schedulers/k3s.md +++ b/docs/deployment/schedulers/k3s.md @@ -5,8 +5,9 @@ ``` scheduler-k3s:annotations:set () [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE>, Set or clear an annotation for a given app/process-type/resource-type combination +scheduler-k3s:annotations:report [|--global] [--format stdout|json] [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE] # Displays a scheduler-k3s annotations report for one or more apps scheduler-k3s:autoscaling-auth:set [<--metadata key=value>...], Set or clear a scheduler-k3s autoscaling keda trigger authentication resource for an app -scheduler-k3s:autoscaling-auth:report [--format stdout|json] [--include-metadata] # Displays a scheduler-k3s autoscaling auth report for an app +scheduler-k3s:autoscaling-auth:report [|--global] [--format stdout|json] [--include-metadata] # Displays a scheduler-k3s autoscaling auth report for one or more apps scheduler-k3s:charts:report [] [--format stdout|json] # Displays a scheduler-k3s chart override report scheduler-k3s:charts:set () # Set or clear a chart-specific helm value scheduler-k3s:cluster:add [ssh://user@host:port] # Adds a server node to a Dokku-managed cluster @@ -15,6 +16,7 @@ scheduler-k3s:cluster:remove [node-id] # Removes client node to a D scheduler-k3s:ensure-charts # Ensures the k3s charts are installed scheduler-k3s:initialize # Initializes a cluster scheduler-k3s:labels:set () [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE> # Set or clear a label for a given app/process-type/resource-type combination +scheduler-k3s:labels:report [|--global] [--format stdout|json] [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE] # Displays a scheduler-k3s labels report for one or more apps scheduler-k3s:profiles:add [--role ROLE] [--insecure-allow-unknown-hosts] [--taint-scheduling] [--kubelet-args KUBELET_ARGS] Adds a node profile to the k3s cluster scheduler-k3s:profiles:list [--format json|stdout] # Lists all node profiles in the k3s cluster scheduler-k3s:profiles:remove # Removes a node profile from the k3s cluster @@ -387,6 +389,35 @@ dokku scheduler-k3s:annotations:set node-js-app annotation.key --resource-type d A `ps:restart` is required after removing annotations in order to remove them from running resources. +#### Displaying annotations + +Configured annotations can be inspected with the `scheduler-k3s:annotations:report` command. Without arguments, it iterates every app and prints all annotations. Passing an app name (or `--global`) scopes the report: + +```shell +dokku scheduler-k3s:annotations:report +dokku scheduler-k3s:annotations:report node-js-app +dokku scheduler-k3s:annotations:report --global +``` + +`--process-type` and `--resource-type` flags narrow the output to a specific scope, matching the flags accepted by `scheduler-k3s:annotations:set`: + +```shell +dokku scheduler-k3s:annotations:report node-js-app --resource-type deployment +dokku scheduler-k3s:annotations:report node-js-app --process-type web --resource-type deployment +``` + +JSON output emits flat keys of the form `{process_type}.{resource_type}.{annotation_key}`. The literal `--global` process type is rendered as `global` to keep keys free of leading dashes: + +```shell +dokku scheduler-k3s:annotations:report node-js-app --format json +``` + +A single value can also be read directly with a flag of the form `--scheduler-k3s-annotations.{process_type}.{resource_type}.{annotation_key}`: + +```shell +dokku scheduler-k3s:annotations:report node-js-app --scheduler-k3s-annotations.global.deployment.annotation.key +``` + #### Setting Labels Dokku injects certain labels into each created resource by default, but it may be necessary to inject others for tighter integration with third-party tools. The `scheduler-k3s:labels:set` command can be used to perform this task. The command takes an app name and a required `--resource-type` flag. @@ -428,6 +459,20 @@ dokku scheduler-k3s:labels:set node-js-app label.key --resource-type deployment A `ps:restart` is required after removing labels in order to remove them from running resources. +#### Displaying labels + +Configured labels can be inspected with the `scheduler-k3s:labels:report` command. The surface mirrors `scheduler-k3s:annotations:report`: + +```shell +dokku scheduler-k3s:labels:report +dokku scheduler-k3s:labels:report node-js-app +dokku scheduler-k3s:labels:report --global +dokku scheduler-k3s:labels:report node-js-app --resource-type deployment +dokku scheduler-k3s:labels:report node-js-app --process-type web --resource-type deployment +dokku scheduler-k3s:labels:report node-js-app --format json +dokku scheduler-k3s:labels:report node-js-app --scheduler-k3s-labels.global.deployment.label.key +``` + ### Autoscaling #### Workload Autoscaling @@ -566,10 +611,12 @@ dokku scheduler-k3s:autoscaling-auth:set $APP $TRIGGER_TYPE ##### Displaying an Authentication Resource report -To see a list of authentication resources managed by Dokku, run the `scheduler-k3s:autoscaling-auth:report` command. +To see a list of authentication resources managed by Dokku, run the `scheduler-k3s:autoscaling-auth:report` command. Without arguments, the report iterates every app; passing an app name (or `--global`) scopes the report: ```shell +dokku scheduler-k3s:autoscaling-auth:report dokku scheduler-k3s:autoscaling-auth:report node-js-app +dokku scheduler-k3s:autoscaling-auth:report --global ``` ``` diff --git a/plugins/scheduler-k3s/Makefile b/plugins/scheduler-k3s/Makefile index c34a10537..eb25f6ca1 100644 --- a/plugins/scheduler-k3s/Makefile +++ b/plugins/scheduler-k3s/Makefile @@ -1,4 +1,4 @@ -SUBCOMMANDS = subcommands/annotations:set subcommands/autoscaling-auth:set subcommands/autoscaling-auth:report subcommands/charts:report subcommands/charts:set subcommands/cluster:add subcommands/cluster:list subcommands/cluster:remove subcommands/ensure-charts subcommands/initialize subcommands/labels:set subcommands/profiles:add subcommands/profiles:list subcommands/profiles:remove subcommands/report subcommands/set subcommands/show-kubeconfig subcommands/uninstall +SUBCOMMANDS = subcommands/annotations:set subcommands/annotations:report subcommands/autoscaling-auth:set subcommands/autoscaling-auth:report subcommands/charts:report subcommands/charts:set subcommands/cluster:add subcommands/cluster:list subcommands/cluster:remove subcommands/ensure-charts subcommands/initialize subcommands/labels:set subcommands/labels:report subcommands/profiles:add subcommands/profiles:list subcommands/profiles:remove subcommands/report subcommands/set subcommands/show-kubeconfig subcommands/uninstall TRIGGERS = triggers/core-post-deploy triggers/core-post-extract triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-certs-update triggers/post-certs-remove triggers/post-create triggers/post-delete triggers/report triggers/scheduler-app-status triggers/scheduler-deploy triggers/scheduler-enter triggers/scheduler-is-deployed triggers/scheduler-logs triggers/scheduler-proxy-config triggers/scheduler-proxy-logs triggers/scheduler-post-delete triggers/scheduler-run triggers/scheduler-run-list triggers/scheduler-stop triggers/scheduler-cron-write triggers/storage-create triggers/storage-destroy triggers/storage-status triggers/scheduler-storage-exec BUILD = commands subcommands triggers PLUGIN_NAME = scheduler-k3s diff --git a/plugins/scheduler-k3s/report.go b/plugins/scheduler-k3s/report.go index 5bb5e85b5..31143d668 100644 --- a/plugins/scheduler-k3s/report.go +++ b/plugins/scheduler-k3s/report.go @@ -1,12 +1,24 @@ package scheduler_k3s import ( + "encoding/json" "fmt" + "sort" "strings" "github.com/dokku/dokku/plugins/common" ) +const ( + annotationsReportType = "scheduler-k3s-annotations" + labelsReportType = "scheduler-k3s-labels" + + // reportProcessTypeGlobal is the rendered form of GlobalProcessType in annotation + // and label report keys (both JSON and single-flag). Real process types cannot + // collide because they cannot contain "--". + reportProcessTypeGlobal = "global" +) + // tokenMask is shown in place of the raw token value in default stdout output. const tokenMask = "*******" @@ -161,6 +173,304 @@ func ReportAutoscalingAuthSingleApp(appName string, format string, includeMetada }) } +// ExtractReportInfoFlag scans arguments for a single info flag whose name starts with +// the given prefix (e.g. "--scheduler-k3s-annotations.") and returns it separately from +// the remaining passthrough arguments. Returns an error if more than one matching flag +// is present. Matching args are removed from the passthrough list so the caller's +// flag.FlagSet sees only its own flags and positional arguments. +func ExtractReportInfoFlag(prefix string, arguments []string) ([]string, string, error) { + passthrough := []string{} + infoFlag := "" + for _, argument := range arguments { + if strings.HasPrefix(argument, prefix) { + if infoFlag != "" { + return nil, "", fmt.Errorf("only a single info flag may be specified") + } + infoFlag = argument + continue + } + passthrough = append(passthrough, argument) + } + return passthrough, infoFlag, nil +} + +// annotationsLabelsReportEntry is a parsed (processType, resourceType, key) triple +// produced when scanning the property store for annotations or labels. +type annotationsLabelsReportEntry struct { + ProcessType string + ResourceType string + Key string + Value string +} + +// ReportAnnotationsSingleApp displays the scheduler-k3s annotations report for one app. +func ReportAnnotationsSingleApp(appName string, format string, processType string, resourceType string, infoFlag string) error { + if appName != "--global" { + if err := common.VerifyAppName(appName); err != nil { + return err + } + } + + entries, err := collectAnnotationsEntries(appName, processType, resourceType) + if err != nil { + return err + } + + return renderAnnotationsLabelsReport(annotationsLabelsRenderInput{ + AppName: appName, + ReportType: annotationsReportType, + Heading: "annotations", + RowLabel: "Annotation", + Entries: entries, + Format: format, + InfoFlag: infoFlag, + }) +} + +// ReportLabelsSingleApp displays the scheduler-k3s labels report for one app. +func ReportLabelsSingleApp(appName string, format string, processType string, resourceType string, infoFlag string) error { + if appName != "--global" { + if err := common.VerifyAppName(appName); err != nil { + return err + } + } + + entries, err := collectLabelsEntries(appName, processType, resourceType) + if err != nil { + return err + } + + return renderAnnotationsLabelsReport(annotationsLabelsRenderInput{ + AppName: appName, + ReportType: labelsReportType, + Heading: "labels", + RowLabel: "Label", + Entries: entries, + Format: format, + InfoFlag: infoFlag, + }) +} + +// collectAnnotationsEntries scans the property store for annotation entries on appName, +// optionally filtered by processType/resourceType. +func collectAnnotationsEntries(appName string, processType string, resourceType string) ([]annotationsLabelsReportEntry, error) { + properties, err := common.PropertyGetAllByPrefix("scheduler-k3s", appName, "") + if err != nil { + return nil, fmt.Errorf("Unable to get property list: %w", err) + } + + knownResourceTypes := map[string]bool{} + for _, rt := range AnnotationResourceTypes { + knownResourceTypes[rt] = true + } + + entries := []annotationsLabelsReportEntry{} + for propertyName := range properties { + if isReservedAnnotationProperty(propertyName) { + continue + } + + dot := strings.LastIndex(propertyName, ".") + if dot <= 0 || dot == len(propertyName)-1 { + continue + } + + propProcessType := propertyName[:dot] + propResourceType := propertyName[dot+1:] + if !knownResourceTypes[propResourceType] { + continue + } + + if processType != "" && propProcessType != processType { + continue + } + if resourceType != "" && propResourceType != resourceType { + continue + } + + annotationMap, err := getAnnotation(appName, propProcessType, propResourceType) + if err != nil { + return nil, fmt.Errorf("Unable to read annotation %s: %w", propertyName, err) + } + + for key, value := range annotationMap { + entries = append(entries, annotationsLabelsReportEntry{ + ProcessType: propProcessType, + ResourceType: propResourceType, + Key: key, + Value: value, + }) + } + } + + return entries, nil +} + +// collectLabelsEntries scans the property store for label entries on appName, +// optionally filtered by processType/resourceType. +func collectLabelsEntries(appName string, processType string, resourceType string) ([]annotationsLabelsReportEntry, error) { + properties, err := common.PropertyGetAllByPrefix("scheduler-k3s", appName, "labels.") + if err != nil { + return nil, fmt.Errorf("Unable to get property list: %w", err) + } + + knownResourceTypes := map[string]bool{} + for _, rt := range LabelResourceTypes { + knownResourceTypes[rt] = true + } + + entries := []annotationsLabelsReportEntry{} + for propertyName := range properties { + suffix := strings.TrimPrefix(propertyName, "labels.") + dot := strings.LastIndex(suffix, ".") + if dot <= 0 || dot == len(suffix)-1 { + continue + } + + propProcessType := suffix[:dot] + propResourceType := suffix[dot+1:] + if !knownResourceTypes[propResourceType] { + continue + } + + if processType != "" && propProcessType != processType { + continue + } + if resourceType != "" && propResourceType != resourceType { + continue + } + + labelMap, err := getLabel(appName, propProcessType, propResourceType) + if err != nil { + return nil, fmt.Errorf("Unable to read label %s: %w", propertyName, err) + } + + for key, value := range labelMap { + entries = append(entries, annotationsLabelsReportEntry{ + ProcessType: propProcessType, + ResourceType: propResourceType, + Key: key, + Value: value, + }) + } + } + + return entries, nil +} + +func isReservedAnnotationProperty(propertyName string) bool { + for _, prefix := range reservedAnnotationPrefixes { + if strings.HasPrefix(propertyName, prefix) { + return true + } + } + return false +} + +type annotationsLabelsRenderInput struct { + AppName string + ReportType string + // Heading is the plural noun used in stdout headers (e.g. "annotations", "labels"). + Heading string + // RowLabel is the singular noun used to prefix each stdout row (e.g. "Annotation"). + RowLabel string + Entries []annotationsLabelsReportEntry + Format string + InfoFlag string +} + +// renderAnnotationsLabelsReport dispatches to stdout/json/single-flag rendering for the +// annotations and labels reports. +func renderAnnotationsLabelsReport(input annotationsLabelsRenderInput) error { + if input.Format != "stdout" && input.Format != "json" { + return fmt.Errorf("Invalid format: %s", input.Format) + } + if input.Format == "json" && input.InfoFlag != "" { + return fmt.Errorf("--format flag cannot be specified when specifying an info flag") + } + + flatValues := map[string]string{} + flagToValue := map[string]string{} + flagPrefix := "--" + input.ReportType + "." + for _, entry := range input.Entries { + composedKey := renderProcessType(entry.ProcessType) + "." + entry.ResourceType + "." + entry.Key + flatValues[composedKey] = entry.Value + flagToValue[flagPrefix+composedKey] = entry.Value + } + + if input.InfoFlag != "" { + value, ok := flagToValue[input.InfoFlag] + if !ok { + validFlags := []string{} + for flag := range flagToValue { + validFlags = append(validFlags, flag) + } + sort.Strings(validFlags) + return fmt.Errorf("Invalid flag passed, valid flags: %s", strings.Join(validFlags, ", ")) + } + fmt.Println(value) + return nil + } + + if input.Format == "json" { + b, err := json.Marshal(flatValues) + if err != nil { + return fmt.Errorf("Unable to marshal json: %w", err) + } + fmt.Println(string(b)) + return nil + } + + entriesByGroup := map[string][]annotationsLabelsReportEntry{} + for _, entry := range input.Entries { + groupKey := renderProcessType(entry.ProcessType) + "/" + entry.ResourceType + entriesByGroup[groupKey] = append(entriesByGroup[groupKey], entry) + } + + groupKeys := []string{} + for groupKey := range entriesByGroup { + groupKeys = append(groupKeys, groupKey) + } + sort.Strings(groupKeys) + + common.LogInfo2Quiet(fmt.Sprintf("%s %s information", input.AppName, input.Heading)) + if len(groupKeys) == 0 { + return nil + } + + for _, groupKey := range groupKeys { + groupEntries := entriesByGroup[groupKey] + sort.Slice(groupEntries, func(i, j int) bool { + return groupEntries[i].Key < groupEntries[j].Key + }) + + length := 31 + for _, entry := range groupEntries { + label := fmt.Sprintf("%s (%s) %s:", input.RowLabel, groupKey, entry.Key) + if len(label) > length { + length = len(label) + } + } + + for _, entry := range groupEntries { + label := fmt.Sprintf("%s (%s) %s:", input.RowLabel, groupKey, entry.Key) + common.LogVerbose(fmt.Sprintf("%s%s", common.RightPad(label, length, " "), entry.Value)) + } + } + + return nil +} + +// renderProcessType normalizes the in-storage process type for display in JSON keys, +// stdout headers, and single-flag lookups. GlobalProcessType (literal "--global") is +// rendered as "global"; real process types pass through unchanged. +func renderProcessType(processType string) string { + if processType == GlobalProcessType { + return reportProcessTypeGlobal + } + return processType +} + func reportComputedDeployTimeout(appName string) string { return getComputedDeployTimeout(appName) } diff --git a/plugins/scheduler-k3s/scheduler_k3s.go b/plugins/scheduler-k3s/scheduler_k3s.go index 81dff53a7..0f74b553f 100644 --- a/plugins/scheduler-k3s/scheduler_k3s.go +++ b/plugins/scheduler-k3s/scheduler_k3s.go @@ -52,6 +52,54 @@ const KubeConfigPath = "/etc/rancher/k3s/k3s.yaml" const DefaultKubeContext = "" const TriggerAuthPropertyPrefix = "trigger-auth." +// AnnotationResourceTypes lists the kubernetes resource types that scheduler-k3s +// supports user-provided annotations for. The order here is also the iteration order +// used when rendering reports. +var AnnotationResourceTypes = []string{ + "certificate", + "cronjob", + "deployment", + "ingress", + "job", + "keda_scaled_object", + "keda_secret", + "keda_trigger_authentication", + "pod", + "secret", + "service", + "serviceaccount", + "traefik_ingressroute", + "traefik_middleware", +} + +// LabelResourceTypes lists the kubernetes resource types that scheduler-k3s +// supports user-provided labels for. Differs from AnnotationResourceTypes because +// keda resources do not currently flow user labels through. +var LabelResourceTypes = []string{ + "certificate", + "cronjob", + "deployment", + "ingress", + "job", + "pod", + "secret", + "service", + "serviceaccount", + "traefik_ingressroute", + "traefik_middleware", +} + +// reservedAnnotationPrefixes lists property name prefixes that are not annotations +// even though their trailing segment may collide with an AnnotationResourceTypes value +// (e.g. "chart.cert-manager.deployment"). The annotations report uses this list to +// exclude such properties when scanning the property store. +var reservedAnnotationPrefixes = []string{ + "chart.", + "labels.", + "node-profile-", + TriggerAuthPropertyPrefix, +} + var ( runtimeScheme = runtime.NewScheme() codecs = serializer.NewCodecFactory(runtimeScheme) diff --git a/plugins/scheduler-k3s/src/commands/commands.go b/plugins/scheduler-k3s/src/commands/commands.go index 021ebbf4d..d24577469 100644 --- a/plugins/scheduler-k3s/src/commands/commands.go +++ b/plugins/scheduler-k3s/src/commands/commands.go @@ -19,6 +19,7 @@ Additional commands:` helpContent = ` scheduler-k3s:autoscaling-auth:set [<--metadata key=value>...], Set or clear a scheduler-k3s autoscaling keda trigger authentication resource for an app scheduler-k3s:annotations:set () [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE>, Set or clear an annotation for a given app/process-type/resource-type combination + scheduler-k3s:annotations:report [|--global] [--format stdout|json] [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE], Displays a scheduler-k3s annotations report for one or more apps scheduler-k3s:charts:report [] [--format stdout|json], Displays a scheduler-k3s chart override report scheduler-k3s:charts:set (), Set or clear a chart-specific helm value scheduler-k3s:cluster:add [--profile PROFILE] [--role ROLE] [--insecure-allow-unknown-hosts] [--server-ip SERVER_IP] [--taint-scheduling] [--kubelet-args KUBELET_ARGS] , Adds a server node to a Dokku-managed cluster @@ -27,6 +28,7 @@ Additional commands:` scheduler-k3s:ensure-charts, Ensures the k3s charts are installed scheduler-k3s:initialize [--server-ip SERVER_IP] [--taint-scheduling], Initializes a cluster scheduler-k3s:labels:set () [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE>, Set or clear a label for a given app/process-type/resource-type combination + scheduler-k3s:labels:report [|--global] [--format stdout|json] [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE], Displays a scheduler-k3s labels report for one or more apps scheduler-k3s:profiles:add [--role ROLE] [--insecure-allow-unknown-hosts] [--taint-scheduling] [--kubelet-args KUBELET_ARGS], Adds a node profile to the k3s cluster scheduler-k3s:profiles:list [--format json|stdout], Lists all node profiles in the k3s cluster scheduler-k3s:profiles:remove , Removes a node profile from the k3s cluster diff --git a/plugins/scheduler-k3s/src/subcommands/subcommands.go b/plugins/scheduler-k3s/src/subcommands/subcommands.go index fc748b5ad..e7a858bad 100644 --- a/plugins/scheduler-k3s/src/subcommands/subcommands.go +++ b/plugins/scheduler-k3s/src/subcommands/subcommands.go @@ -34,6 +34,23 @@ func main() { } err = scheduler_k3s.CommandAnnotationsSet(appName, *processType, *resourceType, property, value) + case "annotations:report": + args := flag.NewFlagSet("scheduler-k3s:annotations:report", flag.ExitOnError) + global := args.Bool("global", false, "--global: show the global report") + format := args.String("format", "stdout", "format: [ stdout | json ]") + processType := args.String("process-type", "", "--process-type: filter by process-type") + resourceType := args.String("resource-type", "", "--resource-type: filter by resource-type") + passthroughArgs, infoFlag, flagErr := scheduler_k3s.ExtractReportInfoFlag("--scheduler-k3s-annotations.", os.Args[2:]) + if flagErr != nil { + err = flagErr + break + } + args.Parse(passthroughArgs) + appName := args.Arg(0) + if *global { + appName = "--global" + } + err = scheduler_k3s.CommandAnnotationsReport(appName, *format, *processType, *resourceType, infoFlag) case "autoscaling-auth:set": args := flag.NewFlagSet("scheduler-k3s:autoscaling-auth:set", flag.ExitOnError) global := args.Bool("global", false, "--global: set a global property") @@ -48,12 +65,15 @@ func main() { err = scheduler_k3s.CommandAutoscalingAuthSet(appName, trigger, *metadata, *global) case "autoscaling-auth:report": args := flag.NewFlagSet("scheduler-k3s:autoscaling-auth:report", flag.ExitOnError) - global := args.Bool("global", false, "--global: show a global report") + global := args.Bool("global", false, "--global: show the global report") includeMetadata := args.Bool("include-metadata", false, "--include-metadata: include metadata in the report") format := args.String("format", "stdout", "format: [ stdout | json ]") args.Parse(os.Args[2:]) appName := args.Arg(0) - err = scheduler_k3s.CommandAutoscalingAuthReport(appName, *format, *global, *includeMetadata) + if *global { + appName = "--global" + } + err = scheduler_k3s.CommandAutoscalingAuthReport(appName, *format, *includeMetadata) case "charts:set": args := flag.NewFlagSet("scheduler-k3s:charts:set", flag.ExitOnError) args.Parse(os.Args[2:]) @@ -121,6 +141,23 @@ func main() { } err = scheduler_k3s.CommandLabelsSet(appName, *processType, *resourceType, property, value) + case "labels:report": + args := flag.NewFlagSet("scheduler-k3s:labels:report", flag.ExitOnError) + global := args.Bool("global", false, "--global: show the global report") + format := args.String("format", "stdout", "format: [ stdout | json ]") + processType := args.String("process-type", "", "--process-type: filter by process-type") + resourceType := args.String("resource-type", "", "--resource-type: filter by resource-type") + passthroughArgs, infoFlag, flagErr := scheduler_k3s.ExtractReportInfoFlag("--scheduler-k3s-labels.", os.Args[2:]) + if flagErr != nil { + err = flagErr + break + } + args.Parse(passthroughArgs) + appName := args.Arg(0) + if *global { + appName = "--global" + } + err = scheduler_k3s.CommandLabelsReport(appName, *format, *processType, *resourceType, infoFlag) case "profiles:add": args := flag.NewFlagSet("scheduler-k3s:profiles:add", flag.ExitOnError) role := args.String("role", "worker", "role: [ server | worker ]") diff --git a/plugins/scheduler-k3s/subcommands.go b/plugins/scheduler-k3s/subcommands.go index fb7e6aa7b..e49b1d1b6 100644 --- a/plugins/scheduler-k3s/subcommands.go +++ b/plugins/scheduler-k3s/subcommands.go @@ -124,26 +124,69 @@ func CommandAutoscalingAuthSet(appName string, trigger string, metadata map[stri } // CommandAutoscalingAuthReport displays a scheduler-k3s autoscaling keda trigger authentication report for one or more apps -func CommandAutoscalingAuthReport(appName string, format string, global bool, includeMetadata bool) error { - if len(appName) == 0 && !global { - return fmt.Errorf("Missing required app name or --global flag") - } - - if len(appName) > 0 && global { - return fmt.Errorf("Cannot specify both app name and --global flag") - } - - if !global { - if err := common.VerifyAppName(appName); err != nil { +func CommandAutoscalingAuthReport(appName string, format string, includeMetadata bool) error { + if len(appName) == 0 { + apps, err := common.DokkuApps() + if err != nil { + if errors.Is(err, common.NoAppsExist) { + common.LogWarn(err.Error()) + return nil + } return err } + for _, app := range apps { + if err := ReportAutoscalingAuthSingleApp(app, format, includeMetadata); err != nil { + return err + } + } + return nil } - if len(appName) > 0 { - return ReportAutoscalingAuthSingleApp(appName, format, includeMetadata) + return ReportAutoscalingAuthSingleApp(appName, format, includeMetadata) +} + +// CommandAnnotationsReport displays a scheduler-k3s annotations report for one or more apps +func CommandAnnotationsReport(appName string, format string, processType string, resourceType string, infoFlag string) error { + if len(appName) == 0 { + apps, err := common.DokkuApps() + if err != nil { + if errors.Is(err, common.NoAppsExist) { + common.LogWarn(err.Error()) + return nil + } + return err + } + for _, app := range apps { + if err := ReportAnnotationsSingleApp(app, format, processType, resourceType, infoFlag); err != nil { + return err + } + } + return nil } - return ReportAutoscalingAuthSingleApp("--global", format, includeMetadata) + return ReportAnnotationsSingleApp(appName, format, processType, resourceType, infoFlag) +} + +// CommandLabelsReport displays a scheduler-k3s labels report for one or more apps +func CommandLabelsReport(appName string, format string, processType string, resourceType string, infoFlag string) error { + if len(appName) == 0 { + apps, err := common.DokkuApps() + if err != nil { + if errors.Is(err, common.NoAppsExist) { + common.LogWarn(err.Error()) + return nil + } + return err + } + for _, app := range apps { + if err := ReportLabelsSingleApp(app, format, processType, resourceType, infoFlag); err != nil { + return err + } + } + return nil + } + + return ReportLabelsSingleApp(appName, format, processType, resourceType, infoFlag) } // CommandInitialize initializes a k3s cluster on the local server diff --git a/tests/unit/scheduler-k3s-annotations.bats b/tests/unit/scheduler-k3s-annotations.bats new file mode 100644 index 000000000..2ae147d4e --- /dev/null +++ b/tests/unit/scheduler-k3s-annotations.bats @@ -0,0 +1,88 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + dokku apps:create $TEST_APP >/dev/null 2>/dev/null || true +} + +teardown() { + dokku --force apps:destroy $TEST_APP >/dev/null 2>/dev/null || true + dokku --force apps:destroy ${TEST_APP}-2 >/dev/null 2>/dev/null || true + for resource in deployment service pod cronjob job secret ingress serviceaccount; do + dokku scheduler-k3s:annotations:set --global --resource-type "$resource" foo "" >/dev/null 2>/dev/null || true + dokku scheduler-k3s:annotations:set --global --resource-type "$resource" bar "" >/dev/null 2>/dev/null || true + done + global_teardown +} + +@test "(scheduler-k3s:annotations:report) lists annotations after set, clears them after unset" { + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type deployment foo bar" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --format json | jq -r '.\"global.deployment.foo\"'" + assert_success + assert_output "bar" + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP" + assert_success + assert_output_contains "$TEST_APP annotations information" + assert_output_contains "Annotation (global/deployment) foo:" + assert_output_contains "bar" + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --scheduler-k3s-annotations.global.deployment.foo" + assert_success + assert_output "bar" + + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type deployment foo" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --format json" + assert_success + assert_output "{}" +} + +@test "(scheduler-k3s:annotations:report) honours --process-type and --resource-type filters" { + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type deployment foo bar" + assert_success + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --process-type web --resource-type deployment baz qux" + assert_success + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type service alpha beta" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --resource-type deployment --format json | jq -r 'keys | sort | join(\",\")'" + assert_success + assert_output "global.deployment.foo,web.deployment.baz" + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --process-type web --format json | jq -r 'keys | sort | join(\",\")'" + assert_success + assert_output "web.deployment.baz" + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --process-type web --resource-type deployment --format json | jq -r '.\"web.deployment.baz\"'" + assert_success + assert_output "qux" +} + +@test "(scheduler-k3s:annotations:report) supports --global scope and no-arg multi-app loop" { + run /bin/bash -c "dokku apps:create ${TEST_APP}-2" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:set --global --resource-type deployment foo global-val" + assert_success + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type deployment foo app1-val" + assert_success + run /bin/bash -c "dokku scheduler-k3s:annotations:set ${TEST_APP}-2 --resource-type deployment bar app2-val" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:report --global --format json | jq -r '.\"global.deployment.foo\"'" + assert_success + assert_output "global-val" + + run /bin/bash -c "dokku scheduler-k3s:annotations:report" + assert_success + assert_output_contains "$TEST_APP annotations information" + assert_output_contains "${TEST_APP}-2 annotations information" + assert_output_contains "app1-val" + assert_output_contains "app2-val" +} diff --git a/tests/unit/scheduler-k3s-autoscaling-auth.bats b/tests/unit/scheduler-k3s-autoscaling-auth.bats new file mode 100644 index 000000000..92e542486 --- /dev/null +++ b/tests/unit/scheduler-k3s-autoscaling-auth.bats @@ -0,0 +1,30 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + dokku apps:create $TEST_APP >/dev/null 2>/dev/null || true + dokku apps:create ${TEST_APP}-2 >/dev/null 2>/dev/null || true +} + +teardown() { + dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog >/dev/null 2>/dev/null || true + dokku scheduler-k3s:autoscaling-auth:set ${TEST_APP}-2 datadog >/dev/null 2>/dev/null || true + dokku --force apps:destroy $TEST_APP >/dev/null 2>/dev/null || true + dokku --force apps:destroy ${TEST_APP}-2 >/dev/null 2>/dev/null || true + global_teardown +} + +@test "(scheduler-k3s:autoscaling-auth:report) no-arg loops all apps" { + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog --metadata apiKey=secret-1" + assert_success + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set ${TEST_APP}-2 datadog --metadata apiKey=secret-2" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report" + assert_success + assert_output_contains "$TEST_APP scheduler-k3s information" + assert_output_contains "${TEST_APP}-2 scheduler-k3s information" + assert_output_contains "Datadog:" 2 +} diff --git a/tests/unit/scheduler-k3s-labels.bats b/tests/unit/scheduler-k3s-labels.bats new file mode 100644 index 000000000..98d7360b3 --- /dev/null +++ b/tests/unit/scheduler-k3s-labels.bats @@ -0,0 +1,88 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + dokku apps:create $TEST_APP >/dev/null 2>/dev/null || true +} + +teardown() { + dokku --force apps:destroy $TEST_APP >/dev/null 2>/dev/null || true + dokku --force apps:destroy ${TEST_APP}-2 >/dev/null 2>/dev/null || true + for resource in deployment service pod cronjob job secret ingress serviceaccount; do + dokku scheduler-k3s:labels:set --global --resource-type "$resource" foo "" >/dev/null 2>/dev/null || true + dokku scheduler-k3s:labels:set --global --resource-type "$resource" bar "" >/dev/null 2>/dev/null || true + done + global_teardown +} + +@test "(scheduler-k3s:labels:report) lists labels after set, clears them after unset" { + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --resource-type deployment foo bar" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --format json | jq -r '.\"global.deployment.foo\"'" + assert_success + assert_output "bar" + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP" + assert_success + assert_output_contains "$TEST_APP labels information" + assert_output_contains "Label (global/deployment) foo:" + assert_output_contains "bar" + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --scheduler-k3s-labels.global.deployment.foo" + assert_success + assert_output "bar" + + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --resource-type deployment foo" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --format json" + assert_success + assert_output "{}" +} + +@test "(scheduler-k3s:labels:report) honours --process-type and --resource-type filters" { + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --resource-type deployment foo bar" + assert_success + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --process-type web --resource-type deployment baz qux" + assert_success + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --resource-type service alpha beta" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --resource-type deployment --format json | jq -r 'keys | sort | join(\",\")'" + assert_success + assert_output "global.deployment.foo,web.deployment.baz" + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --process-type web --format json | jq -r 'keys | sort | join(\",\")'" + assert_success + assert_output "web.deployment.baz" + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --process-type web --resource-type deployment --format json | jq -r '.\"web.deployment.baz\"'" + assert_success + assert_output "qux" +} + +@test "(scheduler-k3s:labels:report) supports --global scope and no-arg multi-app loop" { + run /bin/bash -c "dokku apps:create ${TEST_APP}-2" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:labels:set --global --resource-type deployment foo global-val" + assert_success + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --resource-type deployment foo app1-val" + assert_success + run /bin/bash -c "dokku scheduler-k3s:labels:set ${TEST_APP}-2 --resource-type deployment bar app2-val" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:labels:report --global --format json | jq -r '.\"global.deployment.foo\"'" + assert_success + assert_output "global-val" + + run /bin/bash -c "dokku scheduler-k3s:labels:report" + assert_success + assert_output_contains "$TEST_APP labels information" + assert_output_contains "${TEST_APP}-2 labels information" + assert_output_contains "app1-val" + assert_output_contains "app2-val" +}