refactor: move functions around and ensure we respect local versions of golang dependencies

This commit is contained in:
Jose Diaz-Gonzalez
2024-01-18 15:11:51 -05:00
parent 9d27ff39da
commit 1c5505f78b
4 changed files with 255 additions and 230 deletions

View File

@@ -4,21 +4,43 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"time"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/config"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubectl/pkg/util/term"
"k8s.io/kubernetes/pkg/client/conditions"
"mvdan.cc/sh/v3/shell"
)
type EnterPodInput struct {
AppName string
Clientset KubernetesClient
Command []string
Entrypoint string
ProcessType string
SelectedPod v1.Pod
}
type GetPodInput struct {
Clientset KubernetesClient
Namespace string
Selector string
}
// StartCommandInput contains all the information needed to get the start command
type StartCommandInput struct {
// AppName is the name of the app
@@ -45,6 +67,21 @@ type KubernetesClient struct {
RestConfig rest.Config
}
type WaitForPodBySelectorRunningInput struct {
Clientset KubernetesClient
Namespace string
Selector string
Timeout int
Waiter func(ctx context.Context, clientset KubernetesClient, podName, namespace string) wait.ConditionWithContextFunc
}
type WaitForPodToExistInput struct {
Clientset KubernetesClient
Namespace string
RetryCount int
Selector string
}
func NewKubernetesClient() (KubernetesClient, error) {
clientConfig := KubernetesClientConfig()
restConf, err := clientConfig.ClientConfig()
@@ -132,15 +169,69 @@ func createKubernetesNamespace(ctx context.Context, namespaceName string) error
return nil
}
func enterPod(ctx context.Context, input EnterPodInput) error {
coreclient, err := corev1client.NewForConfig(&input.Clientset.RestConfig)
if err != nil {
return fmt.Errorf("Error creating corev1 client: %w", err)
}
req := coreclient.RESTClient().Post().
Resource("pods").
Namespace(input.SelectedPod.Namespace).
Name(input.SelectedPod.Name).
SubResource("exec")
req.Param("container", fmt.Sprintf("%s-%s", input.AppName, input.ProcessType))
req.Param("stdin", "true")
req.Param("stdout", "true")
req.Param("stderr", "true")
req.Param("tty", "true")
if input.Entrypoint != "" {
req.Param("command", input.Entrypoint)
}
for _, cmd := range input.Command {
req.Param("command", cmd)
}
t := term.TTY{
In: os.Stdin,
Out: os.Stdout,
Raw: true,
}
size := t.GetSize()
sizeQueue := t.MonitorSize(size)
return t.Safe(func() error {
exec, err := remotecommand.NewSPDYExecutor(&input.Clientset.RestConfig, "POST", req.URL())
if err != nil {
return fmt.Errorf("Error creating executor: %w", err)
}
return exec.StreamWithContext(ctx, remotecommand.StreamOptions{
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
Tty: true,
TerminalSizeQueue: sizeQueue,
})
})
}
func extractStartCommand(input StartCommandInput) string {
command := ""
if input.ImageSourceType == "herokuish" {
command = "/start " + input.ProcessType
}
startCommandOverride, ok := config.Get(input.AppName, "DOKKU_START_CMD")
if ok {
command = startCommandOverride
startCommandResp, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "config-get",
Args: []string{input.AppName, "DOKKU_START_CMD"},
CaptureOutput: true,
StreamStdio: false,
})
if err == nil && startCommandResp.ExitCode == 0 {
command = startCommandResp.Stdout
}
if input.ImageSourceType == "herokuish" {
@@ -148,9 +239,14 @@ func extractStartCommand(input StartCommandInput) string {
}
if input.ImageSourceType == "dockerfile" {
startCommandOverride, ok := config.Get(input.AppName, "DOKKU_DOCKERFILE_START_CMD")
if ok {
command = startCommandOverride
startCommandDockerfileResp, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "config-get",
Args: []string{input.AppName, "DOKKU_DOCKERFILE_START_CMD"},
CaptureOutput: true,
StreamStdio: false,
})
if err == nil && startCommandDockerfileResp.ExitCode == 0 {
command = startCommandDockerfileResp.Stdout
}
}
@@ -221,3 +317,74 @@ func getStartCommand(input StartCommandInput) (StartCommandOutput, error) {
Command: fields,
}, nil
}
func getPod(ctx context.Context, input GetPodInput) (v1.PodList, error) {
listOptions := metav1.ListOptions{LabelSelector: input.Selector}
podList, err := input.Clientset.Client.CoreV1().Pods(input.Namespace).List(ctx, listOptions)
return *podList, err
}
func isPodReady(ctx context.Context, clientset KubernetesClient, podName, namespace string) wait.ConditionWithContextFunc {
return func(ctx context.Context) (bool, error) {
fmt.Printf(".") // progress bar!
pod, err := clientset.Client.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
if err != nil {
return false, err
}
switch pod.Status.Phase {
case v1.PodRunning:
return true, nil
case v1.PodFailed, v1.PodSucceeded:
return false, conditions.ErrPodCompleted
}
return false, nil
}
}
func waitForPodBySelectorRunning(ctx context.Context, input WaitForPodBySelectorRunningInput) error {
podList, err := waitForPodToExist(ctx, WaitForPodToExistInput{
Clientset: input.Clientset,
Namespace: input.Namespace,
RetryCount: 3,
Selector: input.Selector,
})
if err != nil {
return fmt.Errorf("Error waiting for pod to exist: %w", err)
}
if len(podList.Items) == 0 {
return fmt.Errorf("no pods in %s with selector %s", input.Namespace, input.Selector)
}
timeout := time.Duration(input.Timeout) * time.Second
for _, pod := range podList.Items {
if err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, false, input.Waiter(ctx, input.Clientset, pod.Name, pod.Namespace)); err != nil {
print("\n")
return err
}
}
print("\n")
return nil
}
func waitForPodToExist(ctx context.Context, input WaitForPodToExistInput) (v1.PodList, error) {
var podList v1.PodList
var err error
for i := 0; i < input.RetryCount; i++ {
podList, err = getPod(ctx, GetPodInput{
Clientset: input.Clientset,
Namespace: input.Namespace,
Selector: input.Selector,
})
if err == nil {
break
}
time.Sleep(1 * time.Second)
}
if err != nil {
return podList, fmt.Errorf("Error listing pods: %w", err)
}
return podList, nil
}

