From 90e8cac12f943e3fbdd8672e2274d82fd3ab8aaf Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 21 Oct 2024 22:08:53 -0400 Subject: [PATCH] feat: implement nginx:show-config for k3s plugin This allows users to see what nginx configs are in use for their applications. Closes #7291 --- docs/deployment/schedulers/k3s.md | 12 ++- plugins/nginx-vhosts/command-functions | 13 +++ plugins/nginx-vhosts/functions | 5 +- plugins/scheduler-k3s/Makefile | 2 +- plugins/scheduler-k3s/k8s.go | 27 +++++- .../scheduler-k3s/src/triggers/triggers.go | 5 + plugins/scheduler-k3s/triggers.go | 97 ++++++++++++++++++- 7 files changed, 151 insertions(+), 10 deletions(-) diff --git a/docs/deployment/schedulers/k3s.md b/docs/deployment/schedulers/k3s.md index 039ca589c..00c42654f 100644 --- a/docs/deployment/schedulers/k3s.md +++ b/docs/deployment/schedulers/k3s.md @@ -528,13 +528,17 @@ This plugin implements various functionality through `plugn` triggers to integra - `enter` - `deploy` - healthchecks - - Due to Kubernetes limitations, only a single healthcheck is supported for each of the `liveness`, `readiness`, and `startup` healthchecks - - Due to Kubernetes limitations, content checks are not supported - - Ports specified in the `app.json` are ignored in favor of the container port on the port mapping detected + - Due to Kubernetes limitations, only a single healthcheck is supported for each of the `liveness`, `readiness`, and `startup` healthchecks + - Due to Kubernetes limitations, content checks are not supported + - Ports specified in the `app.json` are ignored in favor of the container port on the port mapping detected - `logs` +- `nginx` + - Properties set by the `nginx` plugin will be respected, either by turning them into annotations or creating a custom server/location snippet that the `ingress-nginx` project can use. + - The `nginx:access-logs` and `nginx:error-logs` commands will fetch logs from one running `ingress-nginx` pod. + - The `nginx:show-config` command will retrieve any `server` blocks associated with a domain attached to the app from one running `ingress-nginx` pod. - `ps:stop` - `run` - - The `scheduler-post-run` trigger is not always triggered + - The `scheduler-post-run` trigger is not always triggered - `run:detached` - `run:list` diff --git a/plugins/nginx-vhosts/command-functions b/plugins/nginx-vhosts/command-functions index 4143e2550..7555a1289 100755 --- a/plugins/nginx-vhosts/command-functions +++ b/plugins/nginx-vhosts/command-functions @@ -158,8 +158,21 @@ cmd-nginx-show-config() { declare cmd="nginx:show-config" [[ "$1" == "$cmd" ]] && shift 1 declare APP="$1" + local proxy_type verify_app_name "$APP" + proxy_type="$(plugn trigger proxy-type "$APP")" + + local DOKKU_SCHEDULER=$(get_app_scheduler "$APP") + if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then + plugn trigger scheduler-proxy-config "$DOKKU_SCHEDULER" "$APP" "$proxy_type" + return $? + fi + + if [[ "$proxy_type" != "nginx" ]]; then + dokku_log_fail "This command is only available for apps with the nginx proxy type" + fi + if [[ ! -f "$DOKKU_ROOT/$APP/nginx.conf" ]]; then dokku_log_fail "No nginx.conf exists for $APP" fi diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index b4a21a6b1..3c0d9a11c 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -66,6 +66,7 @@ restart_nginx() { nginx_logs() { declare desc="display app nginx logs" declare NGINX_LOGS_TYPE="${1#nginx:}" APP="$2" + local proxy_type local NGINX_LOGS_TYPE=${NGINX_LOGS_TYPE%-logs} local NGINX_LOGS_PATH="$("fn-nginx-computed-${NGINX_LOGS_TYPE}-log-path" "$APP")" @@ -74,6 +75,8 @@ nginx_logs() { dokku_log_fail "$NGINX_LOGS_TYPE logs are disabled for this app" fi + proxy_type="$(plugn trigger proxy-type "$APP")" + local tail=false local num=0 if [[ $3 == "-t" ]]; then @@ -84,7 +87,7 @@ nginx_logs() { local DOKKU_SCHEDULER=$(get_app_scheduler "$APP") if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then - plugn trigger scheduler-proxy-logs "$DOKKU_SCHEDULER" "$APP" "nginx" "$NGINX_LOGS_TYPE" "$tail" "$num" + plugn trigger scheduler-proxy-logs "$DOKKU_SCHEDULER" "$APP" "$proxy_type" "$NGINX_LOGS_TYPE" "$tail" "$num" return $? fi diff --git a/plugins/scheduler-k3s/Makefile b/plugins/scheduler-k3s/Makefile index 07b715153..e369f2b3d 100644 --- a/plugins/scheduler-k3s/Makefile +++ b/plugins/scheduler-k3s/Makefile @@ -1,5 +1,5 @@ SUBCOMMANDS = subcommands/annotations:set subcommands/autoscaling-auth:set subcommands/autoscaling-auth:report subcommands/cluster-add subcommands/cluster-list subcommands/cluster-remove subcommands/initialize subcommands/labels:set subcommands/report subcommands/set subcommands/show-kubeconfig subcommands/uninstall -TRIGGERS = triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-delete triggers/report triggers/scheduler-app-status triggers/scheduler-deploy triggers/scheduler-enter triggers/scheduler-logs triggers/scheduler-proxy-logs triggers/scheduler-post-delete triggers/scheduler-run triggers/scheduler-run-list triggers/scheduler-stop +TRIGGERS = triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-delete triggers/report triggers/scheduler-app-status triggers/scheduler-deploy triggers/scheduler-enter 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 BUILD = commands subcommands triggers PLUGIN_NAME = scheduler-k3s diff --git a/plugins/scheduler-k3s/k8s.go b/plugins/scheduler-k3s/k8s.go index 145c11412..9505f4af3 100644 --- a/plugins/scheduler-k3s/k8s.go +++ b/plugins/scheduler-k3s/k8s.go @@ -312,6 +312,12 @@ type ExecCommandInput struct { // Namespace is the Kubernetes namespace Namespace string + + // Stderr is the error writer + Stderr io.Writer + + // Stdout is the output writer + Stdout io.Writer } // ExecCommand executes a command in a Kubernetes pod @@ -340,11 +346,26 @@ func (k KubernetesClient) ExecCommand(ctx context.Context, input ExecCommandInpu req.Param("command", cmd) } + var stdout io.Writer + if input.Stdout == nil { + stdout = os.Stdout + } else { + stdout = input.Stdout + } + + var stderr io.Writer + if input.Stderr == nil { + stderr = os.Stderr + } else { + stderr = input.Stderr + } + t := term.TTY{ In: os.Stdin, - Out: os.Stdout, + Out: stdout, Raw: true, } + size := t.GetSize() sizeQueue := t.MonitorSize(size) @@ -356,8 +377,8 @@ func (k KubernetesClient) ExecCommand(ctx context.Context, input ExecCommandInpu return exec.StreamWithContext(ctx, remotecommand.StreamOptions{ Stdin: os.Stdin, - Stdout: os.Stdout, - Stderr: os.Stderr, + Stdout: stdout, + Stderr: stderr, Tty: true, TerminalSizeQueue: sizeQueue, }) diff --git a/plugins/scheduler-k3s/src/triggers/triggers.go b/plugins/scheduler-k3s/src/triggers/triggers.go index 8244ccc25..4092da096 100644 --- a/plugins/scheduler-k3s/src/triggers/triggers.go +++ b/plugins/scheduler-k3s/src/triggers/triggers.go @@ -84,6 +84,11 @@ func main() { } err = scheduler_k3s.TriggerSchedulerLogs(scheduler, appName, processType, tail, quiet, numLines) + case "scheduler-proxy-config": + scheduler := flag.Arg(0) + appName := flag.Arg(1) + proxyType := flag.Arg(2) + err = scheduler_k3s.TriggerSchedulerProxyConfig(scheduler, appName, proxyType) case "scheduler-proxy-logs": var tail bool var numLines int64 diff --git a/plugins/scheduler-k3s/triggers.go b/plugins/scheduler-k3s/triggers.go index d98c19135..a2a38e30a 100644 --- a/plugins/scheduler-k3s/triggers.go +++ b/plugins/scheduler-k3s/triggers.go @@ -1,12 +1,14 @@ package scheduler_k3s import ( + "bytes" "context" "crypto/rand" "encoding/base64" "encoding/json" "errors" "fmt" + "io" "os" "os/signal" "path/filepath" @@ -841,9 +843,102 @@ func TriggerSchedulerLogs(scheduler string, appName string, processType string, }) } +// TriggerSchedulerProxyConfig displays nginx config for a given application +func TriggerSchedulerProxyConfig(scheduler string, appName string, proxyType string) error { + if scheduler != "k3s" || proxyType != "k3s" { + return nil + } + + clientset, err := NewKubernetesClient() + if err != nil { + return fmt.Errorf("Error creating kubernetes client: %w", err) + } + + pods, err := clientset.ListPods(context.Background(), ListPodsInput{ + Namespace: "ingress-nginx", + LabelSelector: "app.kubernetes.io/name=ingress-nginx", + }) + if err != nil { + return fmt.Errorf("Error listing pods: %w", err) + } + + if len(pods) == 0 { + return errors.New("No pods found for ingress-nginx") + } + + var buf bytes.Buffer + w := io.MultiWriter(&buf) + + command := []string{"cat", "/etc/nginx/nginx.conf"} + err = clientset.ExecCommand(context.Background(), ExecCommandInput{ + Command: command, + ContainerName: "controller", + Name: pods[0].Name, + Namespace: "ingress-nginx", + Stdout: w, + }) + if err != nil { + return fmt.Errorf("Error reading nginx config: %w", err) + } + + // split buffer string into lines + lines := strings.Split(buf.String(), "\n") + + // get every domain block from the nginx config + // domain blocks begin with "## start server DOMAIN_NAME" and end with "## end server DOMAIN_NAME" + // each line also may have space characters at the beginning + domainBlocks := map[string][]string{} + for i, line := range lines { + strippedLine := strings.TrimSpace(line) + if strings.HasPrefix(strippedLine, "## start server ") { + domainName := strings.TrimPrefix(strippedLine, "## start server ") + domainBlocks[domainName] = []string{} + for _, nextLine := range lines[i+1:] { + nextStrippedLine := strings.TrimSpace(nextLine) + if strings.HasPrefix(nextStrippedLine, "## end server "+domainName) { + break + } + domainBlocks[domainName] = append(domainBlocks[domainName], nextLine) + } + } + } + + // get all domains for this app + domains := []string{} + _, err = common.CallPlugnTrigger(common.PlugnTriggerInput{ + Trigger: "domains-vhost-enabled", + Args: []string{appName}, + StreamStdio: true, + }) + if err == nil { + results, err := common.CallPlugnTrigger(common.PlugnTriggerInput{ + Trigger: "domains-list", + Args: []string{appName}, + }) + if err != nil { + return fmt.Errorf("Error getting domains for deployment: %w", err) + } + + for _, domain := range strings.Split(results.StdoutContents(), "\n") { + domain = strings.TrimSpace(domain) + if domain != "" { + domains = append(domains, domain) + } + } + } + + for _, domain := range domains { + if domainBlock, ok := domainBlocks[domain]; ok { + fmt.Println(strings.Join(domainBlock, "\n")) + } + } + + return err +} + // TriggerSchedulerProxyLogs displays nginx logs for a given application func TriggerSchedulerProxyLogs(scheduler string, appName string, proxyType string, logType string, tail bool, numLines int64) error { - if scheduler != "k3s" || proxyType != "nginx" { + if scheduler != "k3s" || proxyType != "k3s" { return nil }