View File

@@ -4,7 +4,8 @@ go 1.21
require (
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
github.com/dokku/dokku/plugins/config v0.0.0-20240112043630-928c388f10fa
github.com/dokku/dokku/plugins/config v0.0.0-00010101000000-000000000000
github.com/dokku/dokku/plugins/cron v0.0.0-00010101000000-000000000000
github.com/fatih/color v1.16.0
github.com/go-resty/resty/v2 v2.11.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
@@ -54,6 +55,7 @@ require (
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dokku/dokku/plugins/app-json v0.0.0-00010101000000-000000000000 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
@@ -111,6 +113,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
@@ -123,6 +126,7 @@ require (
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rubenv/sql-migrate v1.5.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/columnize v2.1.2+incompatible // indirect
@@ -169,4 +173,10 @@ require (
sigs.k8s.io/yaml v1.3.0 // indirect
)
replace github.com/dokku/dokku/plugins/app-json => ../app-json
replace github.com/dokku/dokku/plugins/common => ../common
replace github.com/dokku/dokku/plugins/config => ../config
replace github.com/dokku/dokku/plugins/cron => ../cron

View File

@@ -102,8 +102,6 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/dokku/dokku/plugins/config v0.0.0-20240112043630-928c388f10fa h1:lIscxUTyCvJSHV9oY3JBQazshpvSc7H2kEBwIZddMfc=
github.com/dokku/dokku/plugins/config v0.0.0-20240112043630-928c388f10fa/go.mod h1:7lDnO8LtGfKX1eFKAXunqGGTqwzJNaf6V7IkKhxlN2g=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -319,6 +317,8 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -373,6 +373,8 @@ github.com/rancher/dynamiclistener v0.3.5 h1:5TaIHvkDGmZKvc96Huur16zfTKOiLhDtK4S
github.com/rancher/dynamiclistener v0.3.5/go.mod h1:dW/YF6/m2+uEyJ5VtEcd9THxda599HP6N9dSXk81+k0=
github.com/rancher/wharfie v0.6.5 h1:Xxj/ki8d4BFeQBvyViW9jX8BqxaPlZwXvRD8BG2Mm/8=
github.com/rancher/wharfie v0.6.5/go.mod h1:iGXn5Y9GEhsx1h/lPUXAklK4DoY4+6NOJF+Li1j248Y=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY=
github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0=

View File

@@ -29,10 +29,6 @@ import (
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubectl/pkg/util/term"
"k8s.io/kubernetes/pkg/client/conditions"
"k8s.io/utils/ptr"
)
@@ -69,25 +65,6 @@ func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error {
return nil
}
// TriggerSchedulerPostDelete destroys the scheduler-k3s data for a given app container
func TriggerSchedulerPostDelete(scheduler string, appName string) error {
if scheduler != "k3s" {
return nil
}
namespace := common.PropertyGetDefault("scheduler-k3s", appName, "namespace", "default")
helmAgent, err := NewHelmAgent(namespace, DeployLogPrinter)
if err != nil {
return fmt.Errorf("Error creating helm agent: %w", err)
}
err = helmAgent.UninstallChart(fmt.Sprintf("dokku-%s", appName))
if err != nil {
return fmt.Errorf("Error uninstalling chart: %w", err)
}
return nil
}
// TriggerPostDelete destroys the scheduler-k3s data for a given app container
func TriggerPostDelete(appName string) error {
dataErr := common.RemoveAppDataDirectory("scheduler-k3s", appName)
@@ -535,64 +512,6 @@ func TriggerSchedulerEnter(scheduler string, appName string, processType string,
})
}
type EnterPodInput struct {
AppName string
Clientset KubernetesClient
Command []string
Entrypoint string
ProcessType string
SelectedPod v1.Pod
}
func enterPod(ctx context.Context, input EnterPodInput) error {
coreclient, err := corev1client.NewForConfig(&input.Clientset.RestConfig)
if err != nil {
return fmt.Errorf("Error creating corev1 client: %w", err)
}
req := coreclient.RESTClient().Post().
Resource("pods").
Namespace(input.SelectedPod.Namespace).
Name(input.SelectedPod.Name).
SubResource("exec")
req.Param("container", fmt.Sprintf("%s-%s", input.AppName, input.ProcessType))
req.Param("stdin", "true")
req.Param("stdout", "true")
req.Param("stderr", "true")
req.Param("tty", "true")
if input.Entrypoint != "" {
req.Param("command", input.Entrypoint)
}
for _, cmd := range input.Command {
req.Param("command", cmd)
}
t := term.TTY{
In: os.Stdin,
Out: os.Stdout,
Raw: true,
}
size := t.GetSize()
sizeQueue := t.MonitorSize(size)
return t.Safe(func() error {
exec, err := remotecommand.NewSPDYExecutor(&input.Clientset.RestConfig, "POST", req.URL())
if err != nil {
return fmt.Errorf("Error creating executor: %w", err)
}
return exec.StreamWithContext(ctx, remotecommand.StreamOptions{
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
Tty: true,
TerminalSizeQueue: sizeQueue,
})
})
}
// TriggerSchedulerLogs displays logs for a given application
func TriggerSchedulerLogs(scheduler string, appName string, processType string, tail bool, quiet bool, numLines int64) error {
if scheduler != "k3s" {
@@ -712,59 +631,6 @@ func TriggerSchedulerLogs(scheduler string, appName string, processType string,
return nil
}
// TriggerSchedulerStop stops an application
func TriggerSchedulerStop(scheduler string, appName string) error {
if scheduler != "k3s" {
return nil
}
clientset, err := NewKubernetesClient()
if err != nil {
return fmt.Errorf("Error creating kubernetes client: %w", err)
}
ctx, cancel := context.WithCancel(context.Background())
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt, syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGQUIT,
syscall.SIGTERM)
go func() {
<-signals
cancel()
}()
namespace := common.PropertyGetDefault("scheduler-k3s", appName, "namespace", "default")
deploymentList, err := clientset.Client.AppsV1().Deployments(namespace).List(ctx, metav1.ListOptions{
LabelSelector: fmt.Sprintf("dokku.com/app-name=%s", appName),
})
if err != nil {
return fmt.Errorf("Error listing deployments: %w", err)
}
for _, deployment := range deploymentList.Items {
processType, ok := deployment.Annotations["dokku.com/process-type"]
if !ok {
return fmt.Errorf("Deployment %s does not have a process type annotation", deployment.Name)
}
common.LogVerboseQuiet(fmt.Sprintf("Stopping %s process", processType))
_, err := clientset.Client.AppsV1().Deployments(namespace).UpdateScale(ctx, deployment.Name, &autoscalingv1.Scale{
ObjectMeta: metav1.ObjectMeta{
Name: deployment.Name,
Namespace: namespace,
},
Spec: autoscalingv1.ScaleSpec{
Replicas: 0,
},
}, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("Error updating deployment scale: %w", err)
}
}
return nil
}
// TriggerSchedulerRun runs a command in an ephemeral container
func TriggerSchedulerRun(scheduler string, appName string, envCount int, args []string) error {
if scheduler != "k3s" {
@@ -954,7 +820,7 @@ func TriggerSchedulerRun(scheduler string, appName string, envCount int, args []
}
batchJobSelector := fmt.Sprintf("batch.kubernetes.io/job-name=%s", createdJob.Name)
podList, err := WaitForPodToExist(ctx, WaitForPodToExistInput{
podList, err := waitForPodToExist(ctx, WaitForPodToExistInput{
Clientset: clientset,
Namespace: namespace,
RetryCount: 3,
@@ -972,7 +838,7 @@ func TriggerSchedulerRun(scheduler string, appName string, envCount int, args []
return nil
}
err = WaitForPodBySelectorRunning(ctx, WaitForPodBySelectorRunningInput{
err = waitForPodBySelectorRunning(ctx, WaitForPodBySelectorRunningInput{
Clientset: clientset,
Namespace: namespace,
Selector: batchJobSelector,
@@ -981,7 +847,7 @@ func TriggerSchedulerRun(scheduler string, appName string, envCount int, args []
})
if err != nil {
if errors.Is(err, conditions.ErrPodCompleted) {
podList, podErr := GetPod(ctx, GetPodInput{
podList, podErr := getPod(ctx, GetPodInput{
Clientset: clientset,
Namespace: namespace,
Selector: batchJobSelector,
@@ -1009,7 +875,7 @@ func TriggerSchedulerRun(scheduler string, appName string, envCount int, args []
return fmt.Errorf("Error waiting for pod to be running: %w", err)
}
podList, err = GetPod(ctx, GetPodInput{
podList, err = getPod(ctx, GetPodInput{
Clientset: clientset,
Namespace: namespace,
Selector: batchJobSelector,
@@ -1065,94 +931,74 @@ func TriggerSchedulerRun(scheduler string, appName string, envCount int, args []
return nil
}
func isPodReady(ctx context.Context, clientset KubernetesClient, podName, namespace string) wait.ConditionWithContextFunc {
return func(ctx context.Context) (bool, error) {
fmt.Printf(".") // progress bar!
pod, err := clientset.Client.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
if err != nil {
return false, err
}
switch pod.Status.Phase {
case v1.PodRunning:
return true, nil
case v1.PodFailed, v1.PodSucceeded:
return false, conditions.ErrPodCompleted
}
return false, nil
}
}
type WaitForPodBySelectorRunningInput struct {
Clientset KubernetesClient
Namespace string
Selector string
Timeout int
Waiter func(ctx context.Context, clientset KubernetesClient, podName, namespace string) wait.ConditionWithContextFunc
}
type WaitForPodToExistInput struct {
Clientset KubernetesClient
Namespace string
RetryCount int
Selector string
}
func WaitForPodToExist(ctx context.Context, input WaitForPodToExistInput) (v1.PodList, error) {
var podList v1.PodList
var err error
for i := 0; i < input.RetryCount; i++ {
podList, err = GetPod(ctx, GetPodInput{
Clientset: input.Clientset,
Namespace: input.Namespace,
Selector: input.Selector,
})
if err == nil {
break
}
time.Sleep(1 * time.Second)
// TriggerSchedulerPostDelete destroys the scheduler-k3s data for a given app container
func TriggerSchedulerPostDelete(scheduler string, appName string) error {
if scheduler != "k3s" {
return nil
}
namespace := common.PropertyGetDefault("scheduler-k3s", appName, "namespace", "default")
helmAgent, err := NewHelmAgent(namespace, DeployLogPrinter)
if err != nil {
return podList, fmt.Errorf("Error listing pods: %w", err)
return fmt.Errorf("Error creating helm agent: %w", err)
}
return podList, nil
}
type GetPodInput struct {
Clientset KubernetesClient
Namespace string
Selector string
}
func GetPod(ctx context.Context, input GetPodInput) (v1.PodList, error) {
listOptions := metav1.ListOptions{LabelSelector: input.Selector}
podList, err := input.Clientset.Client.CoreV1().Pods(input.Namespace).List(ctx, listOptions)
return *podList, err
}
func WaitForPodBySelectorRunning(ctx context.Context, input WaitForPodBySelectorRunningInput) error {
podList, err := WaitForPodToExist(ctx, WaitForPodToExistInput{
Clientset: input.Clientset,
Namespace: input.Namespace,
RetryCount: 3,
Selector: input.Selector,
})
err = helmAgent.UninstallChart(fmt.Sprintf("dokku-%s", appName))
if err != nil {
return fmt.Errorf("Error waiting for pod to exist: %w", err)
return fmt.Errorf("Error uninstalling chart: %w", err)
}
return nil
}
// TriggerSchedulerStop stops an application
func TriggerSchedulerStop(scheduler string, appName string) error {
if scheduler != "k3s" {
return nil
}
clientset, err := NewKubernetesClient()
if err != nil {
return fmt.Errorf("Error creating kubernetes client: %w", err)
}
ctx, cancel := context.WithCancel(context.Background())
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt, syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGQUIT,
syscall.SIGTERM)
go func() {
<-signals
cancel()
}()
namespace := common.PropertyGetDefault("scheduler-k3s", appName, "namespace", "default")
deploymentList, err := clientset.Client.AppsV1().Deployments(namespace).List(ctx, metav1.ListOptions{
LabelSelector: fmt.Sprintf("dokku.com/app-name=%s", appName),
})
if err != nil {
return fmt.Errorf("Error listing deployments: %w", err)
}
for _, deployment := range deploymentList.Items {
processType, ok := deployment.Annotations["dokku.com/process-type"]
if !ok {
return fmt.Errorf("Deployment %s does not have a process type annotation", deployment.Name)
}
common.LogVerboseQuiet(fmt.Sprintf("Stopping %s process", processType))
_, err := clientset.Client.AppsV1().Deployments(namespace).UpdateScale(ctx, deployment.Name, &autoscalingv1.Scale{
ObjectMeta: metav1.ObjectMeta{
Name: deployment.Name,
Namespace: namespace,
},
Spec: autoscalingv1.ScaleSpec{
Replicas: 0,
},
}, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("Error updating deployment scale: %w", err)
}
}
if len(podList.Items) == 0 {
return fmt.Errorf("no pods in %s with selector %s", input.Namespace, input.Selector)
}
timeout := time.Duration(input.Timeout) * time.Second
for _, pod := range podList.Items {
if err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, false, input.Waiter(ctx, input.Clientset, pod.Name, pod.Namespace)); err != nil {
print("\n")
return err
}
}
print("\n")
return nil
}