Merge pull request #6435 from dokku/k3s-support
Implement native k3s scheduler support
This commit is contained in:
3
.github/linters/.yamllint.yml
vendored
3
.github/linters/.yamllint.yml
vendored
@@ -3,3 +3,6 @@ extends: default
|
||||
|
||||
rules:
|
||||
line-length: disable
|
||||
|
||||
ignore:
|
||||
- plugins/scheduler-k3s/templates/*
|
||||
|
||||
2
debian/control
vendored
2
debian/control
vendored
@@ -3,7 +3,7 @@ Version: 0.32.4
|
||||
Section: web
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Depends: apache2-utils, locales, git, cpio, curl, man-db, netcat, sshcommand, docker-engine-cs (>= 17.05.0) | docker-engine (>= 17.05.0) | docker-io (>= 17.05.0) | docker.io (>= 17.05.0) | docker-ce (>= 17.05.0) | docker-ee (>= 17.05.0) | moby-engine, docker-compose-plugin | moby-compose, docker-buildx-plugin | moby-buildx, docker-container-healthchecker, docker-image-labeler, lambda-builder, net-tools, netrc, software-properties-common, parallel, procfile-util, python-software-properties | python3-software-properties, rsync, rsyslog, dos2unix, jq, unzip, util-linux
|
||||
Depends: apache2-utils, acl, locales, git, cpio, curl, man-db, netcat, sshcommand, docker-engine-cs (>= 17.05.0) | docker-engine (>= 17.05.0) | docker-io (>= 17.05.0) | docker.io (>= 17.05.0) | docker-ce (>= 17.05.0) | docker-ee (>= 17.05.0) | moby-engine, docker-compose-plugin | moby-compose, docker-buildx-plugin | moby-buildx, docker-container-healthchecker, docker-image-labeler, lambda-builder, net-tools, netrc, software-properties-common, parallel, procfile-util, python-software-properties | python3-software-properties, rsync, rsyslog, dos2unix, jq, unzip, util-linux
|
||||
Recommends: herokuish, bash-completion, dokku-update, dokku-event-listener
|
||||
Pre-Depends: gliderlabs-sigil, nginx (>= 1.8.0) | openresty, dnsutils, cgroupfs-mount | cgroup-lite, plugn, sudo, python3, debconf
|
||||
Maintainer: Jose Diaz-Gonzalez <dokku@josediazgonzalez.com>
|
||||
|
||||
460
docs/deployment/schedulers/k3s.md
Normal file
460
docs/deployment/schedulers/k3s.md
Normal file
@@ -0,0 +1,460 @@
|
||||
# K3s Scheduler
|
||||
|
||||
```
|
||||
scheduler-k3s:initialize # initializes a cluster
|
||||
scheduler-k3s:add-server [user:password@host:port] # adds a server node to a Dokku-managed cluster
|
||||
scheduler-k3s:add-client [user:password@host:port] # adds a client node to a Dokku-managed cluster
|
||||
scheduler-k3s:set [<app>|--global] <key> (<value>) # Set or clear a scheduler-k3s property for an app or the scheduler
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> New as of 0.33.0
|
||||
|
||||
For multi-server support, Dokku provides the ability for users to setup a K3s cluster. As with all schedulers, it is set on a per-app basis. The scheduler can currently be overridden by running the following command:
|
||||
|
||||
```shell
|
||||
dokku scheduler:set node-js-app selected k3s
|
||||
```
|
||||
|
||||
As it is the default, unsetting the `selected` scheduler property is also a valid way to reset the scheduler.
|
||||
|
||||
```shell
|
||||
dokku scheduler:set node-js-app k3s
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Initializing a cluster
|
||||
|
||||
> [!WARNING]
|
||||
> This command must be run as root
|
||||
|
||||
Clusters can be initialized via the `scheduler-k3s:initialize` command. This will start a k3s cluster on the Dokku node itself.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:initialize
|
||||
```
|
||||
|
||||
By default, the k3s installation can run both app and system workloads. For clusters where app workloads are run on distinct worker nodes, initialize the cluster with the `--taint-scheduling` flag, which will allow _only_ Critical cluster components on the k3s control-plane nodes.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:initialize --taint-scheduling
|
||||
```
|
||||
|
||||
By default, Dokku will attempt to auto-detect the IP address of the server. In cases where the auto-detected IP address is incorrect, an override may be specified via the `--server-ip` flag:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:initialize --server-ip 192.168.20.15
|
||||
```
|
||||
|
||||
### Adding nodes to the cluster
|
||||
|
||||
> [!WARNING]
|
||||
> The `dokku` user _must_ be able to ssh onto the server in order to connect nodes to the cluster. The remote user must be root or have sudo enabled, or the install will fail.
|
||||
|
||||
#### Adding a worker node
|
||||
|
||||
Nodes that run app workloads can be added via the `scheduler-k3s:cluster-add` command. This will ssh onto the specified server, install k3s, and join it to the current Dokku node in worker mode. Workers are typically used to run app workloads.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:cluster-add ssh://root@worker-1.example.com
|
||||
```
|
||||
|
||||
If the server isn't in the `known_hosts` file, the connection will fail. This can be bypassed by setting the `--insecure-allow-unknown-hosts` flag:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:cluster-add --insecure-allow-unknown-hosts ssh://root@worker-1.example.com
|
||||
```
|
||||
|
||||
By default, Dokku will attempt to auto-detect the IP address of the Dokku server for the remote server to connect to. In cases where the auto-detected IP address is incorrect, an override may be specified via the `--server-ip` flag:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:cluster-add --server-ip 192.168.20.15 ssh://root@worker-1.example.com
|
||||
```
|
||||
|
||||
#### Adding a server node
|
||||
|
||||
> [!NOTE]
|
||||
> Only the initial Dokku server will be properly configured for push deployment, and should be considered your git remote. Additional server nodes are for ensuring high-availability of the K3s etcd state. Ensure this server is properly backed up and restorable or deployments will not work.
|
||||
|
||||
Server nodes are typically used to replicate the cluster state, and it is recommended to have an odd number of nodes spread across several availability zones (datacenters in close proximity within a region). This allows for higher availability in the event of a cluster failure. Server nodes run control-plane services such as the traefik load balancer and the etcd backing store.
|
||||
|
||||
Server nodes can also be added with the `scheduler-k3s:cluster-add` command by specifying `--role server`. This will ssh onto the specified server, install k3s, and join it to the current Dokku node in server mode.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:cluster-add --role server ssh://root@server-1.example.com
|
||||
```
|
||||
|
||||
Server nodes allow any workloads to be scheduled on them by default, in addition to the control-plane, etcd, and the scheduler itself. To avoid app workloads being scheduled on your control-plane, use the `--taint-scheduling` flag:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:cluster-add --role server --taint-scheduling ssh://root@server-1.example.com
|
||||
```
|
||||
|
||||
If the server isn't in the `known_hosts` file, the connection will fail. This can be bypassed by setting the `--insecure-allow-unknown-hosts` flag:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:cluster-add --role server --insecure-allow-unknown-hosts ssh://root@server-1.example.com
|
||||
```
|
||||
|
||||
By default, Dokku will attempt to auto-detect the IP address of the Dokku server for the remote server to connect to. In cases where the auto-detected IP address is incorrect, an override may be specified via the `--server-ip` flag:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:cluster-add --role server --server-ip 192.168.20.15 ssh://root@server-1.example.com
|
||||
```
|
||||
|
||||
#### Changing the network interface
|
||||
|
||||
When attaching an worker or server node, the K3s plugin will look at the IP associated with the `eth0` interface and use that to connect the new node to the cluster. To change this, set the `network-interface` property to the appropriate value.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set --global network-interface eth1
|
||||
```
|
||||
|
||||
### Changing deploy timeouts
|
||||
|
||||
By default, app deploys will timeout after 300s. To customize this value, set the `deploy-timeout` property via `scheduler-k3s:set`:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set node-js-app deploy-timeout 60s
|
||||
```
|
||||
|
||||
The default value may be set by passing an empty value for the option:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set node-js-app deploy-timeout
|
||||
```
|
||||
|
||||
The `deploy-timeout` property can also be set globally. The global default is `300s`.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set --global deploy-timeout 60s
|
||||
```
|
||||
|
||||
The default value may be set by passing an empty value for the option.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set --global deploy-timeout
|
||||
```
|
||||
|
||||
### Customizing the namespace
|
||||
|
||||
By default, app deploys will run against the `default` Kubernetes namespace. To customize this value, set the `namespace` property via `scheduler-k3s:set`:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set node-js-app namespace lollipop
|
||||
```
|
||||
|
||||
The default value may be set by passing an empty value for the option:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set node-js-app namespace
|
||||
```
|
||||
|
||||
The `namespace` property can also be set globally. The global default is `default`.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set --global namespace 60s
|
||||
```
|
||||
|
||||
The default value may be set by passing an empty value for the option.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set --global namespace
|
||||
```
|
||||
|
||||
### Enabling rollback on failure
|
||||
|
||||
By default, app deploys do not rollback on failure. To enable this functionality, set the `rollback-on-failure` property via `scheduler-k3s:set`:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set node-js-app rollback-on-failure true
|
||||
```
|
||||
|
||||
The default value may be set by passing an empty value for the option:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set node-js-app rollback-on-failure
|
||||
```
|
||||
|
||||
The `rollback-on-failure` property can also be set globally. The global default is `false`.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set --global rollback-on-failure false
|
||||
```
|
||||
|
||||
The default value may be set by passing an empty value for the option.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set --global rollback-on-failure
|
||||
```
|
||||
|
||||
### Using image pull secrets
|
||||
|
||||
When authenticating against a registry via `registry:login`, the scheduler-k3s plugin will authenticate all servers in the cluster against the registry specified. If desired, an image pull secret can be used instead. To customize this value, set the `image-pull-secrets` property via `scheduler-k3s:set`:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set node-js-app image-pull-secrets lollipop
|
||||
```
|
||||
|
||||
The default value may be set by passing an empty value for the option:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set node-js-app image-pull-secrets
|
||||
```
|
||||
|
||||
The `image-pull-secrets` property can also be set globally. The global default is empty string, and k3s will use the local `registries.yaml` for any private registry pulls.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set --global image-pull-secrets 60s
|
||||
```
|
||||
|
||||
The default value may be set by passing an empty value for the option.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set --global image-pull-secrets
|
||||
```
|
||||
|
||||
#### Enabling letsencrypt integration
|
||||
|
||||
By default, letsencrypt is disabled and https port mappings are ignored. To enable, set the `letsencrypt-email-prod` or `letsencrypt-email-stag` property with the `--global` flag:
|
||||
|
||||
```shell
|
||||
# set the value for prod
|
||||
dokku scheduler-k3s:set --global letsencrypt-email-prod automated@dokku.sh
|
||||
|
||||
# set the value for stag
|
||||
dokku scheduler-k3s:set --global letsencrypt-email-stag automated@dokku.sh
|
||||
```
|
||||
|
||||
After enabling, all apps with an `https` port mapping that utilize the related letsencrypt server will be automatically updated to enable ssl. All http requests will then be redirected to https.
|
||||
|
||||
#### Customizing the letsencrypt server
|
||||
|
||||
The letsencrypt integration is set to the production letsencrypt server by default. This can be changed on an app-level by setting the `letsencrypt-server` property with the `scheduler-k3s:set` command
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set node-js-app letsencrypt-server staging
|
||||
```
|
||||
|
||||
The default value may be set by passing an empty value for the option:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set node-js-app letsencrypt-server
|
||||
```
|
||||
|
||||
The `image-pull-secrets` property can also be set globally. The global default is `production`.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set --global letsencrypt-server staging
|
||||
```
|
||||
|
||||
The default value may be set by passing an empty value for the option.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set --global letsencrypt-server staging
|
||||
```
|
||||
|
||||
After changing, all apps with an
|
||||
|
||||
### Using kubectl remotely
|
||||
|
||||
> [!WARNING]
|
||||
> Certain ports must be open for interacting with the remote kubernets api. Refer to the [K3s networking documentation](https://docs.k3s.io/installation/requirements?os=debian#networking) for the required open ports between servers prior to running the command.
|
||||
|
||||
By default, Dokku assumes that all it controls all actions on the cluster, and thus does not expose the `kubectl` binary for administrators. To interact with kubectl, you will need to retrieve the `kubeconfig` for the cluster and configure your client to use that configuration.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:show-kubeconfig
|
||||
```
|
||||
|
||||
## Tutorial
|
||||
|
||||
### Single-node cluster
|
||||
|
||||
Initialize the cluster in single-node mode. This will start k3s on the Dokku node itself.
|
||||
|
||||
```shell
|
||||
# must be run as root
|
||||
dokku scheduler-k3s:initialize
|
||||
```
|
||||
|
||||
The above command will initialize a cluster with the following configuration:
|
||||
|
||||
- etcd distributed backing store
|
||||
- Wireguard as the networking flannel
|
||||
- K3s automatic upgrader
|
||||
- Longhorn distributed block storage
|
||||
- Traefik configured to run on all nodes in the cluster
|
||||
|
||||
Additionally, an internal token for authentication will be automatically generated. This token should be stored securely for later recovery, and can be displayed with via the `scheduler-k3s:report` command:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:report --global
|
||||
```
|
||||
|
||||
```
|
||||
=====> --global scheduler-k3s information
|
||||
scheduler k3s token: topsecret:server:token
|
||||
```
|
||||
|
||||
Setup registry authentication. The K3s scheduler plugin will automatically pick up any newly configured registry backends and ensure nodes in the cluster have the credentials in place for pulling images from the cluster.
|
||||
|
||||
```shell
|
||||
# hub.docker.com
|
||||
dokku registry:login hub.docker.com $USERNAME $PASSWORD
|
||||
```
|
||||
|
||||
To ensure images are pushed and pulled from the correct registry, set the correct `server` registry property. This can be set on a per-app basis, but we will set it globally here for this tutorial.
|
||||
|
||||
```shell
|
||||
dokku registry:set --global server hub.docker.com
|
||||
```
|
||||
|
||||
If using docker hub, you'll need to use a custom repository name. This can be set via a global template, allowing users access to the app name as the variable `AppName` as shown below.
|
||||
|
||||
```shell
|
||||
dokku registry:set --global image-repo-template "my-awesome-prefix/{{ .AppName }}"
|
||||
```
|
||||
|
||||
Additionally, apps should be configured to push images on the release phase via the `push-on-release` registry property.
|
||||
|
||||
```shell
|
||||
dokku registry:set --global push-on-release true
|
||||
```
|
||||
|
||||
As routing is handled by traefik managed on the k3s plugin, set the proxy plugin to `k3s` as well.
|
||||
|
||||
```shell
|
||||
dokku proxy:set --global k3s
|
||||
```
|
||||
|
||||
Ensure any other proxy implementations are disabled. Typically at least nginx will be running on the Dokku host and should be stopped if the host is used as load balancer.
|
||||
|
||||
```shell
|
||||
dokku nginx:stop
|
||||
```
|
||||
|
||||
Finally, set the scheduler to `k3s` so that app deploys will work on k3s.
|
||||
|
||||
```shell
|
||||
dokku scheduler:set --global selected k3s
|
||||
```
|
||||
|
||||
At this point, all app deploys will be performed against the k3s cluster.
|
||||
|
||||
> [!NOTE]
|
||||
> HTTP requests for apps can be performed against any node in the cluster. Without extra configuration, many other ports may also be available on the host. For security reasons, it may be desirable to place the k3s cluster behind one or more TCP load balancers while shutting off traffic to all cluster ports. Please consult your hosting provider for more information on how to provision a TCP load balancer and shut off all ports other than 22/80/443 access to the outside world.
|
||||
|
||||
### Running a multi-cluster node
|
||||
|
||||
> [!WARNING]
|
||||
> Certain ports must be open for cross-server communication. Refer to the [K3s networking documentation](https://docs.k3s.io/installation/requirements?os=debian#networking) for the required open ports between servers prior to running the command.
|
||||
|
||||
For high-availability, it is recommended to add both worker and server nodes to the cluster. Dokku will default to starting the cluster with an embedded Etcd database backend, and is ready to add new worker or server nodes immediately.
|
||||
|
||||
When attaching an worker or server node, the K3s plugin will look at the IP associated with the `eth0` interface and use that to connect the new node to the cluster. To change this, set the `network-interface` property to the appropriate value.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:set --global network-interface eth1
|
||||
```
|
||||
|
||||
Dokku will connect to remote servers via the `root` user with the `dokku` user's SSH key pair. Dokku servers may not have an ssh key pair by default, but they can be generated as needed via the `git:generate-deploy-key` command.
|
||||
|
||||
```shell
|
||||
dokku git:generate-deploy-key
|
||||
```
|
||||
|
||||
This key can then be displayed with the `git:public-key` command, and copied to the remote server's `/root/.ssh/authorized_keys` file.
|
||||
|
||||
```shell
|
||||
dokku git:public-key
|
||||
```
|
||||
|
||||
Multiple server nodes can be added with the `scheduler-k3s:cluster-add` command. This will ssh onto the specified server, install k3s, and join it to the current Dokku node in server mode.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:cluster-add --role server ssh://root@server-1.example.com
|
||||
```
|
||||
|
||||
Server nodes allow any workloads to be scheduled on them by default, in addition to the control-plane, etcd, and the scheduler itself. To avoid app workloads being scheduled on your control-plane, use the `--taint-scheduling` flag:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:cluster-add --role server --taint-scheduling ssh://root@server-1.example.com
|
||||
```
|
||||
|
||||
If the server isn't in the `known_hosts` file, the connection will fail. This can be bypassed by setting the `--insecure-allow-unknown-hosts` flag:
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:cluster-add --role server --insecure-allow-unknown-hosts ssh://root@worker-1.example.com
|
||||
```
|
||||
|
||||
Server nodes are typically used to replicate the cluster state, and it is recommended to have an odd number of nodes spread across several availability zones (datacenters in close proximity within a region). This allows for higher availability in the event of a cluster failure. Server nodes run control-plane services such as the traefik load balancer and the etcd backing store.
|
||||
|
||||
> [!NOTE]
|
||||
> Only the initial Dokku server will be properly configured for push deployment, and should be considered your git remote. Additional server nodes are for ensuring high-availability of the K3s etcd state. Ensure this server is properly backed up and restorable or deployments will not work.
|
||||
|
||||
Worker nodes are used to run. To add an worker, run the `scheduler-k3s:cluster-add` with the `--role worker` flag. This will ssh onto the specified server, install k3s, and join it to the current Dokku node in worker mode. Workers are typically used to run app workloads.
|
||||
|
||||
```shell
|
||||
dokku scheduler-k3s:cluster-add --role worker ssh://root@worker-1.example.com
|
||||
```
|
||||
|
||||
## Scheduler Interface
|
||||
|
||||
The following sections describe implemented and unimplemented scheduler functionality for the `k3s` scheduler.
|
||||
|
||||
### Implemented Commands and Triggers
|
||||
|
||||
This plugin implements various functionality through `plugn` triggers to integrate with Docker for running apps on a single server. The following functionality is supported by the `scheduler-k3s` plugin.
|
||||
|
||||
- `apps:clone`
|
||||
- `apps:destroy`
|
||||
- `apps:rename`
|
||||
- `cron`
|
||||
- `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
|
||||
- `logs`
|
||||
- `ps:stop`
|
||||
- `run`
|
||||
- The `scheduler-post-run` trigger is not always triggered
|
||||
- `run:detached`
|
||||
- `run:list`
|
||||
|
||||
### Unimplemented command functionality
|
||||
|
||||
- `run:logs`
|
||||
- `ps:inspect`
|
||||
|
||||
The following Dokku functionality is not implemented at this time.
|
||||
|
||||
- `vector` log integration
|
||||
- persistent storage
|
||||
|
||||
### Logging support
|
||||
|
||||
App logs for the `logs` command are fetched by Dokku from running containers via the `kubectl` cli. Persisting logs via Vector is not implemented at this time. Users may choose to configure the Vector Kubernetes integration directly by following [this guide](https://vector.dev/docs/setup/installation/platforms/kubernetes/).
|
||||
|
||||
### Supported Resource Management Properties
|
||||
|
||||
The `k3s` scheduler supports a minimal list of resource _limits_ and _reservations_. The following properties are supported:
|
||||
|
||||
#### Resource Limits
|
||||
|
||||
> [!NOTE]
|
||||
> Cron tasks retrieve resource limits based on the computed cron task ID. If unspecified, the default will be 1 CPU and 512m RAM.
|
||||
|
||||
- cpu: is specified in number of CPUs a process can access.
|
||||
- memory: should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes). Default unit is `m` (megabytes).
|
||||
|
||||
#### Resource Reservations
|
||||
|
||||
> [!NOTE]
|
||||
> Cron tasks retrieve resource reservations based on the computed cron task ID. If unspecified, the default will be 1 CPU and 512m RAM.
|
||||
|
||||
- cpu: is specified in number of CPUs a process can access.
|
||||
- memory: should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes). Default unit is `m` (megabytes).
|
||||
4
dokku
4
dokku
@@ -89,14 +89,14 @@ fi
|
||||
|
||||
if [[ $(id -un) != "dokku" ]]; then
|
||||
unset TMP TMPDIR TEMP TEMPDIR
|
||||
if [[ ! $1 =~ plugin:* ]] && [[ $1 != "ssh-keys:add" ]] && [[ $1 != "ssh-keys:remove" ]]; then
|
||||
if [[ ! $1 =~ plugin:* ]] && [[ $1 != "ssh-keys:add" ]] && [[ $1 != "ssh-keys:remove" ]] && [[ $1 != "scheduler-k3s:initialize" ]] && [[ $1 != "scheduler-k3s:uninstall" ]]; then
|
||||
export SSH_USER=$(id -un)
|
||||
sudo -u dokku -E -H "$0" "$@"
|
||||
exit $?
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $1 =~ ^plugin:.* && $1 != "plugin:help" && $1 != "plugin:list" ]] || [[ $1 == "ssh-keys:add" ]] || [[ $1 == "ssh-keys:remove" ]]; then
|
||||
if [[ $1 =~ ^plugin:.* && $1 != "plugin:help" && $1 != "plugin:list" ]] || [[ $1 == "ssh-keys:add" ]] || [[ $1 == "ssh-keys:remove" ]] || [[ $1 == "scheduler-k3s:initialize" ]] || [[ $1 == "scheduler-k3s:uninstall" ]]; then
|
||||
if [[ $(id -un) != "root" ]]; then
|
||||
dokku_log_fail "This command must be run as root"
|
||||
else
|
||||
|
||||
1
go.work
1
go.work
@@ -20,4 +20,5 @@ use (
|
||||
./plugins/resource
|
||||
./plugins/scheduler
|
||||
./plugins/scheduler-docker-local
|
||||
./plugins/scheduler-k3s
|
||||
)
|
||||
|
||||
@@ -18,20 +18,17 @@ import (
|
||||
|
||||
// SftpCopyInput is the input for CallSftpCopy
|
||||
type SftpCopyInput struct {
|
||||
// SourcePath is the path to the file to copy
|
||||
SourcePath string
|
||||
// AllowUknownHosts allows connecting to hosts with unknown host keys
|
||||
AllowUknownHosts bool
|
||||
|
||||
// DestinationPath is the path to copy the file to
|
||||
DestinationPath string
|
||||
|
||||
// AllowUknownHosts allows connecting to hosts with unknown host keys
|
||||
AllowUknownHosts bool
|
||||
|
||||
// RemoteHost is the remote host to connect to
|
||||
RemoteHost string
|
||||
|
||||
// Sudo runs the command with sudo -n -u root
|
||||
Sudo bool
|
||||
// SourcePath is the path to the file to copy
|
||||
SourcePath string
|
||||
}
|
||||
|
||||
// CallSftpCopy copies a file to a remote host via sftp
|
||||
@@ -226,14 +223,7 @@ func (task SftpCopyTask) Execute(ctx context.Context) (SftpCopyResult, error) {
|
||||
return SftpCopyResult{}, fmt.Errorf("failed to create sftp client: %w", err)
|
||||
}
|
||||
|
||||
srcFile, err := os.Open(task.SourcePath)
|
||||
if err != nil {
|
||||
return SftpCopyResult{}, fmt.Errorf("failed to open source file: %w", err)
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
var contents []byte
|
||||
_, err = srcFile.Read(contents)
|
||||
contents, err := os.ReadFile(task.SourcePath)
|
||||
if err != nil {
|
||||
return SftpCopyResult{}, fmt.Errorf("failed to read source file: %w", err)
|
||||
}
|
||||
|
||||
@@ -244,6 +244,10 @@ func (task SshTask) Execute(ctx context.Context) (SshResult, error) {
|
||||
task.Port = 22
|
||||
}
|
||||
|
||||
if err := TouchFile(filepath.Join(os.Getenv("DOKKU_ROOT"), ".ssh", "known_hosts")); err != nil {
|
||||
return SshResult{}, fmt.Errorf("failed to touch known_hosts file: %w", err)
|
||||
}
|
||||
|
||||
auth, err := goph.Key(task.SshKeyPath, "")
|
||||
if err != nil {
|
||||
return SshResult{}, fmt.Errorf("failed to load ssh key: %w", err)
|
||||
|
||||
@@ -25,10 +25,12 @@ func CommandDefault(appName string, num int64, process string, tail, quiet bool)
|
||||
q := strconv.FormatBool(quiet)
|
||||
n := strconv.FormatInt(num, 10)
|
||||
|
||||
if err := common.PlugnTrigger("scheduler-logs", []string{s, appName, process, t, q, n}...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
|
||||
Args: []string{s, appName, process, t, q, n},
|
||||
StreamStdio: true,
|
||||
Trigger: "scheduler-logs",
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// CommandFailed shows the last failed deploy logs
|
||||
|
||||
8
plugins/scheduler-k3s/.gitignore
vendored
Normal file
8
plugins/scheduler-k3s/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/commands
|
||||
/subcommands/*
|
||||
/triggers/*
|
||||
/triggers
|
||||
/scheduler-*
|
||||
/install
|
||||
/post-*
|
||||
/report
|
||||
6
plugins/scheduler-k3s/Makefile
Normal file
6
plugins/scheduler-k3s/Makefile
Normal file
@@ -0,0 +1,6 @@
|
||||
SUBCOMMANDS = subcommands/initialize subcommands/cluster-add subcommands/cluster-list subcommands/cluster-remove 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/post-registry-login triggers/report triggers/scheduler-deploy triggers/scheduler-enter triggers/scheduler-logs triggers/scheduler-post-delete triggers/scheduler-run triggers/scheduler-run-list triggers/scheduler-stop
|
||||
BUILD = commands subcommands triggers
|
||||
PLUGIN_NAME = scheduler-k3s
|
||||
|
||||
include ../../common.mk
|
||||
903
plugins/scheduler-k3s/functions.go
Normal file
903
plugins/scheduler-k3s/functions.go
Normal file
@@ -0,0 +1,903 @@
|
||||
package scheduler_k3s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
appjson "github.com/dokku/dokku/plugins/app-json"
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
"gopkg.in/yaml.v3"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
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"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
)
|
||||
|
||||
// EnterPodInput contains all the information needed to enter a pod
|
||||
type EnterPodInput struct {
|
||||
// Clientset is the kubernetes clientset
|
||||
Clientset KubernetesClient
|
||||
|
||||
// Command is the command to run
|
||||
Command []string
|
||||
|
||||
// Entrypoint is the entrypoint to run
|
||||
Entrypoint string
|
||||
|
||||
// SelectedContainerName is the container name to enter
|
||||
SelectedContainerName string
|
||||
|
||||
// SelectedPod is the pod to enter
|
||||
SelectedPod v1.Pod
|
||||
|
||||
// WaitTimeout is the timeout to wait for the pod to be ready
|
||||
WaitTimeout int
|
||||
}
|
||||
|
||||
// Node contains information about a node
|
||||
type Node struct {
|
||||
// Name is the name of the node
|
||||
Name string
|
||||
|
||||
// Roles is the roles of the node
|
||||
Roles []string
|
||||
|
||||
// Ready is whether the node is ready
|
||||
Ready bool
|
||||
|
||||
// RemoteHost is the remote host
|
||||
RemoteHost string
|
||||
|
||||
// Version is the version of the node
|
||||
Version string
|
||||
}
|
||||
|
||||
// String returns a string representation of the node
|
||||
func (n Node) String() string {
|
||||
return fmt.Sprintf("%s|%s|%s|%s", n.Name, strconv.FormatBool(n.Ready), strings.Join(n.Roles, ","), n.Version)
|
||||
}
|
||||
|
||||
// StartCommandInput contains all the information needed to get the start command
|
||||
type StartCommandInput struct {
|
||||
// AppName is the name of the app
|
||||
AppName string
|
||||
// ProcessType is the process type
|
||||
ProcessType string
|
||||
// ImageSourceType is the image source type
|
||||
ImageSourceType string
|
||||
// Port is the port
|
||||
Port int32
|
||||
// Env is the environment variables
|
||||
Env map[string]string
|
||||
}
|
||||
|
||||
// StartCommandOutput contains the start command
|
||||
type StartCommandOutput struct {
|
||||
// Command is the start command
|
||||
Command []string
|
||||
}
|
||||
|
||||
type WaitForNodeToExistInput struct {
|
||||
Clientset KubernetesClient
|
||||
Namespace string
|
||||
RetryCount int
|
||||
NodeName string
|
||||
}
|
||||
|
||||
type WaitForPodBySelectorRunningInput struct {
|
||||
Clientset KubernetesClient
|
||||
Namespace string
|
||||
LabelSelector string
|
||||
PodName string
|
||||
Timeout int
|
||||
Waiter func(ctx context.Context, clientset KubernetesClient, podName, namespace string) wait.ConditionWithContextFunc
|
||||
}
|
||||
|
||||
type WaitForPodToExistInput struct {
|
||||
Clientset KubernetesClient
|
||||
Namespace string
|
||||
RetryCount int
|
||||
PodName string
|
||||
LabelSelector string
|
||||
}
|
||||
|
||||
func applyClusterIssuers(ctx context.Context, appName string) (string, bool, error) {
|
||||
chartDir, err := os.MkdirTemp("", "cluster-issuer-chart-")
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("Error creating cluster-issuer chart directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(chartDir)
|
||||
|
||||
// create the chart.yaml
|
||||
chart := &Chart{
|
||||
ApiVersion: "v2",
|
||||
AppVersion: "1.0.0",
|
||||
Name: appName,
|
||||
Version: "0.0.1",
|
||||
}
|
||||
|
||||
err = writeYaml(WriteYamlInput{
|
||||
Object: chart,
|
||||
Path: filepath.Join(chartDir, "Chart.yaml"),
|
||||
})
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("Error writing cluster-issuer chart: %w", err)
|
||||
}
|
||||
|
||||
// create the values.yaml
|
||||
issuerName := "letsencrypt-stag"
|
||||
server := getComputedLetsencryptServer(appName)
|
||||
if server == "prod" || server == "production" {
|
||||
issuerName = "letsencrypt-prod"
|
||||
} else if server != "stag" && server != "staging" {
|
||||
return "", false, fmt.Errorf("Invalid letsencrypt server config: %s", server)
|
||||
}
|
||||
|
||||
tls := false
|
||||
letsencryptEmailStag := getGlobalLetsencryptEmailStag()
|
||||
letsencryptEmailProd := getGlobalLetsencryptEmailProd()
|
||||
if issuerName == "letsencrypt-stag" {
|
||||
tls = letsencryptEmailStag != ""
|
||||
}
|
||||
if issuerName == "letsencrypt-prod" {
|
||||
tls = letsencryptEmailProd != ""
|
||||
}
|
||||
|
||||
clusterIssuerValues := ClusterIssuerValues{
|
||||
ClusterIssuers: map[string]ClusterIssuer{
|
||||
"letsencrypt-stag": {
|
||||
Email: letsencryptEmailStag,
|
||||
Enabled: letsencryptEmailStag != "",
|
||||
Name: "letsencrypt-stag",
|
||||
Server: "https://acme-staging-v02.api.letsencrypt.org/directory",
|
||||
},
|
||||
"letsencrypt-prod": {
|
||||
Email: letsencryptEmailProd,
|
||||
Enabled: letsencryptEmailProd != "",
|
||||
Name: "letsencrypt-prod",
|
||||
Server: "https://acme-v02.api.letsencrypt.org/directory",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Join(chartDir, "templates"), os.FileMode(0755)); err != nil {
|
||||
return "", false, fmt.Errorf("Error creating cluster-issuer chart templates directory: %w", err)
|
||||
}
|
||||
|
||||
err = writeYaml(WriteYamlInput{
|
||||
Object: clusterIssuerValues,
|
||||
Path: filepath.Join(chartDir, "values.yaml"),
|
||||
})
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("Error writing chart: %w", err)
|
||||
}
|
||||
|
||||
// create the templates/cluster-issuer.yaml
|
||||
b, err := templates.ReadFile("templates/chart/cluster-issuer.yaml")
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("Error reading cluster-issuer template: %w", err)
|
||||
}
|
||||
|
||||
filename := filepath.Join(chartDir, "templates", "cluster-issuer.yaml")
|
||||
err = os.WriteFile(filename, b, os.FileMode(0644))
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("Error writing cluster-issuer template: %w", err)
|
||||
}
|
||||
|
||||
if os.Getenv("DOKKU_TRACE") == "1" {
|
||||
common.CatFile(filename)
|
||||
}
|
||||
|
||||
// install the chart
|
||||
helmAgent, err := NewHelmAgent("cert-manager", DevNullPrinter)
|
||||
if err != nil {
|
||||
return issuerName, tls, fmt.Errorf("Error creating helm agent: %w", err)
|
||||
}
|
||||
|
||||
chartPath, err := filepath.Abs(chartDir)
|
||||
if err != nil {
|
||||
return issuerName, tls, fmt.Errorf("Error getting chart path: %w", err)
|
||||
}
|
||||
|
||||
timeoutDuration, err := time.ParseDuration("300s")
|
||||
if err != nil {
|
||||
return issuerName, tls, fmt.Errorf("Error parsing deploy timeout duration: %w", err)
|
||||
}
|
||||
|
||||
err = helmAgent.InstallOrUpgradeChart(ctx, ChartInput{
|
||||
ChartPath: chartPath,
|
||||
Namespace: "cert-manager",
|
||||
ReleaseName: "cluster-issuers",
|
||||
RollbackOnFailure: true,
|
||||
Timeout: timeoutDuration,
|
||||
})
|
||||
if err != nil {
|
||||
return issuerName, tls, fmt.Errorf("Error installing cluster-issuer chart: %w", err)
|
||||
}
|
||||
|
||||
return issuerName, tls, nil
|
||||
}
|
||||
|
||||
func createKubernetesNamespace(ctx context.Context, namespaceName string) error {
|
||||
clientset, err := NewKubernetesClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namespace := corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespaceName,
|
||||
Annotations: map[string]string{
|
||||
"dokku.com/managed": "true",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"dokku.com/managed": "true",
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = clientset.CreateNamespace(ctx, CreateNamespaceInput{
|
||||
Name: namespace,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
labelSelector := []string{}
|
||||
for k, v := range input.SelectedPod.Labels {
|
||||
labelSelector = append(labelSelector, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
|
||||
if input.WaitTimeout > 0 {
|
||||
input.WaitTimeout = 5
|
||||
}
|
||||
|
||||
err = waitForPodBySelectorRunning(ctx, WaitForPodBySelectorRunningInput{
|
||||
Clientset: input.Clientset,
|
||||
Namespace: input.SelectedPod.Namespace,
|
||||
LabelSelector: strings.Join(labelSelector, ","),
|
||||
PodName: input.SelectedPod.Name,
|
||||
Timeout: input.WaitTimeout,
|
||||
Waiter: isPodReady,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error waiting for pod to be ready: %w", err)
|
||||
}
|
||||
|
||||
defaultContainerName, hasDefaultContainer := input.SelectedPod.Annotations["kubectl.kubernetes.io/default-container"]
|
||||
if input.SelectedContainerName == "" && hasDefaultContainer {
|
||||
input.SelectedContainerName = defaultContainerName
|
||||
}
|
||||
if input.SelectedContainerName == "" {
|
||||
return fmt.Errorf("No container specified and no default container found")
|
||||
}
|
||||
|
||||
req := coreclient.RESTClient().Post().
|
||||
Resource("pods").
|
||||
Namespace(input.SelectedPod.Namespace).
|
||||
Name(input.SelectedPod.Name).
|
||||
SubResource("exec")
|
||||
|
||||
req.Param("container", input.SelectedContainerName)
|
||||
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" {
|
||||
return "/start " + input.ProcessType
|
||||
}
|
||||
|
||||
resp, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
|
||||
Trigger: "config-get",
|
||||
Args: []string{input.AppName, "DOKKU_START_CMD"},
|
||||
CaptureOutput: true,
|
||||
StreamStdio: false,
|
||||
})
|
||||
if err == nil && resp.ExitCode == 0 && len(resp.Stdout) > 0 {
|
||||
command = strings.TrimSpace(resp.Stdout)
|
||||
}
|
||||
|
||||
if input.ImageSourceType == "dockerfile" {
|
||||
resp, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
|
||||
Trigger: "config-get",
|
||||
Args: []string{input.AppName, "DOKKU_DOCKERFILE_START_CMD"},
|
||||
CaptureOutput: true,
|
||||
StreamStdio: false,
|
||||
})
|
||||
if err == nil && resp.ExitCode == 0 && len(resp.Stdout) > 0 {
|
||||
command = strings.TrimSpace(resp.Stdout)
|
||||
}
|
||||
}
|
||||
|
||||
if command == "" {
|
||||
procfileStartCommand, _ := common.PlugnTriggerOutputAsString("procfile-get-command", []string{input.AppName, input.ProcessType, fmt.Sprint(input.Port)}...)
|
||||
if procfileStartCommand != "" {
|
||||
command = procfileStartCommand
|
||||
}
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
func getDeployTimeout(appName string) string {
|
||||
return common.PropertyGetDefault("scheduler-k3s", appName, "deploy-timeout", "")
|
||||
}
|
||||
|
||||
func getGlobalDeployTimeout() string {
|
||||
return common.PropertyGetDefault("scheduler-k3s", "--global", "deploy-timeout", "300s")
|
||||
}
|
||||
|
||||
func getComputedDeployTimeout(appName string) string {
|
||||
deployTimeout := getDeployTimeout(appName)
|
||||
if deployTimeout == "" {
|
||||
deployTimeout = getGlobalDeployTimeout()
|
||||
}
|
||||
|
||||
return deployTimeout
|
||||
}
|
||||
|
||||
func getImagePullSecrets(appName string) string {
|
||||
return common.PropertyGetDefault("scheduler-k3s", appName, "image-pull-secrets", "")
|
||||
}
|
||||
|
||||
func getGlobalImagePullSecrets() string {
|
||||
return common.PropertyGetDefault("scheduler-k3s", "--global", "image-pull-secrets", "")
|
||||
}
|
||||
|
||||
func getComputedImagePullSecrets(appName string) string {
|
||||
imagePullSecrets := getImagePullSecrets(appName)
|
||||
if imagePullSecrets == "" {
|
||||
imagePullSecrets = getGlobalImagePullSecrets()
|
||||
}
|
||||
|
||||
return imagePullSecrets
|
||||
}
|
||||
|
||||
func getLetsencryptServer(appName string) string {
|
||||
return common.PropertyGetDefault("scheduler-k3s", appName, "letsencrypt-server", "")
|
||||
}
|
||||
|
||||
func getGlobalLetsencryptServer() string {
|
||||
return common.PropertyGetDefault("scheduler-k3s", "--global", "letsencrypt-server", "prod")
|
||||
}
|
||||
|
||||
func getComputedLetsencryptServer(appName string) string {
|
||||
letsencryptServer := getLetsencryptServer(appName)
|
||||
if letsencryptServer == "" {
|
||||
letsencryptServer = getGlobalLetsencryptServer()
|
||||
}
|
||||
|
||||
return letsencryptServer
|
||||
}
|
||||
|
||||
func getGlobalLetsencryptEmailProd() string {
|
||||
return common.PropertyGetDefault("scheduler-k3s", "--global", "letsencrypt-email-prod", "")
|
||||
}
|
||||
|
||||
func getGlobalLetsencryptEmailStag() string {
|
||||
return common.PropertyGetDefault("scheduler-k3s", "--global", "letsencrypt-email-stag", "")
|
||||
}
|
||||
|
||||
func getNamespace(appName string) string {
|
||||
return common.PropertyGetDefault("scheduler-k3s", appName, "namespace", "")
|
||||
}
|
||||
|
||||
func getGlobalNamespace() string {
|
||||
return common.PropertyGetDefault("scheduler-k3s", "--global", "namespace", "default")
|
||||
}
|
||||
|
||||
func getComputedNamespace(appName string) string {
|
||||
namespace := getNamespace(appName)
|
||||
if namespace == "" {
|
||||
namespace = getGlobalNamespace()
|
||||
}
|
||||
|
||||
return namespace
|
||||
}
|
||||
|
||||
func getGlobalNetworkInterface() string {
|
||||
return common.PropertyGetDefault("scheduler-k3s", "--global", "network-interface", "eth0")
|
||||
}
|
||||
|
||||
func getRollbackOnFailure(appName string) string {
|
||||
return common.PropertyGetDefault("scheduler-k3s", appName, "rollback-on-failure", "")
|
||||
}
|
||||
|
||||
func getGlobalRollbackOnFailure() string {
|
||||
return common.PropertyGetDefault("scheduler-k3s", "--global", "rollback-on-failure", "false")
|
||||
}
|
||||
|
||||
func getComputedRollbackOnFailure(appName string) string {
|
||||
rollbackOnFailure := getRollbackOnFailure(appName)
|
||||
if rollbackOnFailure == "" {
|
||||
rollbackOnFailure = getGlobalRollbackOnFailure()
|
||||
}
|
||||
|
||||
return rollbackOnFailure
|
||||
}
|
||||
|
||||
func getGlobalGlobalToken() string {
|
||||
return common.PropertyGet("scheduler-k3s", "--global", "token")
|
||||
}
|
||||
|
||||
func getProcessHealtchecks(healthchecks []appjson.Healthcheck, primaryPort int32) ProcessHealthchecks {
|
||||
if len(healthchecks) == 0 {
|
||||
return ProcessHealthchecks{}
|
||||
}
|
||||
|
||||
livenessChecks := []ProcessHealthcheck{}
|
||||
readinessChecks := []ProcessHealthcheck{}
|
||||
startupChecks := []ProcessHealthcheck{}
|
||||
uptimeSeconds := []int32{}
|
||||
for _, healthcheck := range healthchecks {
|
||||
probe := ProcessHealthcheck{
|
||||
InitialDelaySeconds: healthcheck.InitialDelay,
|
||||
PeriodSeconds: healthcheck.Wait,
|
||||
TimeoutSeconds: healthcheck.Timeout,
|
||||
FailureThreshold: healthcheck.Attempts,
|
||||
SuccessThreshold: int32(1),
|
||||
}
|
||||
if len(healthcheck.Command) > 0 {
|
||||
probe.Exec = &ExecHealthcheck{
|
||||
Command: healthcheck.Command,
|
||||
}
|
||||
} else if healthcheck.Listening {
|
||||
probe.TCPSocket = &TCPHealthcheck{
|
||||
Port: primaryPort,
|
||||
}
|
||||
for _, header := range healthcheck.HTTPHeaders {
|
||||
if header.Name == "Host" {
|
||||
probe.TCPSocket.Host = header.Value
|
||||
}
|
||||
}
|
||||
} else if healthcheck.Path != "" {
|
||||
probe.HTTPGet = &HTTPHealthcheck{
|
||||
Path: healthcheck.Path,
|
||||
Port: primaryPort,
|
||||
HTTPHeaders: []HTTPHeader{},
|
||||
}
|
||||
|
||||
if healthcheck.Scheme != "" {
|
||||
probe.HTTPGet.Scheme = URIScheme(strings.ToUpper(healthcheck.Scheme))
|
||||
}
|
||||
|
||||
for _, header := range healthcheck.HTTPHeaders {
|
||||
probe.HTTPGet.HTTPHeaders = append(probe.HTTPGet.HTTPHeaders, HTTPHeader{
|
||||
Name: header.Name,
|
||||
Value: header.Value,
|
||||
})
|
||||
}
|
||||
} else if healthcheck.Uptime > 0 {
|
||||
uptimeSeconds = append(uptimeSeconds, healthcheck.Uptime)
|
||||
}
|
||||
|
||||
if healthcheck.Type == appjson.HealthcheckType_Liveness {
|
||||
livenessChecks = append(livenessChecks, probe)
|
||||
} else if healthcheck.Type == appjson.HealthcheckType_Readiness {
|
||||
readinessChecks = append(readinessChecks, probe)
|
||||
} else if healthcheck.Type == appjson.HealthcheckType_Startup {
|
||||
startupChecks = append(startupChecks, probe)
|
||||
}
|
||||
}
|
||||
if len(livenessChecks) > 1 {
|
||||
common.LogWarn("Multiple liveness checks are not supported, only the first one will be used")
|
||||
}
|
||||
if len(readinessChecks) > 1 {
|
||||
common.LogWarn("Multiple readiness checks are not supported, only the first one will be used")
|
||||
}
|
||||
if len(startupChecks) > 1 {
|
||||
common.LogWarn("Multiple startup checks are not supported, only the first one will be used")
|
||||
}
|
||||
if len(uptimeSeconds) > 1 {
|
||||
common.LogWarn("Multiple uptime checks are not supported, only the first one will be used")
|
||||
}
|
||||
|
||||
processHealthchecks := ProcessHealthchecks{}
|
||||
if len(livenessChecks) > 0 {
|
||||
processHealthchecks.Liveness = livenessChecks[0]
|
||||
}
|
||||
if len(readinessChecks) > 0 {
|
||||
processHealthchecks.Readiness = readinessChecks[0]
|
||||
}
|
||||
if len(startupChecks) > 0 {
|
||||
processHealthchecks.Startup = startupChecks[0]
|
||||
}
|
||||
if len(uptimeSeconds) > 0 {
|
||||
processHealthchecks.MinReadySeconds = uptimeSeconds[0]
|
||||
}
|
||||
|
||||
return processHealthchecks
|
||||
}
|
||||
|
||||
func getProcessResources(appName string, processType string) (ProcessResourcesMap, error) {
|
||||
processResources := ProcessResourcesMap{
|
||||
Limits: ProcessResources{
|
||||
CPU: "1000m",
|
||||
Memory: "512Mi",
|
||||
},
|
||||
Requests: ProcessResources{
|
||||
CPU: "1000m",
|
||||
Memory: "512Mi",
|
||||
},
|
||||
}
|
||||
cpuLimit, err := common.PlugnTriggerOutputAsString("resource-get-property", []string{appName, processType, "limit", "cpu"}...)
|
||||
if err != nil && cpuLimit != "" && cpuLimit != "0" {
|
||||
_, err := resource.ParseQuantity(cpuLimit)
|
||||
if err != nil {
|
||||
return ProcessResourcesMap{}, fmt.Errorf("Error parsing cpu limit: %w", err)
|
||||
}
|
||||
processResources.Limits.CPU = cpuLimit
|
||||
}
|
||||
nvidiaGpuLimit, err := common.PlugnTriggerOutputAsString("resource-get-property", []string{appName, processType, "limit", "nvidia-gpu"}...)
|
||||
if err != nil && nvidiaGpuLimit != "" && nvidiaGpuLimit != "0" {
|
||||
_, err := resource.ParseQuantity(nvidiaGpuLimit)
|
||||
if err != nil {
|
||||
return ProcessResourcesMap{}, fmt.Errorf("Error parsing nvidia-gpu limit: %w", err)
|
||||
}
|
||||
processResources.Limits.NvidiaGPU = nvidiaGpuLimit
|
||||
}
|
||||
memoryLimit, err := common.PlugnTriggerOutputAsString("resource-get-property", []string{appName, processType, "limit", "memory"}...)
|
||||
if err != nil && memoryLimit != "" && memoryLimit != "0" {
|
||||
_, err := resource.ParseQuantity(memoryLimit)
|
||||
if err != nil {
|
||||
return ProcessResourcesMap{}, fmt.Errorf("Error parsing memory limit: %w", err)
|
||||
}
|
||||
processResources.Limits.Memory = memoryLimit
|
||||
}
|
||||
|
||||
cpuRequest, err := common.PlugnTriggerOutputAsString("resource-get-property", []string{appName, processType, "reserve", "cpu"}...)
|
||||
if err != nil && cpuRequest != "" && cpuRequest != "0" {
|
||||
_, err := resource.ParseQuantity(cpuRequest)
|
||||
if err != nil {
|
||||
return ProcessResourcesMap{}, fmt.Errorf("Error parsing cpu request: %w", err)
|
||||
}
|
||||
processResources.Requests.CPU = cpuRequest
|
||||
}
|
||||
memoryRequest, err := common.PlugnTriggerOutputAsString("resource-get-property", []string{appName, processType, "reserve", "memory"}...)
|
||||
if err != nil && memoryRequest != "" && memoryRequest != "0" {
|
||||
_, err := resource.ParseQuantity(memoryRequest)
|
||||
if err != nil {
|
||||
return ProcessResourcesMap{}, fmt.Errorf("Error parsing memory request: %w", err)
|
||||
}
|
||||
processResources.Requests.Memory = memoryRequest
|
||||
}
|
||||
|
||||
return processResources, nil
|
||||
}
|
||||
|
||||
func getServerIP() (string, error) {
|
||||
serverIP := ""
|
||||
networkInterface := getGlobalNetworkInterface()
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Unable to get network interfaces: %w", err)
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
if iface.Name == networkInterface {
|
||||
addr, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Unable to get network addresses for interface %s: %w", networkInterface, err)
|
||||
}
|
||||
for _, a := range addr {
|
||||
if ipnet, ok := a.(*net.IPNet); ok {
|
||||
if ipnet.IP.To4() != nil {
|
||||
serverIP = ipnet.IP.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(serverIP) == 0 {
|
||||
return "", fmt.Errorf(fmt.Sprintf("Unable to determine server ip address from network-interface %s", networkInterface))
|
||||
}
|
||||
return serverIP, nil
|
||||
}
|
||||
|
||||
func getStartCommand(input StartCommandInput) (StartCommandOutput, error) {
|
||||
command := extractStartCommand(input)
|
||||
fields, err := shell.Fields(command, func(name string) string {
|
||||
if name == "PORT" {
|
||||
return fmt.Sprint(input.Port)
|
||||
}
|
||||
|
||||
return input.Env[name]
|
||||
})
|
||||
if err != nil {
|
||||
return StartCommandOutput{}, err
|
||||
}
|
||||
|
||||
return StartCommandOutput{
|
||||
Command: fields,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func installHelmCharts(ctx context.Context, clientset KubernetesClient) error {
|
||||
for _, repo := range HelmRepositories {
|
||||
helmAgent, err := NewHelmAgent("default", DeployLogPrinter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating helm agent: %w", err)
|
||||
}
|
||||
|
||||
err = helmAgent.AddRepository(ctx, AddRepositoryInput(repo))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error adding helm repository %s: %w", repo.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, chart := range HelmCharts {
|
||||
if chart.CreateNamespace {
|
||||
namespace := corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: chart.Namespace,
|
||||
Annotations: map[string]string{
|
||||
"dokku.com/managed": "true",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"dokku.com/managed": "true",
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := clientset.CreateNamespace(ctx, CreateNamespaceInput{
|
||||
Name: namespace,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating namespace %s: %w", chart.Namespace, err)
|
||||
}
|
||||
}
|
||||
|
||||
contents, err := templates.ReadFile(fmt.Sprintf("templates/helm-config/%s.yaml", chart.ReleaseName))
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("Error reading values file %s: %w", chart.ReleaseName, err)
|
||||
}
|
||||
|
||||
var values map[string]interface{}
|
||||
if len(contents) > 0 {
|
||||
err = yaml.Unmarshal(contents, &values)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error unmarshalling values file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
helmAgent, err := NewHelmAgent(chart.Namespace, DeployLogPrinter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating helm agent: %w", err)
|
||||
}
|
||||
|
||||
err = helmAgent.InstallOrUpgradeChart(ctx, ChartInput{
|
||||
ChartPath: chart.ChartPath,
|
||||
Namespace: chart.Namespace,
|
||||
ReleaseName: chart.ReleaseName,
|
||||
RepoURL: chart.RepoURL,
|
||||
Values: values,
|
||||
Version: chart.Version,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error installing chart %s: %w", chart.ChartPath, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isK3sInstalled() error {
|
||||
if !common.FileExists("/usr/local/bin/k3s") {
|
||||
return fmt.Errorf("k3s binary is not available")
|
||||
}
|
||||
|
||||
if !common.FileExists(RegistryConfigPath) {
|
||||
return fmt.Errorf("k3s registry config is not available")
|
||||
}
|
||||
|
||||
if !common.FileExists(KubeConfigPath) {
|
||||
return fmt.Errorf("k3s kubeconfig is not available")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isPodReady(ctx context.Context, clientset KubernetesClient, podName, namespace string) wait.ConditionWithContextFunc {
|
||||
return func(ctx context.Context) (bool, error) {
|
||||
fmt.Printf(".")
|
||||
|
||||
pod, err := clientset.GetPod(ctx, GetPodInput{
|
||||
Name: podName,
|
||||
Namespace: namespace,
|
||||
})
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// kubernetesNodeToNode converts a kubernetes node to a Node
|
||||
func kubernetesNodeToNode(node v1.Node) Node {
|
||||
roles := []string{}
|
||||
if len(node.Labels["kubernetes.io/role"]) > 0 {
|
||||
roles = append(roles, node.Labels["kubernetes.io/role"])
|
||||
} else {
|
||||
for k, v := range node.Labels {
|
||||
if strings.HasPrefix(k, "node-role.kubernetes.io/") && v == "true" {
|
||||
roles = append(roles, strings.TrimPrefix(k, "node-role.kubernetes.io/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(roles)
|
||||
|
||||
ready := false
|
||||
for _, condition := range node.Status.Conditions {
|
||||
if condition.Type == "Ready" {
|
||||
ready = condition.Status == "True"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
remoteHost := ""
|
||||
if val, ok := node.Annotations["dokku.com/remote-host"]; ok {
|
||||
remoteHost = val
|
||||
}
|
||||
|
||||
return Node{
|
||||
Name: node.Name,
|
||||
Roles: roles,
|
||||
Ready: ready,
|
||||
RemoteHost: remoteHost,
|
||||
Version: node.Status.NodeInfo.KubeletVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func waitForPodBySelectorRunning(ctx context.Context, input WaitForPodBySelectorRunningInput) error {
|
||||
pods, err := waitForPodToExist(ctx, WaitForPodToExistInput{
|
||||
Clientset: input.Clientset,
|
||||
LabelSelector: input.LabelSelector,
|
||||
Namespace: input.Namespace,
|
||||
PodName: input.PodName,
|
||||
RetryCount: 3,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error waiting for pod to exist: %w", err)
|
||||
}
|
||||
|
||||
if len(pods) == 0 {
|
||||
return fmt.Errorf("no pods in %s with selector %s", input.Namespace, input.LabelSelector)
|
||||
}
|
||||
|
||||
timeout := time.Duration(input.Timeout) * time.Second
|
||||
for _, pod := range pods {
|
||||
if input.PodName != "" && pod.Name != input.PodName {
|
||||
break
|
||||
}
|
||||
|
||||
if err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, false, input.Waiter(ctx, input.Clientset, pod.Name, pod.Namespace)); err != nil {
|
||||
print("\n")
|
||||
return fmt.Errorf("Error waiting for pod to be ready: %w", err)
|
||||
}
|
||||
}
|
||||
print("\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForNodeToExist(ctx context.Context, input WaitForNodeToExistInput) ([]v1.Node, error) {
|
||||
var matchingNodes []v1.Node
|
||||
var err error
|
||||
for i := 0; i < input.RetryCount; i++ {
|
||||
nodes, err := input.Clientset.ListNodes(ctx, ListNodesInput{})
|
||||
if err != nil {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
if input.NodeName == "" {
|
||||
matchingNodes = nodes
|
||||
break
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
if node.Name == input.NodeName {
|
||||
matchingNodes = append(matchingNodes, node)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(matchingNodes) > 0 {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
if err != nil {
|
||||
return matchingNodes, fmt.Errorf("Error listing nodes: %w", err)
|
||||
}
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func waitForPodToExist(ctx context.Context, input WaitForPodToExistInput) ([]v1.Pod, error) {
|
||||
var pods []v1.Pod
|
||||
var err error
|
||||
for i := 0; i < input.RetryCount; i++ {
|
||||
pods, err = input.Clientset.ListPods(ctx, ListPodsInput{
|
||||
Namespace: input.Namespace,
|
||||
LabelSelector: input.LabelSelector,
|
||||
})
|
||||
if err != nil {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
if input.PodName == "" {
|
||||
break
|
||||
}
|
||||
|
||||
for _, pod := range pods {
|
||||
if pod.Name == input.PodName {
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
if err != nil {
|
||||
return pods, fmt.Errorf("Error listing pods: %w", err)
|
||||
}
|
||||
return pods, nil
|
||||
}
|
||||
185
plugins/scheduler-k3s/go.mod
Normal file
185
plugins/scheduler-k3s/go.mod
Normal file
@@ -0,0 +1,185 @@
|
||||
module github.com/dokku/dokku/plugins/scheduler-k3s
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/cert-manager/cert-manager v1.13.3
|
||||
github.com/dokku/dokku/plugins/app-json v0.0.0-00010101000000-000000000000
|
||||
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
|
||||
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-openapi/jsonpointer v0.19.6
|
||||
github.com/go-resty/resty/v2 v2.11.0
|
||||
github.com/gofrs/flock v0.8.1
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/rancher/wharfie v0.6.5
|
||||
github.com/ryanuber/columnize v2.1.2+incompatible
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/traefik/traefik/v2 v2.10.7
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
helm.sh/helm/v3 v3.13.3
|
||||
k8s.io/api v0.29.0
|
||||
k8s.io/apimachinery v0.29.0
|
||||
k8s.io/client-go v0.29.0
|
||||
k8s.io/kubectl v0.28.4
|
||||
k8s.io/kubernetes v1.29.1
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e
|
||||
mvdan.cc/sh/v3 v3.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.0 // indirect
|
||||
github.com/alexellis/go-execute/v2 v2.2.1 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect
|
||||
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 // indirect
|
||||
github.com/containerd/containerd v1.7.6 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v24.0.6+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/docker v24.0.7+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
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/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
|
||||
github.com/go-acme/lego/v4 v4.14.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
|
||||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-containerregistry v0.14.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/gosuri/uitable v0.0.4 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||
github.com/joho/godotenv v1.2.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.1 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/melbahja/goph v1.4.0 // indirect
|
||||
github.com/miekg/dns v1.1.55 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
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
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
|
||||
github.com/otiai10/copy v1.14.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkg/sftp v1.13.5 // indirect
|
||||
github.com/prometheus/client_golang v1.16.0 // indirect
|
||||
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/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.7.0 // indirect
|
||||
github.com/traefik/paerser v0.2.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
go.opentelemetry.io/otel v1.20.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.20.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.20.0 // indirect
|
||||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/oauth2 v0.12.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/term v0.16.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.16.1 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230911183012-2d3300fd4832 // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.28.4 // indirect
|
||||
k8s.io/apiserver v0.28.4 // indirect
|
||||
k8s.io/cli-runtime v0.29.0 // indirect
|
||||
k8s.io/component-base v0.28.4 // indirect
|
||||
k8s.io/klog/v2 v2.110.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
|
||||
oras.land/oras-go v1.2.4 // indirect
|
||||
sigs.k8s.io/gateway-api v0.8.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
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
|
||||
662
plugins/scheduler-k3s/go.sum
Normal file
662
plugins/scheduler-k3s/go.sum
Normal file
@@ -0,0 +1,662 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
|
||||
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/Microsoft/hcsshim v0.11.0 h1:7EFNIY4igHEXUdj1zXgAyU3fLc7QfOKHbkldRVTBdiM=
|
||||
github.com/Microsoft/hcsshim v0.11.0/go.mod h1:OEthFdQv/AD2RAdzR6Mm1N1KPCztGKDurW1Z8b8VGMM=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alexellis/go-execute/v2 v2.2.1 h1:4Ye3jiCKQarstODOEmqDSRCqxMHLkC92Bhse743RdOI=
|
||||
github.com/alexellis/go-execute/v2 v2.2.1/go.mod h1:FMdRnUTiFAmYXcv23txrp3VYZfLo24nMpiIneWgKHTQ=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
|
||||
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
||||
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng=
|
||||
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ=
|
||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cert-manager/cert-manager v1.13.3 h1:3R4G0RI7K0OkTZhWlVOC5SGZMYa2NwqmQJoyKydrz/M=
|
||||
github.com/cert-manager/cert-manager v1.13.3/go.mod h1:BM2+Pt/NmSv1Zr25/MHv6BgIEF9IUxA1xAjp80qkxgc=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
|
||||
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 h1:HHUr4P/aKh4quafGxDT9LDasjGdlGkzLbfmmrlng3kA=
|
||||
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27/go.mod h1:VQx0hjo2oUeQkQUET7wRwradO6f+fN5jzXgB/zROxxE=
|
||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
||||
github.com/containerd/containerd v1.7.6 h1:oNAVsnhPoy4BTPQivLgTzI9Oleml9l/+eYIDYXRCYo8=
|
||||
github.com/containerd/containerd v1.7.6/go.mod h1:SY6lrkkuJT40BVNO37tlYTSnKJnP5AXBc0fhx0q+TJ4=
|
||||
github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM=
|
||||
github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI=
|
||||
github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY=
|
||||
github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
||||
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
|
||||
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
||||
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/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=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI=
|
||||
github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4=
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/go-acme/lego/v4 v4.14.0 h1:/skZoRHgVh0d2RK7l1g3Ch8HqeqP9LB8ZEjLdGEpcDE=
|
||||
github.com/go-acme/lego/v4 v4.14.0/go.mod h1:zjmvNCDLGz7GrC1OqdVpVmZFKSRabEDtWbdzmcpBsGo=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
|
||||
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
|
||||
github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
|
||||
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
|
||||
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU=
|
||||
github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs=
|
||||
github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0=
|
||||
github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY=
|
||||
github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY=
|
||||
github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k=
|
||||
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.14.0 h1:z58vMqHxuwvAsVwvKEkmVBz2TlgBgH5k6koEXBtlYkw=
|
||||
github.com/google/go-containerregistry v0.14.0/go.mod h1:aiJ2fp/SXvkWgmYHioXnbMdlgB8eXiiYOY55gfN91Wk=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo=
|
||||
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
|
||||
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
|
||||
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/joho/godotenv v1.2.0 h1:vGTvz69FzUFp+X4/bAkb0j5BoLC+9bpqTWY8mjhA9pc=
|
||||
github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI=
|
||||
github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g=
|
||||
github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI=
|
||||
github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
|
||||
github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY=
|
||||
github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
|
||||
github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
|
||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/melbahja/goph v1.4.0 h1:z0PgDbBFe66lRYl3v5dGb9aFgPy0kotuQ37QOwSQFqs=
|
||||
github.com/melbahja/goph v1.4.0/go.mod h1:uG+VfK2Dlhk+O32zFrRlc3kYKTlV6+BtvPWd/kK7U68=
|
||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
|
||||
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=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||
github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE=
|
||||
github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
|
||||
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
|
||||
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
|
||||
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
|
||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go=
|
||||
github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
|
||||
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
|
||||
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
||||
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||
github.com/rancher/dynamiclistener v0.3.5 h1:5TaIHvkDGmZKvc96Huur16zfTKOiLhDtK4S+WV0JA6A=
|
||||
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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0=
|
||||
github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk=
|
||||
github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
|
||||
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/traefik/paerser v0.2.0 h1:zqCLGSXoNlcBd+mzqSCLjon/I6phqIjeJL2xFB2ysgQ=
|
||||
github.com/traefik/paerser v0.2.0/go.mod h1:afzaVcgF8A+MpTnPG4wBr4whjanCSYA6vK5RwaYVtRc=
|
||||
github.com/traefik/traefik/v2 v2.10.7 h1:vs91abMRVFTxBjn9hsLkqPbQ+D0oM1+bf4K8nD5PKg4=
|
||||
github.com/traefik/traefik/v2 v2.10.7/go.mod h1:TD3saYNYbcBa7qDm29fIeyaiCMVX3ltv2YuaX3ANcnc=
|
||||
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
|
||||
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
|
||||
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY=
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc=
|
||||
go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs=
|
||||
go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA=
|
||||
go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM=
|
||||
go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ=
|
||||
go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU=
|
||||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
|
||||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
|
||||
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230911183012-2d3300fd4832 h1:o4LtQxebKIJ4vkzyhtD2rfUNZ20Zf0ik5YVP5E7G7VE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230911183012-2d3300fd4832/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
helm.sh/helm/v3 v3.13.3 h1:0zPEdGqHcubehJHP9emCtzRmu8oYsJFRrlVF3TFj8xY=
|
||||
helm.sh/helm/v3 v3.13.3/go.mod h1:3OKO33yI3p4YEXtTITN2+4oScsHeQe71KuzhlZ+aPfg=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A=
|
||||
k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA=
|
||||
k8s.io/apiextensions-apiserver v0.28.4 h1:AZpKY/7wQ8n+ZYDtNHbAJBb+N4AXXJvyZx6ww6yAJvU=
|
||||
k8s.io/apiextensions-apiserver v0.28.4/go.mod h1:pgQIZ1U8eJSMQcENew/0ShUTlePcSGFq6dxSxf2mwPM=
|
||||
k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o=
|
||||
k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis=
|
||||
k8s.io/apiserver v0.28.4 h1:BJXlaQbAU/RXYX2lRz+E1oPe3G3TKlozMMCZWu5GMgg=
|
||||
k8s.io/apiserver v0.28.4/go.mod h1:Idq71oXugKZoVGUUL2wgBCTHbUR+FYTWa4rq9j4n23w=
|
||||
k8s.io/cli-runtime v0.29.0 h1:q2kC3cex4rOBLfPOnMSzV2BIrrQlx97gxHJs21KxKS4=
|
||||
k8s.io/cli-runtime v0.29.0/go.mod h1:VKudXp3X7wR45L+nER85YUzOQIru28HQpXr0mTdeCrk=
|
||||
k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8=
|
||||
k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38=
|
||||
k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo=
|
||||
k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU=
|
||||
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
|
||||
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
|
||||
k8s.io/kubectl v0.28.4 h1:gWpUXW/T7aFne+rchYeHkyB8eVDl5UZce8G4X//kjUQ=
|
||||
k8s.io/kubectl v0.28.4/go.mod h1:CKOccVx3l+3MmDbkXtIUtibq93nN2hkDR99XDCn7c/c=
|
||||
k8s.io/kubernetes v1.29.1 h1:fxJFVb8uqbYZDYHpwIsAndBQs360cQGb0xa1gYFh3fo=
|
||||
k8s.io/kubernetes v1.29.1/go.mod h1:xZPKU0yO0CBbLTnbd+XGyRmmtmaVuJykDb8gNCkeeUE=
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ=
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
|
||||
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
|
||||
oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY=
|
||||
oras.land/oras-go v1.2.4/go.mod h1:DYcGfb3YF1nKjcezfX2SNlDAeQFKSXmf+qrFmrh4324=
|
||||
sigs.k8s.io/gateway-api v0.8.0 h1:isQQ3Jx2qFP7vaA3ls0846F0Amp9Eq14P08xbSwVbQg=
|
||||
sigs.k8s.io/gateway-api v0.8.0/go.mod h1:okOnjPNBFbIS/Rw9kAhuIUaIkLhTKEu+ARIuXk2dgaM=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0=
|
||||
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY=
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U=
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
356
plugins/scheduler-k3s/helm.go
Normal file
356
plugins/scheduler-k3s/helm.go
Normal file
@@ -0,0 +1,356 @@
|
||||
package scheduler_k3s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
"github.com/gofrs/flock"
|
||||
"gopkg.in/yaml.v3"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"helm.sh/helm/v3/pkg/cli"
|
||||
"helm.sh/helm/v3/pkg/getter"
|
||||
"helm.sh/helm/v3/pkg/kube"
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
"helm.sh/helm/v3/pkg/storage/driver"
|
||||
)
|
||||
|
||||
var DevNullPrinter = func(format string, v ...interface{}) {}
|
||||
|
||||
var DeployLogPrinter = func(format string, v ...interface{}) {
|
||||
message := strings.TrimSpace(fmt.Sprintf(format, v...))
|
||||
if message == "" {
|
||||
return
|
||||
}
|
||||
r := []rune(message)
|
||||
r[0] = unicode.ToUpper(r[0])
|
||||
s := string(r)
|
||||
|
||||
if strings.HasPrefix(s, "Beginning wait") {
|
||||
common.LogExclaim(s)
|
||||
} else if strings.HasPrefix(s, "Warning:") {
|
||||
common.LogExclaim(s)
|
||||
} else {
|
||||
common.LogVerboseQuiet(s)
|
||||
}
|
||||
}
|
||||
|
||||
type ChartInput struct {
|
||||
ChartPath string
|
||||
Namespace string
|
||||
ReleaseName string
|
||||
RepoURL string
|
||||
RollbackOnFailure bool
|
||||
Timeout time.Duration
|
||||
Version string
|
||||
Values map[string]interface{}
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Version int
|
||||
}
|
||||
|
||||
type HelmAgent struct {
|
||||
Configuration *action.Configuration
|
||||
Namespace string
|
||||
Logger action.DebugLog
|
||||
}
|
||||
|
||||
func NewHelmAgent(namespace string, logger action.DebugLog) (*HelmAgent, error) {
|
||||
actionConfig := new(action.Configuration)
|
||||
|
||||
helmDriver := os.Getenv("HELM_DRIVER")
|
||||
if helmDriver == "" {
|
||||
helmDriver = "secrets"
|
||||
}
|
||||
|
||||
kubeConfig := kube.GetConfig(KubeConfigPath, "", namespace)
|
||||
if err := actionConfig.Init(kubeConfig, namespace, helmDriver, logger); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &HelmAgent{
|
||||
Configuration: actionConfig,
|
||||
Namespace: namespace,
|
||||
Logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type AddRepositoryInput struct {
|
||||
Name string
|
||||
URL string
|
||||
}
|
||||
|
||||
func (h *HelmAgent) AddRepository(ctx context.Context, helmRepo AddRepositoryInput) error {
|
||||
settings := cli.New()
|
||||
repoFile := settings.RepositoryConfig
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(repoFile), os.ModePerm)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return fmt.Errorf("Error creating repository directory: %w", err)
|
||||
}
|
||||
|
||||
fileLock := flock.New(strings.Replace(repoFile, filepath.Ext(repoFile), ".lock", 1))
|
||||
locked, err := fileLock.TryLockContext(ctx, time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error acquiring file lock: %w", err)
|
||||
}
|
||||
|
||||
if !locked {
|
||||
return fmt.Errorf("Could not acquire file lock")
|
||||
}
|
||||
|
||||
defer fileLock.Unlock() // nolint: errcheck
|
||||
|
||||
b, err := os.ReadFile(repoFile)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("Error reading repository file: %w", err)
|
||||
}
|
||||
|
||||
var f repo.File
|
||||
if err := yaml.Unmarshal(b, &f); err != nil {
|
||||
return fmt.Errorf("Error unmarshalling yaml: %w", err)
|
||||
}
|
||||
|
||||
if f.Has(helmRepo.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := repo.Entry{
|
||||
Name: helmRepo.Name,
|
||||
URL: helmRepo.URL,
|
||||
}
|
||||
|
||||
r, err := repo.NewChartRepository(&c, getter.All(settings))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating chart repository: %w", err)
|
||||
}
|
||||
|
||||
if _, err := r.DownloadIndexFile(); err != nil {
|
||||
return fmt.Errorf("Specified repository '%q' is not a valid chart repository or cannot be reached: %w", helmRepo.URL, err)
|
||||
}
|
||||
|
||||
f.Update(&c)
|
||||
|
||||
if err := f.WriteFile(repoFile, 0644); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HelmAgent) ChartExists(releaseName string) (bool, error) {
|
||||
if releaseName == "" {
|
||||
return false, fmt.Errorf("Release name is required")
|
||||
}
|
||||
|
||||
client := action.NewHistory(h.Configuration)
|
||||
client.Max = 1
|
||||
releases, err := client.Run(releaseName)
|
||||
if err != nil {
|
||||
if errors.Is(err, driver.ErrReleaseNotFound) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(releases) > 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (h *HelmAgent) DeleteRevision(ctx context.Context, releaseName string, revision int) error {
|
||||
clientset, err := NewKubernetesClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
secretName := fmt.Sprintf("sh.helm.release.v1.%s.v%d", releaseName, revision)
|
||||
err = clientset.DeleteSecret(ctx, DeleteSecretInput{
|
||||
Name: secretName,
|
||||
Namespace: h.Namespace,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deleting secret: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HelmAgent) GetValues(releaseName string) (map[string]interface{}, error) {
|
||||
client := action.NewGetValues(h.Configuration)
|
||||
client.AllValues = true
|
||||
values, err := client.Run(releaseName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting values: %w", err)
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (h *HelmAgent) InstallOrUpgradeChart(ctx context.Context, input ChartInput) error {
|
||||
chartExists, err := h.ChartExists(input.ReleaseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error checking if chart exists: %w", err)
|
||||
}
|
||||
|
||||
if chartExists {
|
||||
return h.UpgradeChart(ctx, input)
|
||||
}
|
||||
|
||||
return h.InstallChart(ctx, input)
|
||||
}
|
||||
|
||||
func (h *HelmAgent) InstallChart(ctx context.Context, input ChartInput) error {
|
||||
namespace := input.Namespace
|
||||
if namespace == "" {
|
||||
namespace = h.Namespace
|
||||
}
|
||||
|
||||
if input.ChartPath == "" {
|
||||
return fmt.Errorf("Chart path is required")
|
||||
}
|
||||
if input.ReleaseName == "" {
|
||||
return fmt.Errorf("Release name is required")
|
||||
}
|
||||
if input.Values == nil {
|
||||
input.Values = map[string]interface{}{}
|
||||
}
|
||||
|
||||
client := action.NewInstall(h.Configuration)
|
||||
client.Atomic = false
|
||||
client.ChartPathOptions = action.ChartPathOptions{}
|
||||
client.CreateNamespace = true
|
||||
client.DryRun = false
|
||||
client.Namespace = namespace
|
||||
client.ReleaseName = input.ReleaseName
|
||||
client.Timeout = input.Timeout
|
||||
client.Wait = false
|
||||
|
||||
settings := cli.New()
|
||||
if input.RepoURL != "" {
|
||||
client.ChartPathOptions.RepoURL = input.RepoURL
|
||||
}
|
||||
if input.Version != "" {
|
||||
client.ChartPathOptions.Version = input.Version
|
||||
}
|
||||
|
||||
chart, err := client.ChartPathOptions.LocateChart(input.ChartPath, settings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error locating chart: %w", err)
|
||||
}
|
||||
|
||||
chartRequested, err := loader.Load(chart)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error loading chart: %w", err)
|
||||
}
|
||||
|
||||
_, err = client.RunWithContext(ctx, chartRequested, input.Values)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deploying: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HelmAgent) ListRevisions(ctx context.Context, releaseName string) ([]Release, error) {
|
||||
client := action.NewHistory(h.Configuration)
|
||||
response, err := client.Run(releaseName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting revisions: %w", err)
|
||||
}
|
||||
|
||||
releases := []Release{}
|
||||
for _, release := range response {
|
||||
releases = append(releases, Release{
|
||||
Name: release.Name,
|
||||
Namespace: release.Namespace,
|
||||
Version: release.Version,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(releases, func(i, j int) bool {
|
||||
return releases[i].Version < releases[j].Version
|
||||
})
|
||||
|
||||
return releases, nil
|
||||
}
|
||||
|
||||
func (h *HelmAgent) UpgradeChart(ctx context.Context, input ChartInput) error {
|
||||
namespace := input.Namespace
|
||||
if namespace == "" {
|
||||
namespace = h.Namespace
|
||||
}
|
||||
|
||||
if input.ChartPath == "" {
|
||||
return fmt.Errorf("Chart path is required")
|
||||
}
|
||||
if input.ReleaseName == "" {
|
||||
return fmt.Errorf("Release name is required")
|
||||
}
|
||||
if input.Values == nil {
|
||||
input.Values = map[string]interface{}{}
|
||||
}
|
||||
|
||||
client := action.NewUpgrade(h.Configuration)
|
||||
client.Atomic = input.RollbackOnFailure
|
||||
client.ChartPathOptions = action.ChartPathOptions{}
|
||||
client.CleanupOnFail = true
|
||||
client.DryRun = false
|
||||
client.MaxHistory = 10
|
||||
client.Namespace = namespace
|
||||
client.Timeout = input.Timeout
|
||||
client.Wait = false
|
||||
if input.RepoURL != "" {
|
||||
client.RepoURL = input.RepoURL
|
||||
}
|
||||
|
||||
chart, err := client.ChartPathOptions.LocateChart(input.ChartPath, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error locating chart: %w", err)
|
||||
}
|
||||
|
||||
chartRequested, err := loader.Load(chart)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error loading chart: %w", err)
|
||||
}
|
||||
|
||||
_, err = client.RunWithContext(ctx, input.ReleaseName, chartRequested, input.Values)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deploying: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HelmAgent) UninstallChart(releaseName string) error {
|
||||
exists, err := h.ChartExists(releaseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error checking if chart exists: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
uninstall := action.NewUninstall(h.Configuration)
|
||||
uninstall.DeletionPropagation = "foreground"
|
||||
_, err = uninstall.Run(releaseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error uninstalling chart: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
447
plugins/scheduler-k3s/k8s.go
Normal file
447
plugins/scheduler-k3s/k8s.go
Normal file
@@ -0,0 +1,447 @@
|
||||
package scheduler_k3s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
batchv1 "k8s.io/api/batch/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/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
// KubernetesClient is a wrapper around the Kubernetes client
|
||||
type KubernetesClient struct {
|
||||
// Client is the Kubernetes client
|
||||
Client kubernetes.Clientset
|
||||
|
||||
// RestClient is the Kubernetes REST client
|
||||
RestClient rest.Interface
|
||||
|
||||
// RestConfig is the Kubernetes REST config
|
||||
RestConfig rest.Config
|
||||
}
|
||||
|
||||
// NewKubernetesClient creates a new Kubernetes client
|
||||
func NewKubernetesClient() (KubernetesClient, error) {
|
||||
clientConfig := KubernetesClientConfig()
|
||||
restConf, err := clientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return KubernetesClient{}, err
|
||||
}
|
||||
|
||||
restConf.GroupVersion = &schema.GroupVersion{
|
||||
Group: "api",
|
||||
Version: "v1",
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(restConf)
|
||||
if err != nil {
|
||||
return KubernetesClient{}, err
|
||||
}
|
||||
|
||||
restConf.NegotiatedSerializer = runtime.NewSimpleNegotiatedSerializer(runtime.SerializerInfo{})
|
||||
|
||||
restClient, err := rest.RESTClientFor(restConf)
|
||||
if err != nil {
|
||||
return KubernetesClient{}, err
|
||||
}
|
||||
|
||||
return KubernetesClient{
|
||||
Client: *client,
|
||||
RestConfig: *restConf,
|
||||
RestClient: restClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// KubernetesClientConfig returns a Kubernetes client config
|
||||
func KubernetesClientConfig() clientcmd.ClientConfig {
|
||||
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
||||
&clientcmd.ClientConfigLoadingRules{ExplicitPath: KubeConfigPath},
|
||||
&clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: ""}})
|
||||
}
|
||||
|
||||
// AnnotateNodeInput contains all the information needed to annotates a Kubernetes node
|
||||
type AnnotateNodeInput struct {
|
||||
// Name is the Kubernetes node name
|
||||
Name string
|
||||
// Key is the annotation key
|
||||
Key string
|
||||
// Value is the annotation value
|
||||
Value string
|
||||
}
|
||||
|
||||
// AnnotateNode annotates a Kubernetes node
|
||||
func (k KubernetesClient) AnnotateNode(ctx context.Context, input AnnotateNodeInput) error {
|
||||
node, err := k.Client.CoreV1().Nodes().Get(ctx, input.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if node == nil {
|
||||
return errors.New("node is nil")
|
||||
}
|
||||
|
||||
keyPath := fmt.Sprintf("/metadata/annotations/%s", jsonpointer.Escape(input.Key))
|
||||
patch := fmt.Sprintf(`[{"op":"add", "path":"%s", "value":"%s" }]`, keyPath, input.Value)
|
||||
_, err = k.Client.CoreV1().Nodes().Patch(ctx, node.Name, types.JSONPatchType, []byte(patch), metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to annotate node: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ApplyKubernetesManifestInput struct {
|
||||
// Manifest is the path to the Kubernetes manifest
|
||||
Manifest string
|
||||
}
|
||||
|
||||
func (k KubernetesClient) ApplyKubernetesManifest(ctx context.Context, input ApplyKubernetesManifestInput) error {
|
||||
upgradeCmd, err := common.CallExecCommand(common.ExecCommandInput{
|
||||
Command: "kubectl",
|
||||
Args: []string{
|
||||
"apply",
|
||||
"-f",
|
||||
input.Manifest,
|
||||
},
|
||||
StreamStdio: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call kubectl command: %w", err)
|
||||
}
|
||||
if upgradeCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from kubectl command: %d", upgradeCmd.ExitCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateJobInput contains all the information needed to create a Kubernetes job
|
||||
type CreateJobInput struct {
|
||||
// Job is the Kubernetes job
|
||||
Job batchv1.Job
|
||||
|
||||
// Namespace is the Kubernetes namespace
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// CreateJob creates a Kubernetes job
|
||||
func (k KubernetesClient) CreateJob(ctx context.Context, input CreateJobInput) (batchv1.Job, error) {
|
||||
job, err := k.Client.BatchV1().Jobs(input.Namespace).Create(ctx, &input.Job, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return batchv1.Job{}, err
|
||||
}
|
||||
|
||||
if job == nil {
|
||||
return batchv1.Job{}, errors.New("job is nil")
|
||||
}
|
||||
|
||||
return *job, err
|
||||
}
|
||||
|
||||
// CreateNamespaceInput contains all the information needed to create a Kubernetes namespace
|
||||
type CreateNamespaceInput struct {
|
||||
// Name is the name of the Kubernetes namespace
|
||||
Name v1.Namespace
|
||||
}
|
||||
|
||||
// CreateNamespace creates a Kubernetes namespace
|
||||
func (k KubernetesClient) CreateNamespace(ctx context.Context, input CreateNamespaceInput) (v1.Namespace, error) {
|
||||
namespaces, err := k.ListNamespaces(ctx)
|
||||
if err != nil {
|
||||
return v1.Namespace{}, err
|
||||
}
|
||||
|
||||
for _, namespace := range namespaces {
|
||||
if namespace.Name == input.Name.Name {
|
||||
return namespace, nil
|
||||
}
|
||||
}
|
||||
|
||||
namespace, err := k.Client.CoreV1().Namespaces().Create(ctx, &input.Name, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return v1.Namespace{}, err
|
||||
}
|
||||
|
||||
if namespace == nil {
|
||||
return v1.Namespace{}, errors.New("namespace is nil")
|
||||
}
|
||||
|
||||
return *namespace, err
|
||||
}
|
||||
|
||||
// DeleteJobInput contains all the information needed to delete a Kubernetes job
|
||||
type DeleteJobInput struct {
|
||||
// Name is the Kubernetes job name
|
||||
Name string
|
||||
|
||||
// Namespace is the Kubernetes namespace
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// DeleteJob deletes a Kubernetes job
|
||||
func (k KubernetesClient) DeleteJob(ctx context.Context, input DeleteJobInput) error {
|
||||
return k.Client.BatchV1().Jobs(input.Namespace).Delete(ctx, input.Name, metav1.DeleteOptions{
|
||||
PropagationPolicy: ptr.To(metav1.DeletePropagationForeground),
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteNodeInput contains all the information needed to delete a Kubernetes node
|
||||
type DeleteNodeInput struct {
|
||||
// Name is the Kubernetes node name
|
||||
Name string
|
||||
}
|
||||
|
||||
// DeleteNode deletes a Kubernetes node
|
||||
func (k KubernetesClient) DeleteNode(ctx context.Context, input DeleteNodeInput) error {
|
||||
return k.Client.CoreV1().Nodes().Delete(ctx, input.Name, metav1.DeleteOptions{})
|
||||
}
|
||||
|
||||
// DeleteSecretInput contains all the information needed to delete a Kubernetes secret
|
||||
type DeleteSecretInput struct {
|
||||
// Name is the Kubernetes secret name
|
||||
Name string
|
||||
|
||||
// Namespace is the Kubernetes namespace
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// DeleteSecret deletes a Kubernetes secret
|
||||
func (k KubernetesClient) DeleteSecret(ctx context.Context, input DeleteSecretInput) error {
|
||||
return k.Client.CoreV1().Secrets(input.Namespace).Delete(ctx, input.Name, metav1.DeleteOptions{})
|
||||
}
|
||||
|
||||
// GetNodeInput contains all the information needed to get a Kubernetes node
|
||||
type GetNodeInput struct {
|
||||
// Name is the Kubernetes node name
|
||||
Name string
|
||||
}
|
||||
|
||||
// GetNode gets a Kubernetes node
|
||||
func (k KubernetesClient) GetNode(ctx context.Context, input GetNodeInput) (Node, error) {
|
||||
if input.Name == "" {
|
||||
return Node{}, errors.New("node name is required")
|
||||
}
|
||||
|
||||
node, err := k.Client.CoreV1().Nodes().Get(ctx, input.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return Node{}, err
|
||||
}
|
||||
|
||||
if node == nil {
|
||||
return Node{}, errors.New("node is nil")
|
||||
}
|
||||
|
||||
return kubernetesNodeToNode(*node), err
|
||||
}
|
||||
|
||||
// GetJobInput contains all the information needed to get a Kubernetes job
|
||||
type GetPodInput struct {
|
||||
// Name is the Kubernetes pod name
|
||||
Name string
|
||||
|
||||
// Namespace is the Kubernetes namespace
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// GetJob gets a Kubernetes job
|
||||
func (k KubernetesClient) GetPod(ctx context.Context, input GetPodInput) (v1.Pod, error) {
|
||||
pod, err := k.Client.CoreV1().Pods(input.Namespace).Get(ctx, input.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return v1.Pod{}, err
|
||||
}
|
||||
|
||||
if pod == nil {
|
||||
return v1.Pod{}, errors.New("pod is nil")
|
||||
}
|
||||
|
||||
return *pod, err
|
||||
}
|
||||
|
||||
// LabelNodeInput contains all the information needed to label a Kubernetes node
|
||||
type LabelNodeInput struct {
|
||||
// Name is the Kubernetes node name
|
||||
Name string
|
||||
// Key is the label key
|
||||
Key string
|
||||
// Value is the label value
|
||||
Value string
|
||||
}
|
||||
|
||||
// LabelNode labels a Kubernetes node
|
||||
func (k KubernetesClient) LabelNode(ctx context.Context, input LabelNodeInput) error {
|
||||
node, err := k.Client.CoreV1().Nodes().Get(ctx, input.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if node == nil {
|
||||
return errors.New("node is nil")
|
||||
}
|
||||
|
||||
keyPath := fmt.Sprintf("/metadata/labels/%s", jsonpointer.Escape(input.Key))
|
||||
patch := fmt.Sprintf(`[{"op":"add", "path":"%s", "value":"%s" }]`, keyPath, input.Value)
|
||||
_, err = k.Client.CoreV1().Nodes().Patch(ctx, node.Name, types.JSONPatchType, []byte(patch), metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to label node: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListCronJobsInput contains all the information needed to list Kubernetes cron jobs
|
||||
type ListCronJobsInput struct {
|
||||
// LabelSelector is the Kubernetes label selector
|
||||
LabelSelector string
|
||||
|
||||
// Namespace is the Kubernetes namespace
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// ListCronJobs lists Kubernetes cron jobs
|
||||
func (k KubernetesClient) ListCronJobs(ctx context.Context, input ListCronJobsInput) ([]batchv1.CronJob, error) {
|
||||
listOptions := metav1.ListOptions{}
|
||||
if input.LabelSelector != "" {
|
||||
listOptions.LabelSelector = input.LabelSelector
|
||||
}
|
||||
|
||||
cronJobs, err := k.Client.BatchV1().CronJobs(input.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return []batchv1.CronJob{}, err
|
||||
}
|
||||
|
||||
if cronJobs == nil {
|
||||
return []batchv1.CronJob{}, errors.New("cron jobs is nil")
|
||||
}
|
||||
|
||||
return cronJobs.Items, err
|
||||
}
|
||||
|
||||
// ListDeploymentsInput contains all the information needed to list Kubernetes deployments
|
||||
type ListDeploymentsInput struct {
|
||||
// Namespace is the Kubernetes namespace
|
||||
Namespace string
|
||||
|
||||
// LabelSelector is the Kubernetes label selector
|
||||
LabelSelector string
|
||||
}
|
||||
|
||||
// ListDeployments lists Kubernetes deployments
|
||||
func (k KubernetesClient) ListDeployments(ctx context.Context, input ListDeploymentsInput) ([]appsv1.Deployment, error) {
|
||||
listOptions := metav1.ListOptions{LabelSelector: input.LabelSelector}
|
||||
deployments, err := k.Client.AppsV1().Deployments(input.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return []appsv1.Deployment{}, err
|
||||
}
|
||||
|
||||
if deployments == nil {
|
||||
return []appsv1.Deployment{}, errors.New("deployments is nil")
|
||||
}
|
||||
|
||||
return deployments.Items, nil
|
||||
}
|
||||
|
||||
// ListNamespaces lists Kubernetes namespaces
|
||||
func (k KubernetesClient) ListNamespaces(ctx context.Context) ([]v1.Namespace, error) {
|
||||
namespaces, err := k.Client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return []v1.Namespace{}, err
|
||||
}
|
||||
if namespaces == nil {
|
||||
return []v1.Namespace{}, errors.New("namespaces is nil")
|
||||
}
|
||||
|
||||
return namespaces.Items, nil
|
||||
}
|
||||
|
||||
// ListNodesInput contains all the information needed to list Kubernetes nodes
|
||||
type ListNodesInput struct {
|
||||
// LabelSelector is the Kubernetes label selector
|
||||
LabelSelector string
|
||||
}
|
||||
|
||||
// ListNodes lists Kubernetes nodes
|
||||
func (k KubernetesClient) ListNodes(ctx context.Context, input ListNodesInput) ([]v1.Node, error) {
|
||||
listOptions := metav1.ListOptions{}
|
||||
if input.LabelSelector != "" {
|
||||
common.LogDebug(fmt.Sprintf("Using label selector: %s", input.LabelSelector))
|
||||
listOptions.LabelSelector = input.LabelSelector
|
||||
}
|
||||
nodeList, err := k.Client.CoreV1().Nodes().List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return []v1.Node{}, err
|
||||
}
|
||||
|
||||
if nodeList == nil {
|
||||
return []v1.Node{}, errors.New("pod list is nil")
|
||||
}
|
||||
|
||||
return nodeList.Items, err
|
||||
}
|
||||
|
||||
// ListPodsInput contains all the information needed to list Kubernetes pods
|
||||
type ListPodsInput struct {
|
||||
// Namespace is the Kubernetes namespace
|
||||
Namespace string
|
||||
|
||||
// LabelSelector is the Kubernetes label selector
|
||||
LabelSelector string
|
||||
}
|
||||
|
||||
// ListPods lists Kubernetes pods
|
||||
func (k KubernetesClient) ListPods(ctx context.Context, input ListPodsInput) ([]v1.Pod, error) {
|
||||
listOptions := metav1.ListOptions{LabelSelector: input.LabelSelector}
|
||||
podList, err := k.Client.CoreV1().Pods(input.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return []v1.Pod{}, err
|
||||
}
|
||||
|
||||
if podList == nil {
|
||||
return []v1.Pod{}, errors.New("pod list is nil")
|
||||
}
|
||||
|
||||
return podList.Items, err
|
||||
}
|
||||
|
||||
// ScaleDeploymentInput contains all the information needed to scale a Kubernetes deployment
|
||||
type ScaleDeploymentInput struct {
|
||||
// Name is the Kubernetes deployment name
|
||||
Name string
|
||||
|
||||
// Namespace is the Kubernetes namespace
|
||||
Namespace string
|
||||
|
||||
// Replicas is the number of replicas to scale to
|
||||
Replicas int32
|
||||
}
|
||||
|
||||
// ScaleDeployment scales a Kubernetes deployment
|
||||
func (k KubernetesClient) ScaleDeployment(ctx context.Context, input ScaleDeploymentInput) error {
|
||||
_, err := k.Client.AppsV1().Deployments(input.Namespace).UpdateScale(ctx, input.Name, &autoscalingv1.Scale{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: input.Name,
|
||||
Namespace: input.Namespace,
|
||||
},
|
||||
Spec: autoscalingv1.ScaleSpec{
|
||||
Replicas: input.Replicas,
|
||||
},
|
||||
}, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
4
plugins/scheduler-k3s/plugin.toml
Normal file
4
plugins/scheduler-k3s/plugin.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[plugin]
|
||||
description = "dokku core scheduler-k3s plugin"
|
||||
version = "0.32.3"
|
||||
[plugin.config]
|
||||
52
plugins/scheduler-k3s/portmap.go
Normal file
52
plugins/scheduler-k3s/portmap.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package scheduler_k3s
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
type PortMap struct {
|
||||
ContainerPort int32 `json:"container_port"`
|
||||
HostPort int32 `json:"host_port"`
|
||||
Scheme string `json:"scheme"`
|
||||
}
|
||||
|
||||
func (p PortMap) String() string {
|
||||
return fmt.Sprintf("%s-%d-%d", p.Scheme, p.HostPort, p.ContainerPort)
|
||||
}
|
||||
|
||||
func (p PortMap) IsAllowedHttp() bool {
|
||||
return p.Scheme == "http" || p.ContainerPort == 80
|
||||
}
|
||||
|
||||
func (p PortMap) IsAllowedHttps() bool {
|
||||
return p.Scheme == "https" || p.ContainerPort == 443
|
||||
}
|
||||
|
||||
func getPortMaps(appName string) (map[string]PortMap, error) {
|
||||
portMaps := []PortMap{}
|
||||
|
||||
allowedMappings := map[string]PortMap{}
|
||||
output, err := common.PlugnTriggerOutputAsString("ports-get", []string{appName, "json"}...)
|
||||
if err != nil {
|
||||
return allowedMappings, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(output), &portMaps)
|
||||
if err != nil {
|
||||
return allowedMappings, err
|
||||
}
|
||||
|
||||
for _, portMap := range portMaps {
|
||||
if !portMap.IsAllowedHttp() && !portMap.IsAllowedHttps() {
|
||||
// todo: log warning
|
||||
continue
|
||||
}
|
||||
|
||||
allowedMappings[portMap.String()] = portMap
|
||||
}
|
||||
|
||||
return allowedMappings, nil
|
||||
}
|
||||
78
plugins/scheduler-k3s/registry.go
Normal file
78
plugins/scheduler-k3s/registry.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package scheduler_k3s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
func copyRegistryToNode(ctx context.Context, remoteHost string) error {
|
||||
common.LogInfo1Quiet(fmt.Sprintf("Updating k3s registry configuration on %s", remoteHost))
|
||||
sftpCmd, err := common.CallSftpCopy(common.SftpCopyInput{
|
||||
AllowUknownHosts: true,
|
||||
DestinationPath: "/tmp/registries.yaml",
|
||||
RemoteHost: remoteHost,
|
||||
SourcePath: RegistryConfigPath,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error copying registries.yaml to remote host: %w", err)
|
||||
}
|
||||
|
||||
if sftpCmd.ExitErr != nil {
|
||||
return fmt.Errorf("Error copying registries.yaml to remote host: %s", sftpCmd.ExitErr.Error())
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet("Moving k3s registry configuration into place")
|
||||
mvCmd, err := common.CallSshCommand(common.SshCommandInput{
|
||||
AllowUknownHosts: true,
|
||||
Args: []string{"/tmp/registries.yaml", RegistryConfigPath},
|
||||
Command: "mv",
|
||||
RemoteHost: remoteHost,
|
||||
StreamStdio: true,
|
||||
Sudo: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call mv command over ssh: %w", err)
|
||||
}
|
||||
|
||||
if mvCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from mv command over ssh: %d", mvCmd.ExitCode)
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet("Updating k3s registry permissions")
|
||||
chmodCmd, err := common.CallSshCommand(common.SshCommandInput{
|
||||
AllowUknownHosts: true,
|
||||
Args: []string{"0644", RegistryConfigPath},
|
||||
Command: "chmod",
|
||||
RemoteHost: remoteHost,
|
||||
StreamStdio: true,
|
||||
Sudo: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call chmod command over ssh: %w", err)
|
||||
}
|
||||
|
||||
if chmodCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from chmod command over ssh: %d", chmodCmd.ExitCode)
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet("Updating k3s registry ower")
|
||||
chownCmd, err := common.CallSshCommand(common.SshCommandInput{
|
||||
AllowUknownHosts: true,
|
||||
Args: []string{"root:root", RegistryConfigPath},
|
||||
Command: "chown",
|
||||
RemoteHost: remoteHost,
|
||||
StreamStdio: true,
|
||||
Sudo: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call chown command over ssh: %w", err)
|
||||
}
|
||||
|
||||
if chownCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from chown command over ssh: %d", chownCmd.ExitCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
115
plugins/scheduler-k3s/report.go
Normal file
115
plugins/scheduler-k3s/report.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package scheduler_k3s
|
||||
|
||||
import (
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
// ReportSingleApp is an internal function that displays the scheduler-k3s report for one or more apps
|
||||
func ReportSingleApp(appName string, format string, infoFlag string) error {
|
||||
if err := common.VerifyAppName(appName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flags := map[string]common.ReportFunc{
|
||||
"--scheduler-k3s-computed-deploy-timeout": reportComputedDeployTimeout,
|
||||
"--scheduler-k3s-deploy-timeout": reportDeployTimeout,
|
||||
"--scheduler-k3s-global-deploy-timeout": reportGlobalDeployTimeout,
|
||||
"--scheduler-k3s-computed-image-pull-secrets": reportComputedImagePullSecrets,
|
||||
"--scheduler-k3s-image-pull-secrets": reportImagePullSecrets,
|
||||
"--scheduler-k3s-global-image-pull-secrets": reportGlobalImagePullSecrets,
|
||||
"--scheduler-k3s-computed-letsencrypt-server": reportComputedLetsencryptServer,
|
||||
"--scheduler-k3s-letsencrypt-server": reportLetsencryptServer,
|
||||
"--scheduler-k3s-global-letsencrypt-server": reportGlobalLetsencryptServer,
|
||||
"--scheduler-k3s-global-letsencrypt-email-prod": reportGlobalLetsencryptEmailProd,
|
||||
"--scheduler-k3s-global-letsencrypt-email-stag": reportGlobalLetsencryptEmailStag,
|
||||
"--scheduler-k3s-computed-namespace": reportComputedNamespace,
|
||||
"--scheduler-k3s-namespace": reportNamespace,
|
||||
"--scheduler-k3s-global-namespace": reportGlobalNamespace,
|
||||
"--scheduler-k3s-global-network-interface": reportGlobalNetworkInterface,
|
||||
"--scheduler-k3s-computed-rollback-on-failure": reportComputedRollbackOnFailure,
|
||||
"--scheduler-k3s-rollback-on-failure": reportRollbackOnFailure,
|
||||
"--scheduler-k3s-global-rollback-on-failure": reportGlobalRollbackOnFailure,
|
||||
}
|
||||
|
||||
flagKeys := []string{}
|
||||
for flagKey := range flags {
|
||||
flagKeys = append(flagKeys, flagKey)
|
||||
}
|
||||
|
||||
trimPrefix := false
|
||||
uppercaseFirstCharacter := true
|
||||
infoFlags := common.CollectReport(appName, infoFlag, flags)
|
||||
return common.ReportSingleApp("scheduler-k3s", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter)
|
||||
}
|
||||
|
||||
func reportComputedDeployTimeout(appName string) string {
|
||||
return getComputedDeployTimeout(appName)
|
||||
}
|
||||
|
||||
func reportDeployTimeout(appName string) string {
|
||||
return getDeployTimeout(appName)
|
||||
}
|
||||
|
||||
func reportGlobalDeployTimeout(appName string) string {
|
||||
return getGlobalDeployTimeout()
|
||||
}
|
||||
|
||||
func reportComputedImagePullSecrets(appName string) string {
|
||||
return getComputedImagePullSecrets(appName)
|
||||
}
|
||||
|
||||
func reportImagePullSecrets(appName string) string {
|
||||
return getImagePullSecrets(appName)
|
||||
}
|
||||
|
||||
func reportGlobalImagePullSecrets(appName string) string {
|
||||
return getGlobalImagePullSecrets()
|
||||
}
|
||||
|
||||
func reportComputedLetsencryptServer(appName string) string {
|
||||
return getComputedLetsencryptServer(appName)
|
||||
}
|
||||
|
||||
func reportLetsencryptServer(appName string) string {
|
||||
return getLetsencryptServer(appName)
|
||||
}
|
||||
|
||||
func reportGlobalLetsencryptServer(appName string) string {
|
||||
return getGlobalLetsencryptServer()
|
||||
}
|
||||
|
||||
func reportGlobalLetsencryptEmailProd(appName string) string {
|
||||
return getGlobalLetsencryptEmailProd()
|
||||
}
|
||||
|
||||
func reportGlobalLetsencryptEmailStag(appName string) string {
|
||||
return getGlobalLetsencryptEmailStag()
|
||||
}
|
||||
|
||||
func reportComputedNamespace(appName string) string {
|
||||
return getComputedNamespace(appName)
|
||||
}
|
||||
|
||||
func reportNamespace(appName string) string {
|
||||
return getNamespace(appName)
|
||||
}
|
||||
|
||||
func reportGlobalNamespace(appName string) string {
|
||||
return getGlobalNamespace()
|
||||
}
|
||||
|
||||
func reportGlobalNetworkInterface(appName string) string {
|
||||
return getGlobalNetworkInterface()
|
||||
}
|
||||
|
||||
func reportComputedRollbackOnFailure(appName string) string {
|
||||
return getComputedRollbackOnFailure(appName)
|
||||
}
|
||||
|
||||
func reportRollbackOnFailure(appName string) string {
|
||||
return getRollbackOnFailure(appName)
|
||||
}
|
||||
|
||||
func reportGlobalRollbackOnFailure(appName string) string {
|
||||
return getGlobalRollbackOnFailure()
|
||||
}
|
||||
132
plugins/scheduler-k3s/scheduler_k3s.go
Normal file
132
plugins/scheduler-k3s/scheduler_k3s.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package scheduler_k3s
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"sync"
|
||||
|
||||
certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
|
||||
traefikv1alpha1 "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
kjson "k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultProperties is a map of all valid k3s properties with corresponding default property values
|
||||
DefaultProperties = map[string]string{
|
||||
"deploy-timeout": "",
|
||||
"letsencrypt-server": "",
|
||||
"image-pull-secrets": "",
|
||||
"namespace": "",
|
||||
"rollback-on-failure": "",
|
||||
}
|
||||
|
||||
// GlobalProperties is a map of all valid global k3s properties
|
||||
GlobalProperties = map[string]bool{
|
||||
"deploy-timeout": true,
|
||||
"image-pull-secrets": true,
|
||||
"letsencrypt-server": true,
|
||||
"letsencrypt-email-prod": true,
|
||||
"letsencrypt-email-stag": true,
|
||||
"namespace": true,
|
||||
"network-interface": true,
|
||||
"rollback-on-failure": true,
|
||||
"token": true,
|
||||
}
|
||||
)
|
||||
|
||||
const KubeConfigPath = "/etc/rancher/k3s/k3s.yaml"
|
||||
|
||||
const RegistryConfigPath = "/etc/rancher/k3s/registries.yaml"
|
||||
|
||||
var (
|
||||
runtimeScheme = runtime.NewScheme()
|
||||
codecs = serializer.NewCodecFactory(runtimeScheme)
|
||||
deserializer = codecs.UniversalDeserializer()
|
||||
jsonSerializer = kjson.NewSerializerWithOptions(kjson.DefaultMetaFactory, runtimeScheme, runtimeScheme, kjson.SerializerOptions{})
|
||||
)
|
||||
|
||||
var k8sNativeSchemeOnce sync.Once
|
||||
|
||||
type Manifest struct {
|
||||
Name string
|
||||
Version string
|
||||
Path string
|
||||
}
|
||||
|
||||
var KubernetesManifests = []Manifest{
|
||||
{
|
||||
Name: "system-upgrader",
|
||||
Version: "0.13.2",
|
||||
Path: "https://github.com/rancher/system-upgrade-controller/releases/download/v0.13.2/system-upgrade-controller.yaml",
|
||||
},
|
||||
}
|
||||
|
||||
type HelmChart struct {
|
||||
ReleaseName string
|
||||
ChartPath string
|
||||
Namespace string
|
||||
CreateNamespace bool
|
||||
Version string
|
||||
Path string
|
||||
RepoURL string
|
||||
}
|
||||
|
||||
var HelmCharts = []HelmChart{
|
||||
{
|
||||
ChartPath: "cert-manager",
|
||||
CreateNamespace: true,
|
||||
Namespace: "cert-manager",
|
||||
ReleaseName: "cert-manager",
|
||||
RepoURL: "https://charts.jetstack.io",
|
||||
Version: "v1.13.3",
|
||||
},
|
||||
{
|
||||
ChartPath: "longhorn",
|
||||
CreateNamespace: true,
|
||||
Namespace: "longhorn-system",
|
||||
ReleaseName: "longhorn",
|
||||
RepoURL: "https://charts.longhorn.io",
|
||||
Version: "1.5.3",
|
||||
},
|
||||
}
|
||||
|
||||
type HelmRepository struct {
|
||||
Name string
|
||||
URL string
|
||||
}
|
||||
|
||||
var HelmRepositories = []HelmRepository{
|
||||
{
|
||||
Name: "jetstack",
|
||||
URL: "https://charts.jetstack.io",
|
||||
},
|
||||
{
|
||||
Name: "longhorn",
|
||||
URL: "https://charts.longhorn.io",
|
||||
},
|
||||
}
|
||||
|
||||
var ServerLabels = map[string]string{
|
||||
"svccontroller.k3s.cattle.io/enablelb": "true",
|
||||
}
|
||||
|
||||
var WorkerLabels = map[string]string{
|
||||
"node-role.kubernetes.io/role": "worker",
|
||||
}
|
||||
|
||||
//go:embed templates/*
|
||||
var templates embed.FS
|
||||
|
||||
func init() {
|
||||
k8sNativeSchemeOnce.Do(func() {
|
||||
_ = appsv1.AddToScheme(runtimeScheme)
|
||||
_ = batchv1.AddToScheme(runtimeScheme)
|
||||
_ = certmanagerv1.AddToScheme(runtimeScheme)
|
||||
_ = corev1.AddToScheme(runtimeScheme)
|
||||
_ = traefikv1alpha1.AddToScheme(runtimeScheme)
|
||||
})
|
||||
}
|
||||
57
plugins/scheduler-k3s/src/commands/commands.go
Normal file
57
plugins/scheduler-k3s/src/commands/commands.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
const (
|
||||
helpHeader = `Usage: dokku scheduler-k3s[:COMMAND]
|
||||
|
||||
Manage scheduler-k3s settings for an app
|
||||
|
||||
Additional commands:`
|
||||
|
||||
helpContent = `
|
||||
scheduler-k3s:initialize, Initializes a cluster
|
||||
scheduler-k3s:report [<app>] [<flag>], Displays a scheduler-k3s report for one or more apps
|
||||
scheduler-k3s:set <app> <property> (<value>), Set or clear a scheduler-k3s property for an app
|
||||
`
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
cmd := flag.Arg(0)
|
||||
switch cmd {
|
||||
case "scheduler-k3s", "scheduler-k3s:help":
|
||||
usage()
|
||||
case "help":
|
||||
command := common.NewShellCmd(fmt.Sprintf("ps -o command= %d", os.Getppid()))
|
||||
command.ShowOutput = false
|
||||
output, err := command.Output()
|
||||
|
||||
if err == nil && strings.Contains(string(output), "--all") {
|
||||
fmt.Println(helpContent)
|
||||
} else {
|
||||
fmt.Print("\n scheduler-k3s, Manage scheduler-k3s settings for an app\n")
|
||||
}
|
||||
default:
|
||||
dokkuNotImplementExitCode, err := strconv.Atoi(os.Getenv("DOKKU_NOT_IMPLEMENTED_EXIT"))
|
||||
if err != nil {
|
||||
fmt.Println("failed to retrieve DOKKU_NOT_IMPLEMENTED_EXIT environment variable")
|
||||
dokkuNotImplementExitCode = 10
|
||||
}
|
||||
os.Exit(dokkuNotImplementExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
common.CommandUsage(helpHeader, helpContent)
|
||||
}
|
||||
83
plugins/scheduler-k3s/src/subcommands/subcommands.go
Normal file
83
plugins/scheduler-k3s/src/subcommands/subcommands.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
scheduler_k3s "github.com/dokku/dokku/plugins/scheduler-k3s"
|
||||
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// main entrypoint to all subcommands
|
||||
func main() {
|
||||
parts := strings.Split(os.Args[0], "/")
|
||||
subcommand := parts[len(parts)-1]
|
||||
|
||||
var err error
|
||||
switch subcommand {
|
||||
case "initialize":
|
||||
args := flag.NewFlagSet("scheduler-k3s:initialize", flag.ExitOnError)
|
||||
taintScheduling := args.Bool("taint-scheduling", false, "taint-scheduling: add a taint against scheduling app workloads")
|
||||
serverIP := args.String("server-ip", "", "server-ip: IP address of the dokku server node")
|
||||
args.Parse(os.Args[2:])
|
||||
err = scheduler_k3s.CommandInitialize(*serverIP, *taintScheduling)
|
||||
case "cluster-add":
|
||||
args := flag.NewFlagSet("scheduler-k3s:cluster-add", flag.ExitOnError)
|
||||
allowUknownHosts := args.Bool("insecure-allow-unknown-hosts", false, "insecure-allow-unknown-hosts: allow unknown hosts")
|
||||
taintScheduling := args.Bool("taint-scheduling", false, "taint-scheduling: add a taint against scheduling app workloads")
|
||||
serverIP := args.String("server-ip", "", "server-ip: IP address of the dokku server node")
|
||||
role := args.String("role", "worker", "role: [ server | worker ]")
|
||||
args.Parse(os.Args[2:])
|
||||
remoteHost := args.Arg(0)
|
||||
err = scheduler_k3s.CommandClusterAdd(*role, remoteHost, *serverIP, *allowUknownHosts, *taintScheduling)
|
||||
case "cluster-list":
|
||||
args := flag.NewFlagSet("scheduler-k3s:cluster-list", flag.ExitOnError)
|
||||
format := args.String("format", "stdout", "format: [ stdout | json ]")
|
||||
args.Parse(os.Args[2:])
|
||||
err = scheduler_k3s.CommandClusterList(*format)
|
||||
case "cluster-remove":
|
||||
args := flag.NewFlagSet("scheduler-k3s:cluster-remove", flag.ExitOnError)
|
||||
args.Parse(os.Args[2:])
|
||||
nodeName := args.Arg(0)
|
||||
err = scheduler_k3s.CommandClusterRemove(nodeName)
|
||||
case "report":
|
||||
args := flag.NewFlagSet("scheduler-k3s:report", flag.ExitOnError)
|
||||
format := args.String("format", "stdout", "format: [ stdout | json ]")
|
||||
osArgs, infoFlag, flagErr := common.ParseReportArgs("scheduler-k3s", os.Args[2:])
|
||||
if flagErr == nil {
|
||||
args.Parse(osArgs)
|
||||
appName := args.Arg(0)
|
||||
err = scheduler_k3s.CommandReport(appName, *format, infoFlag)
|
||||
}
|
||||
case "set":
|
||||
args := flag.NewFlagSet("scheduler-k3s:set", flag.ExitOnError)
|
||||
global := args.Bool("global", false, "--global: set a global property")
|
||||
args.Parse(os.Args[2:])
|
||||
appName := args.Arg(0)
|
||||
property := args.Arg(1)
|
||||
value := args.Arg(2)
|
||||
if *global {
|
||||
appName = "--global"
|
||||
property = args.Arg(0)
|
||||
value = args.Arg(1)
|
||||
}
|
||||
err = scheduler_k3s.CommandSet(appName, property, value)
|
||||
case "show-kubeconfig":
|
||||
args := flag.NewFlagSet("scheduler-k3s:show-kubeconfig", flag.ExitOnError)
|
||||
args.Parse(os.Args[2:])
|
||||
err = scheduler_k3s.CommandShowKubeconfig()
|
||||
case "uninstall":
|
||||
args := flag.NewFlagSet("scheduler-k3s:uninstall", flag.ExitOnError)
|
||||
args.Parse(os.Args[2:])
|
||||
err = scheduler_k3s.CommandUninstall()
|
||||
default:
|
||||
err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
common.LogFailWithError(err)
|
||||
}
|
||||
}
|
||||
123
plugins/scheduler-k3s/src/triggers/triggers.go
Normal file
123
plugins/scheduler-k3s/src/triggers/triggers.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
flag "github.com/spf13/pflag"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
scheduler_k3s "github.com/dokku/dokku/plugins/scheduler-k3s"
|
||||
)
|
||||
|
||||
// main entrypoint to all triggers
|
||||
func main() {
|
||||
parts := strings.Split(os.Args[0], "/")
|
||||
trigger := parts[len(parts)-1]
|
||||
podIdentifier := flag.String("container-id", "", "--container-id: A pod identifier")
|
||||
flag.Parse()
|
||||
|
||||
var err error
|
||||
switch trigger {
|
||||
case "install":
|
||||
err = scheduler_k3s.TriggerInstall()
|
||||
case "post-app-clone-setup":
|
||||
oldAppName := flag.Arg(0)
|
||||
newAppName := flag.Arg(1)
|
||||
err = scheduler_k3s.TriggerPostAppCloneSetup(oldAppName, newAppName)
|
||||
case "post-app-rename-setup":
|
||||
oldAppName := flag.Arg(0)
|
||||
newAppName := flag.Arg(1)
|
||||
err = scheduler_k3s.TriggerPostAppRenameSetup(oldAppName, newAppName)
|
||||
case "post-delete":
|
||||
appName := flag.Arg(0)
|
||||
err = scheduler_k3s.TriggerPostDelete(appName)
|
||||
case "post-registry-login":
|
||||
server := flag.Arg(0)
|
||||
username := flag.Arg(1)
|
||||
err = scheduler_k3s.TriggerPostRegistryLogin(server, username)
|
||||
case "report":
|
||||
appName := flag.Arg(0)
|
||||
err = scheduler_k3s.ReportSingleApp(appName, "", "")
|
||||
case "scheduler-deploy":
|
||||
scheduler := flag.Arg(0)
|
||||
appName := flag.Arg(1)
|
||||
imageTag := flag.Arg(2)
|
||||
err = scheduler_k3s.TriggerSchedulerDeploy(scheduler, appName, imageTag)
|
||||
case "scheduler-enter":
|
||||
scheduler := flag.Arg(0)
|
||||
appName := flag.Arg(1)
|
||||
containerType := flag.Arg(2)
|
||||
args := flag.Args()
|
||||
if len(args) == 2 {
|
||||
_, args = common.ShiftString(args)
|
||||
_, args = common.ShiftString(args)
|
||||
} else if len(args) >= 3 {
|
||||
_, args = common.ShiftString(args)
|
||||
_, args = common.ShiftString(args)
|
||||
_, args = common.ShiftString(args)
|
||||
}
|
||||
|
||||
err = scheduler_k3s.TriggerSchedulerEnter(scheduler, appName, containerType, ptr.Deref(podIdentifier, ""), args)
|
||||
case "scheduler-logs":
|
||||
var tail bool
|
||||
var quiet bool
|
||||
var numLines int64
|
||||
scheduler := flag.Arg(0)
|
||||
appName := flag.Arg(1)
|
||||
processType := flag.Arg(2)
|
||||
tail, err = strconv.ParseBool(flag.Arg(3))
|
||||
if err != nil {
|
||||
tail = false
|
||||
}
|
||||
quiet, err = strconv.ParseBool(flag.Arg(4))
|
||||
if err != nil {
|
||||
quiet = false
|
||||
}
|
||||
|
||||
numLines, err = strconv.ParseInt(flag.Arg(5), 10, 64)
|
||||
if err != nil {
|
||||
numLines = 0
|
||||
}
|
||||
|
||||
err = scheduler_k3s.TriggerSchedulerLogs(scheduler, appName, processType, tail, quiet, numLines)
|
||||
case "scheduler-stop":
|
||||
scheduler := flag.Arg(0)
|
||||
appName := flag.Arg(1)
|
||||
err = scheduler_k3s.TriggerSchedulerStop(scheduler, appName)
|
||||
case "scheduler-post-delete":
|
||||
scheduler := flag.Arg(0)
|
||||
appName := flag.Arg(1)
|
||||
err = scheduler_k3s.TriggerSchedulerPostDelete(scheduler, appName)
|
||||
case "scheduler-run":
|
||||
var envCount int
|
||||
scheduler := flag.Arg(0)
|
||||
appName := flag.Arg(1)
|
||||
envCount, err = strconv.Atoi(flag.Arg(2))
|
||||
if err != nil {
|
||||
envCount = 0
|
||||
}
|
||||
args := flag.Args()
|
||||
if len(args) >= 3 {
|
||||
_, args = common.ShiftString(args)
|
||||
_, args = common.ShiftString(args)
|
||||
_, args = common.ShiftString(args)
|
||||
}
|
||||
|
||||
err = scheduler_k3s.TriggerSchedulerRun(scheduler, appName, envCount, args)
|
||||
case "scheduler-run-list":
|
||||
scheduler := flag.Arg(0)
|
||||
appName := flag.Arg(1)
|
||||
format := flag.Arg(2)
|
||||
err = scheduler_k3s.TriggerSchedulerRunList(scheduler, appName, format)
|
||||
default:
|
||||
err = fmt.Errorf("Invalid plugin trigger call: %s", trigger)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
common.LogFailWithError(err)
|
||||
}
|
||||
}
|
||||
722
plugins/scheduler-k3s/subcommands.go
Normal file
722
plugins/scheduler-k3s/subcommands.go
Normal file
@@ -0,0 +1,722 @@
|
||||
package scheduler_k3s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
resty "github.com/go-resty/resty/v2"
|
||||
"github.com/ryanuber/columnize"
|
||||
)
|
||||
|
||||
// CommandInitialize initializes a k3s cluster on the local server
|
||||
func CommandInitialize(serverIP string, taintScheduling bool) error {
|
||||
if err := isK3sInstalled(); err == nil {
|
||||
return fmt.Errorf("k3s already installed, cannot re-initialize k3s")
|
||||
}
|
||||
|
||||
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()
|
||||
}()
|
||||
|
||||
if serverIP == "" {
|
||||
var err error
|
||||
serverIP, err = getServerIP()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to get server ip address: %w", err)
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet(fmt.Sprintf("Using server ip address: %s", serverIP))
|
||||
}
|
||||
|
||||
common.LogInfo1Quiet("Initializing k3s")
|
||||
|
||||
common.LogInfo2Quiet("Updating apt")
|
||||
aptUpdateCmd, err := common.CallExecCommand(common.ExecCommandInput{
|
||||
Command: "apt-get",
|
||||
Args: []string{
|
||||
"update",
|
||||
},
|
||||
StreamStdio: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call apt-get update command: %w", err)
|
||||
}
|
||||
if aptUpdateCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from apt-get update command: %d", aptUpdateCmd.ExitCode)
|
||||
}
|
||||
|
||||
common.LogInfo2Quiet("Installing k3s dependencies")
|
||||
aptInstallCmd, err := common.CallExecCommand(common.ExecCommandInput{
|
||||
Command: "apt-get",
|
||||
Args: []string{
|
||||
"-y",
|
||||
"install",
|
||||
"ca-certificates",
|
||||
"curl",
|
||||
"open-iscsi",
|
||||
"nfs-common",
|
||||
"wireguard",
|
||||
},
|
||||
StreamStdio: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call apt-get install command: %w", err)
|
||||
}
|
||||
if aptInstallCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from apt-get install command: %d", aptInstallCmd.ExitCode)
|
||||
}
|
||||
|
||||
common.LogInfo2Quiet("Downloading k3s installer")
|
||||
client := resty.New()
|
||||
resp, err := client.R().
|
||||
Get("https://get.k3s.io")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to download k3s installer: %w", err)
|
||||
}
|
||||
if resp == nil {
|
||||
return fmt.Errorf("Missing response from k3s installer download: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode() != 200 {
|
||||
return fmt.Errorf("Invalid status code for k3s installer script: %d", resp.StatusCode())
|
||||
}
|
||||
|
||||
f, err := os.CreateTemp("", "sample")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create temporary file for k3s installer: %w", err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("Unable to close k3s installer file: %w", err)
|
||||
}
|
||||
|
||||
err = common.WriteSliceToFile(common.WriteSliceToFileInput{
|
||||
Filename: f.Name(),
|
||||
Lines: strings.Split(resp.String(), "\n"),
|
||||
Mode: os.FileMode(0755),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to write k3s installer to file: %w", err)
|
||||
}
|
||||
|
||||
fi, err := os.Stat(f.Name())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to get k3s installer file size: %w", err)
|
||||
}
|
||||
|
||||
if fi.Size() == 0 {
|
||||
return fmt.Errorf("Invalid k3s installer filesize")
|
||||
}
|
||||
|
||||
token := getGlobalGlobalToken()
|
||||
if len(token) == 0 {
|
||||
n := 5
|
||||
b := make([]byte, n)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return fmt.Errorf("Unable to generate random node name: %w", err)
|
||||
}
|
||||
|
||||
token = strings.ToLower(fmt.Sprintf("%X", b))
|
||||
if err := CommandSet("--global", "token", token); err != nil {
|
||||
return fmt.Errorf("Unable to set k3s token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
nodeName := serverIP
|
||||
n := 5
|
||||
b := make([]byte, n)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return fmt.Errorf("Unable to generate random node name: %w", err)
|
||||
}
|
||||
nodeName = strings.ReplaceAll(strings.ToLower(fmt.Sprintf("ip-%s-%s", nodeName, fmt.Sprintf("%X", b))), ".", "-")
|
||||
|
||||
args := []string{
|
||||
// initialize the cluster
|
||||
"--cluster-init",
|
||||
// disable local-storage
|
||||
"--disable", "local-storage",
|
||||
// expose etcd metrics
|
||||
"--etcd-expose-metrics",
|
||||
// use wireguard for flannel
|
||||
"--flannel-backend=wireguard-native",
|
||||
// bind controller-manager to all interfaces
|
||||
"--kube-controller-manager-arg", "bind-address=0.0.0.0",
|
||||
// bind proxy metrics to all interfaces
|
||||
"--kube-proxy-arg", "metrics-bind-address=0.0.0.0",
|
||||
// bind scheduler to all interfaces
|
||||
"--kube-scheduler-arg", "bind-address=0.0.0.0",
|
||||
// gc terminated pods
|
||||
"--kube-controller-manager-arg", "terminated-pod-gc-threshold=10",
|
||||
// specify the node name
|
||||
"--node-name", nodeName,
|
||||
// allow access for the dokku user
|
||||
"--write-kubeconfig-mode", "0644",
|
||||
// specify a token
|
||||
"--token", token,
|
||||
}
|
||||
if taintScheduling {
|
||||
args = append(args, "--node-taint", "CriticalAddonsOnly=true:NoSchedule")
|
||||
}
|
||||
|
||||
common.LogInfo2Quiet("Running k3s installer")
|
||||
installerCmd, err := common.CallExecCommand(common.ExecCommandInput{
|
||||
Command: f.Name(),
|
||||
Args: args,
|
||||
StreamStdio: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call k3s installer command: %w", err)
|
||||
}
|
||||
if installerCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from k3s installer command: %d", installerCmd.ExitCode)
|
||||
}
|
||||
|
||||
if err := common.TouchFile(RegistryConfigPath); err != nil {
|
||||
return fmt.Errorf("Error creating initial registries.yaml file")
|
||||
}
|
||||
|
||||
common.LogInfo2Quiet("Setting registries.yaml permissions")
|
||||
registryAclCmd, err := common.CallExecCommand(common.ExecCommandInput{
|
||||
Command: "setfacl",
|
||||
Args: []string{
|
||||
"-m",
|
||||
"user:dokku:rwx",
|
||||
RegistryConfigPath,
|
||||
},
|
||||
StreamStdio: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call setfacl command: %w", err)
|
||||
}
|
||||
if registryAclCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from setfacl command: %d", registryAclCmd.ExitCode)
|
||||
}
|
||||
|
||||
clientset, err := NewKubernetesClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
for _, manifest := range KubernetesManifests {
|
||||
common.LogInfo2Quiet(fmt.Sprintf("Installing %s@%s", manifest.Name, manifest.Version))
|
||||
err = clientset.ApplyKubernetesManifest(ctx, ApplyKubernetesManifestInput{
|
||||
Manifest: manifest.Path,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to apply kubernetes manifest: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range ServerLabels {
|
||||
common.LogInfo2Quiet(fmt.Sprintf("Labeling node %s=%s", key, value))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
err = clientset.LabelNode(ctx, LabelNodeInput{
|
||||
Name: nodeName,
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to patch node: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
common.LogInfo2Quiet("Updating traefik config")
|
||||
contents, err := templates.ReadFile("templates/helm-config/traefik-config.yaml")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to read traefik config template: %w", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile("/var/lib/rancher/k3s/server/manifests/traefik-custom.yaml", contents, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to write traefik config: %w", err)
|
||||
}
|
||||
|
||||
common.LogInfo2Quiet("Installing helm charts")
|
||||
err = installHelmCharts(ctx, clientset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to install helm charts: %w", err)
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet("Done")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandClusterAdd adds a server to the k3s cluster
|
||||
func CommandClusterAdd(role string, remoteHost string, serverIP string, allowUknownHosts bool, taintScheduling bool) error {
|
||||
if err := isK3sInstalled(); err != nil {
|
||||
return fmt.Errorf("k3s not installed, cannot join cluster")
|
||||
}
|
||||
|
||||
if role != "server" && role != "worker" {
|
||||
return fmt.Errorf("Invalid server-type: %s", role)
|
||||
}
|
||||
|
||||
token := getGlobalGlobalToken()
|
||||
if len(token) == 0 {
|
||||
return fmt.Errorf("Missing k3s token")
|
||||
}
|
||||
|
||||
if taintScheduling && role == "worker" {
|
||||
return fmt.Errorf("Taint scheduling can only be used on the server role")
|
||||
}
|
||||
|
||||
if serverIP == "" {
|
||||
var err error
|
||||
serverIP, err = getServerIP()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to get server ip address: %w", err)
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet(fmt.Sprintf("Using server ip address: %s", serverIP))
|
||||
}
|
||||
|
||||
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()
|
||||
}()
|
||||
|
||||
// todo: check if k3s is installed on the remote host
|
||||
|
||||
k3sVersionCmd, err := common.CallExecCommand(common.ExecCommandInput{
|
||||
Command: "k3s",
|
||||
Args: []string{
|
||||
"--version",
|
||||
},
|
||||
CaptureOutput: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call k3s version command: %w", err)
|
||||
}
|
||||
if k3sVersionCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from k3s --version command: %d", k3sVersionCmd.ExitCode)
|
||||
}
|
||||
|
||||
k3sVersion := ""
|
||||
k3sVersionLines := strings.Split(string(k3sVersionCmd.Stdout), "\n")
|
||||
if len(k3sVersionLines) > 0 {
|
||||
k3sVersionParts := strings.Split(k3sVersionLines[0], " ")
|
||||
if len(k3sVersionParts) != 4 {
|
||||
return fmt.Errorf("Unable to get k3s version from k3s --version: %s", k3sVersionCmd.Stdout)
|
||||
}
|
||||
k3sVersion = k3sVersionParts[2]
|
||||
}
|
||||
common.LogDebug(fmt.Sprintf("k3s version: %s", k3sVersion))
|
||||
|
||||
common.LogInfo1(fmt.Sprintf("Joining %s to k3s cluster as %s", remoteHost, role))
|
||||
common.LogInfo2Quiet("Updating apt")
|
||||
aptUpdateCmd, err := common.CallSshCommand(common.SshCommandInput{
|
||||
Command: "apt-get",
|
||||
Args: []string{
|
||||
"update",
|
||||
},
|
||||
AllowUknownHosts: allowUknownHosts,
|
||||
RemoteHost: remoteHost,
|
||||
StreamStdio: true,
|
||||
Sudo: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call apt-get update command over ssh: %w", err)
|
||||
}
|
||||
if aptUpdateCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from apt-get update command over ssh: %d", aptUpdateCmd.ExitCode)
|
||||
}
|
||||
|
||||
common.LogInfo2Quiet("Installing k3s dependencies")
|
||||
aptInstallCmd, err := common.CallSshCommand(common.SshCommandInput{
|
||||
Command: "apt-get",
|
||||
Args: []string{
|
||||
"-y",
|
||||
"install",
|
||||
"ca-certificates",
|
||||
"curl",
|
||||
"open-iscsi",
|
||||
"nfs-common",
|
||||
"wireguard",
|
||||
},
|
||||
AllowUknownHosts: allowUknownHosts,
|
||||
RemoteHost: remoteHost,
|
||||
StreamStdio: true,
|
||||
Sudo: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call apt-get install command over ssh: %w", err)
|
||||
}
|
||||
if aptInstallCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from apt-get install command over ssh: %d", aptInstallCmd.ExitCode)
|
||||
}
|
||||
|
||||
common.LogInfo2Quiet("Downloading k3s installer")
|
||||
curlTask, err := common.CallSshCommand(common.SshCommandInput{
|
||||
Command: "curl",
|
||||
Args: []string{
|
||||
"-o /tmp/k3s-installer.sh",
|
||||
"https://get.k3s.io",
|
||||
},
|
||||
AllowUknownHosts: allowUknownHosts,
|
||||
RemoteHost: remoteHost,
|
||||
StreamStdio: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call curl command over ssh: %w", err)
|
||||
}
|
||||
if curlTask.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from curl command over ssh: %d", curlTask.ExitCode)
|
||||
}
|
||||
|
||||
common.LogInfo2Quiet("Setting k3s installer permissions")
|
||||
chmodCmd, err := common.CallSshCommand(common.SshCommandInput{
|
||||
Command: "chmod",
|
||||
Args: []string{
|
||||
"0755",
|
||||
"/tmp/k3s-installer.sh",
|
||||
},
|
||||
AllowUknownHosts: allowUknownHosts,
|
||||
RemoteHost: remoteHost,
|
||||
StreamStdio: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call chmod command over ssh: %w", err)
|
||||
}
|
||||
if chmodCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from chmod command over ssh: %d", chmodCmd.ExitCode)
|
||||
}
|
||||
|
||||
u, err := url.Parse(remoteHost)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse remote host: %w", err)
|
||||
}
|
||||
|
||||
nodeName := u.Hostname()
|
||||
n := 5
|
||||
b := make([]byte, n)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return fmt.Errorf("Unable to generate random node name: %w", err)
|
||||
}
|
||||
nodeName = strings.ReplaceAll(strings.ToLower(fmt.Sprintf("ip-%s-%s", nodeName, fmt.Sprintf("%X", b))), ".", "-")
|
||||
|
||||
args := []string{
|
||||
// disable local-storage
|
||||
"--disable", "local-storage",
|
||||
// use wireguard for flannel
|
||||
"--flannel-backend=wireguard-native",
|
||||
// specify the node name
|
||||
"--node-name", nodeName,
|
||||
// server to connect to as the main
|
||||
"--server",
|
||||
fmt.Sprintf("https://%s:6443", serverIP),
|
||||
// specify a token
|
||||
"--token",
|
||||
token,
|
||||
}
|
||||
|
||||
if role == "server" {
|
||||
args = append([]string{"server"}, args...)
|
||||
// expose etcd metrics
|
||||
args = append(args, "--etcd-expose-metrics")
|
||||
// bind controller-manager to all interfaces
|
||||
args = append(args, "--kube-controller-manager-arg", "bind-address=0.0.0.0")
|
||||
// bind proxy metrics to all interfaces
|
||||
args = append(args, "--kube-proxy-arg", "metrics-bind-address=0.0.0.0")
|
||||
// bind scheduler to all interfaces
|
||||
args = append(args, "--kube-scheduler-arg", "bind-address=0.0.0.0")
|
||||
// gc terminated pods
|
||||
args = append(args, "--kube-controller-manager-arg", "terminated-pod-gc-threshold=10")
|
||||
// allow access for the dokku user
|
||||
args = append(args, "--write-kubeconfig-mode", "0644")
|
||||
} else {
|
||||
// disable etcd on workers
|
||||
args = append(args, "--disable-etcd")
|
||||
// disable apiserver on workers
|
||||
args = append(args, "--disable-apiserver")
|
||||
// disable controller-manager on workers
|
||||
args = append(args, "--disable-controller-manager")
|
||||
// disable scheduler on workers
|
||||
args = append(args, "--disable-scheduler")
|
||||
// bind proxy metrics to all interfaces
|
||||
args = append(args, "--kube-proxy-arg", "metrics-bind-address=0.0.0.0")
|
||||
}
|
||||
|
||||
if taintScheduling {
|
||||
args = append(args, "--node-taint", "CriticalAddonsOnly=true:NoSchedule")
|
||||
}
|
||||
|
||||
common.LogInfo2Quiet(fmt.Sprintf("Adding %s k3s cluster", nodeName))
|
||||
joinCmd, err := common.CallSshCommand(common.SshCommandInput{
|
||||
Command: "/tmp/k3s-installer.sh",
|
||||
Args: args,
|
||||
AllowUknownHosts: allowUknownHosts,
|
||||
RemoteHost: remoteHost,
|
||||
StreamStdio: true,
|
||||
Sudo: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call k3s installer command over ssh: %w", err)
|
||||
}
|
||||
if joinCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from k3s installer command over ssh: %d", joinCmd.ExitCode)
|
||||
}
|
||||
|
||||
clientset, err := NewKubernetesClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
common.LogInfo2Quiet("Waiting for node to exist")
|
||||
nodes, err := waitForNodeToExist(ctx, WaitForNodeToExistInput{
|
||||
Clientset: clientset,
|
||||
NodeName: nodeName,
|
||||
RetryCount: 20,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error waiting for pod to exist: %w", err)
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("Unable to find node after joining cluster, node will not be annotated/labeled appropriately access registry secrets")
|
||||
}
|
||||
|
||||
err = copyRegistryToNode(ctx, remoteHost)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error copying registries.yaml to remote host: %w", err)
|
||||
}
|
||||
|
||||
labels := ServerLabels
|
||||
if role == "worker" {
|
||||
labels = WorkerLabels
|
||||
}
|
||||
|
||||
for key, value := range labels {
|
||||
common.LogInfo2Quiet(fmt.Sprintf("Labeling node %s=%s", key, value))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
err = clientset.LabelNode(ctx, LabelNodeInput{
|
||||
Name: nodeName,
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to patch node: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
common.LogInfo2Quiet("Annotating node with connection information")
|
||||
err = clientset.AnnotateNode(ctx, AnnotateNodeInput{
|
||||
Name: nodes[0].Name,
|
||||
Key: "dokku.com/remote-host",
|
||||
Value: remoteHost,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to patch node: %w", err)
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet("Done")
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandClusterList lists the nodes in the k3s cluster
|
||||
func CommandClusterList(format string) error {
|
||||
if format != "stdout" && format != "json" {
|
||||
return fmt.Errorf("Invalid format: %s", format)
|
||||
}
|
||||
if err := isK3sInstalled(); err != nil {
|
||||
return fmt.Errorf("k3s not installed, cannot list cluster nodes")
|
||||
}
|
||||
|
||||
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()
|
||||
}()
|
||||
|
||||
clientset, err := NewKubernetesClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
nodes, err := clientset.ListNodes(ctx, ListNodesInput{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to list nodes: %w", err)
|
||||
}
|
||||
|
||||
output := []Node{}
|
||||
for _, node := range nodes {
|
||||
output = append(output, kubernetesNodeToNode(node))
|
||||
}
|
||||
|
||||
if format == "stdout" {
|
||||
lines := []string{"name|ready|roles|version"}
|
||||
for _, node := range output {
|
||||
lines = append(lines, node.String())
|
||||
}
|
||||
|
||||
columnized := columnize.SimpleFormat(lines)
|
||||
fmt.Println(columnized)
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := json.Marshal(output)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to marshal json: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(string(b))
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandClusterRemove removes a node from the k3s cluster
|
||||
func CommandClusterRemove(nodeName string) error {
|
||||
if err := isK3sInstalled(); err != nil {
|
||||
return fmt.Errorf("k3s not installed, cannot remove node")
|
||||
}
|
||||
|
||||
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()
|
||||
}()
|
||||
|
||||
common.LogInfo1Quiet(fmt.Sprintf("Removing %s from k3s cluster", nodeName))
|
||||
clientset, err := NewKubernetesClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet("Getting node remote connection information")
|
||||
node, err := clientset.GetNode(ctx, GetNodeInput{
|
||||
Name: nodeName,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to get node: %w", err)
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet("Checking if node is a remote node managed by Dokku")
|
||||
if node.RemoteHost == "" {
|
||||
return fmt.Errorf("Node %s is not a remote node managed by Dokku", nodeName)
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet("Uninstalling k3s on remote host")
|
||||
removeCmd, err := common.CallSshCommand(common.SshCommandInput{
|
||||
Command: "/usr/local/bin/k3s-uninstall.sh",
|
||||
Args: []string{},
|
||||
AllowUknownHosts: true,
|
||||
RemoteHost: node.RemoteHost,
|
||||
StreamStdio: true,
|
||||
Sudo: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call k3s uninstall command over ssh: %w", err)
|
||||
}
|
||||
|
||||
if removeCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from k3s uninstall command over ssh: %d", removeCmd.ExitCode)
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet("Deleting node from k3s cluster")
|
||||
err = clientset.DeleteNode(ctx, DeleteNodeInput{
|
||||
Name: nodeName,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to delete node: %w", err)
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet("Done")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandReport displays a scheduler-k3s report for one or more apps
|
||||
func CommandReport(appName string, format string, infoFlag string) error {
|
||||
if len(appName) == 0 {
|
||||
apps, err := common.DokkuApps()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, appName := range apps {
|
||||
if err := ReportSingleApp(appName, format, infoFlag); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return ReportSingleApp(appName, format, infoFlag)
|
||||
}
|
||||
|
||||
// CommandSet set or clear a scheduler-k3s property for an app
|
||||
func CommandSet(appName string, property string, value string) error {
|
||||
common.CommandPropertySet("scheduler-k3s", appName, property, value, DefaultProperties, GlobalProperties)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandShowKubeconfig displays the kubeconfig file contents
|
||||
func CommandShowKubeconfig() error {
|
||||
if !common.FileExists(KubeConfigPath) {
|
||||
return fmt.Errorf("Kubeconfig file does not exist: %s", KubeConfigPath)
|
||||
}
|
||||
|
||||
b, err := os.ReadFile(KubeConfigPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to read kubeconfig file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(string(b))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CommandUninstall() error {
|
||||
if err := isK3sInstalled(); err != nil {
|
||||
return fmt.Errorf("k3s not installed, cannot uninstall")
|
||||
}
|
||||
|
||||
common.LogInfo1("Uninstalling k3s")
|
||||
uninstallerCmd, err := common.CallExecCommand(common.ExecCommandInput{
|
||||
Command: "/usr/local/bin/k3s-uninstall.sh",
|
||||
StreamStdio: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to call k3s uninstaller command: %w", err)
|
||||
}
|
||||
if uninstallerCmd.ExitCode != 0 {
|
||||
return fmt.Errorf("Invalid exit code from k3s uninstaller command: %d", uninstallerCmd.ExitCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
352
plugins/scheduler-k3s/template.go
Normal file
352
plugins/scheduler-k3s/template.go
Normal file
@@ -0,0 +1,352 @@
|
||||
package scheduler_k3s
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
"gopkg.in/yaml.v3"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
type Chart struct {
|
||||
ApiVersion string `yaml:"apiVersion"`
|
||||
AppVersion string `yaml:"appVersion"`
|
||||
Name string `yaml:"name"`
|
||||
Version string `yaml:"version"`
|
||||
}
|
||||
|
||||
type ClusterIssuerValues struct {
|
||||
ClusterIssuers map[string]ClusterIssuer `yaml:"cluster_issuers"`
|
||||
}
|
||||
|
||||
type AppValues struct {
|
||||
Global GlobalValues `yaml:"global"`
|
||||
Processes map[string]ProcessValues `yaml:"processes"`
|
||||
}
|
||||
|
||||
type GlobalValues struct {
|
||||
AppName string `yaml:"app_name"`
|
||||
DeploymentID string `yaml:"deploment_id"`
|
||||
Image GlobalImage `yaml:"image"`
|
||||
Namespace string `yaml:"namespace"`
|
||||
PrimaryPort int32 `yaml:"primary_port"`
|
||||
Secrets map[string]string `yaml:"secrets,omitempty"`
|
||||
}
|
||||
|
||||
type GlobalImage struct {
|
||||
ImagePullSecrets string `yaml:"image_pull_secrets"`
|
||||
Name string `yaml:"name"`
|
||||
Type string `yaml:"type"`
|
||||
WorkingDir string `yaml:"working_dir"`
|
||||
}
|
||||
|
||||
type ProcessValues struct {
|
||||
Args []string `yaml:"args,omitempty"`
|
||||
Cron ProcessCron `yaml:"cron,omitempty"`
|
||||
Healthchecks ProcessHealthchecks `yaml:"healthchecks,omitempty"`
|
||||
ProcessType ProcessType `yaml:"process_type"`
|
||||
Replicas int32 `yaml:"replicas"`
|
||||
Resources ProcessResourcesMap `yaml:"resources,omitempty"`
|
||||
Web ProcessWeb `yaml:"web,omitempty"`
|
||||
}
|
||||
|
||||
type ProcessHealthchecks struct {
|
||||
Liveness ProcessHealthcheck `yaml:"liveness,omitempty"`
|
||||
Readiness ProcessHealthcheck `yaml:"readiness,omitempty"`
|
||||
Startup ProcessHealthcheck `yaml:"startup,omitempty"`
|
||||
MinReadySeconds int32 `yaml:"min_ready_seconds,omitempty"`
|
||||
}
|
||||
|
||||
type ProcessHealthcheck struct {
|
||||
Exec *ExecHealthcheck `yaml:"exec,omitempty"`
|
||||
HTTPGet *HTTPHealthcheck `yaml:"httpGet,omitempty"`
|
||||
TCPSocket *TCPHealthcheck `yaml:"tcpSocket,omitempty"`
|
||||
|
||||
InitialDelaySeconds int32 `yaml:"initialDelaySeconds,omitempty"`
|
||||
TimeoutSeconds int32 `yaml:"timeoutSeconds,omitempty"`
|
||||
PeriodSeconds int32 `yaml:"periodSeconds,omitempty"`
|
||||
SuccessThreshold int32 `yaml:"successThreshold,omitempty"`
|
||||
FailureThreshold int32 `yaml:"failureThreshold,omitempty"`
|
||||
TerminationGracePeriodSeconds *int64 `yaml:"terminationGracePeriodSeconds,omitempty"`
|
||||
}
|
||||
|
||||
type ExecHealthcheck struct {
|
||||
Command []string `yaml:"command,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPHealthcheck struct {
|
||||
Path string `yaml:"path,omitempty"`
|
||||
Port int32 `yaml:"port,omitempty"`
|
||||
Host string `yaml:"host,omitempty"`
|
||||
Scheme URIScheme `yaml:"scheme,omitempty"`
|
||||
HTTPHeaders []HTTPHeader `yaml:"httpHeaders,omitempty"`
|
||||
}
|
||||
|
||||
type TCPHealthcheck struct {
|
||||
Port int32 `yaml:"port,omitempty"`
|
||||
Host string `yaml:"host,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPHeader struct {
|
||||
Name string `yaml:"name"`
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
|
||||
type URIScheme string
|
||||
|
||||
const (
|
||||
URISchemeHTTP URIScheme = "HTTP"
|
||||
URISchemeHTTPS URIScheme = "HTTPS"
|
||||
)
|
||||
|
||||
type ProcessWeb struct {
|
||||
Domains []string `yaml:"domains,omitempty"`
|
||||
PortMaps []ProcessPortMap `yaml:"port_maps,omitempty"`
|
||||
TLS ProcessTls `yaml:"tls"`
|
||||
}
|
||||
|
||||
type ProcessResourcesMap struct {
|
||||
Limits ProcessResources `yaml:"limits,omitempty"`
|
||||
Requests ProcessResources `yaml:"requests,omitempty"`
|
||||
}
|
||||
|
||||
type ProcessResources struct {
|
||||
NvidiaGPU string `yaml:"nvidia.com/gpu,omitempty"`
|
||||
CPU string `yaml:"cpu,omitempty"`
|
||||
Memory string `yaml:"memory,omitempty"`
|
||||
}
|
||||
|
||||
type ProcessType string
|
||||
|
||||
const (
|
||||
ProcessType_Cron ProcessType = "cron"
|
||||
ProcessType_Job ProcessType = "job"
|
||||
ProcessType_Web ProcessType = "web"
|
||||
ProcessType_Worker ProcessType = "worker"
|
||||
)
|
||||
|
||||
type ProcessCron struct {
|
||||
ID string `yaml:"id"`
|
||||
Schedule string `yaml:"schedule"`
|
||||
Suffix string `yaml:"suffix"`
|
||||
}
|
||||
|
||||
type ProcessPortMap struct {
|
||||
ContainerPort int32 `yaml:"container_port"`
|
||||
HostPort int32 `yaml:"host_port"`
|
||||
Scheme string `yaml:"scheme"`
|
||||
Protocol PortmapProtocol `yaml:"protocol"`
|
||||
Name string `yaml:"name"`
|
||||
RedirectToHttps bool `yaml:"redirect_to_https"`
|
||||
}
|
||||
|
||||
type PortmapProtocol string
|
||||
|
||||
const (
|
||||
PortmapProtocol_TCP PortmapProtocol = "TCP"
|
||||
PortmapProtocol_UDP PortmapProtocol = "UDP"
|
||||
)
|
||||
|
||||
type NameSorter []ProcessPortMap
|
||||
|
||||
func (a NameSorter) Len() int { return len(a) }
|
||||
func (a NameSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a NameSorter) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
||||
|
||||
type ProcessTls struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
IssuerName string `yaml:"issuer_name"`
|
||||
}
|
||||
|
||||
type ClusterIssuer struct {
|
||||
Email string `yaml:"email"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
IngressClass string `yaml:"ingress_class"`
|
||||
Name string `yaml:"name"`
|
||||
Server string `yaml:"server"`
|
||||
}
|
||||
|
||||
type Job struct {
|
||||
AppName string
|
||||
Command []string
|
||||
DeploymentID int64
|
||||
Entrypoint string
|
||||
Env map[string]string
|
||||
ID string
|
||||
Image string
|
||||
ImagePullSecrets string
|
||||
ImageSourceType string
|
||||
Interactive bool
|
||||
Labels map[string]string
|
||||
Namespace string
|
||||
ProcessType string
|
||||
Schedule string
|
||||
Suffix string
|
||||
RemoveContainer bool
|
||||
WorkingDir string
|
||||
}
|
||||
|
||||
func templateKubernetesJob(input Job) (batchv1.Job, error) {
|
||||
labels := map[string]string{
|
||||
"app.kubernetes.io/instance": fmt.Sprintf("%s-%s", input.AppName, input.ProcessType),
|
||||
"app.kubernetes.io/name": input.ProcessType,
|
||||
"app.kubernetes.io/part-of": input.AppName,
|
||||
}
|
||||
annotations := map[string]string{
|
||||
"app.kubernetes.io/version": fmt.Sprint(input.DeploymentID),
|
||||
"dokku.com/builder-type": input.ImageSourceType,
|
||||
"dokku.com/managed": "true",
|
||||
}
|
||||
|
||||
for key, value := range input.Labels {
|
||||
labels[key] = value
|
||||
}
|
||||
secretName := fmt.Sprintf("env-%s.%d", input.AppName, input.DeploymentID)
|
||||
|
||||
env := []corev1.EnvVar{}
|
||||
for key, value := range input.Env {
|
||||
env = append(env, corev1.EnvVar{
|
||||
Name: key,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
|
||||
suffix := input.Suffix
|
||||
if suffix == "" {
|
||||
n := 5
|
||||
b := make([]byte, n)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
suffix = strings.ToLower(fmt.Sprintf("%X", b))
|
||||
}
|
||||
annotations["dokku.com/job-suffix"] = suffix
|
||||
|
||||
podAnnotations := annotations
|
||||
podAnnotations["kubectl.kubernetes.io/default-container"] = fmt.Sprintf("%s-%s", input.AppName, input.ProcessType)
|
||||
|
||||
job := batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s-%s-%s", input.AppName, input.ProcessType, suffix),
|
||||
Namespace: input.Namespace,
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
BackoffLimit: ptr.To(int32(0)),
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: labels,
|
||||
Annotations: podAnnotations,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Args: input.Command,
|
||||
Name: fmt.Sprintf("%s-%s", input.AppName, input.ProcessType),
|
||||
Env: env,
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: secretName,
|
||||
},
|
||||
Optional: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Image: input.Image,
|
||||
ImagePullPolicy: corev1.PullAlways,
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Limits: corev1.ResourceList{},
|
||||
Requests: corev1.ResourceList{},
|
||||
},
|
||||
WorkingDir: input.WorkingDir,
|
||||
},
|
||||
},
|
||||
RestartPolicy: corev1.RestartPolicyNever,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if input.Entrypoint != "" {
|
||||
job.Spec.Template.Spec.Containers[0].Command = []string{input.Entrypoint}
|
||||
}
|
||||
|
||||
if input.Interactive {
|
||||
job.Spec.Template.Spec.Containers[0].Stdin = true
|
||||
job.Spec.Template.Spec.Containers[0].StdinOnce = true
|
||||
job.Spec.Template.Spec.Containers[0].TTY = true
|
||||
}
|
||||
|
||||
if input.RemoveContainer {
|
||||
job.Spec.TTLSecondsAfterFinished = ptr.To(int32(60))
|
||||
}
|
||||
|
||||
if input.ImagePullSecrets != "" {
|
||||
job.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{
|
||||
{
|
||||
Name: input.ImagePullSecrets,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
processResources, err := getProcessResources(input.AppName, input.ProcessType)
|
||||
if err != nil {
|
||||
return job, fmt.Errorf("Error getting process resources: %w", err)
|
||||
}
|
||||
if processResources.Limits.CPU != "" {
|
||||
cpuQuantity, _ := resource.ParseQuantity(processResources.Limits.CPU)
|
||||
job.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"] = cpuQuantity
|
||||
}
|
||||
if processResources.Limits.Memory != "" {
|
||||
memoryQuantity, _ := resource.ParseQuantity(processResources.Limits.Memory)
|
||||
job.Spec.Template.Spec.Containers[0].Resources.Limits["memory"] = memoryQuantity
|
||||
}
|
||||
if processResources.Limits.NvidiaGPU != "" {
|
||||
nvidiaGpuQuantity, _ := resource.ParseQuantity(processResources.Limits.NvidiaGPU)
|
||||
job.Spec.Template.Spec.Containers[0].Resources.Limits["nvidia.com/gpu"] = nvidiaGpuQuantity
|
||||
}
|
||||
if processResources.Requests.CPU != "" {
|
||||
cpuQuantity, _ := resource.ParseQuantity(processResources.Requests.CPU)
|
||||
job.Spec.Template.Spec.Containers[0].Resources.Requests["cpu"] = cpuQuantity
|
||||
}
|
||||
if processResources.Requests.Memory != "" {
|
||||
memoryQuantity, _ := resource.ParseQuantity(processResources.Requests.Memory)
|
||||
job.Spec.Template.Spec.Containers[0].Resources.Requests["memory"] = memoryQuantity
|
||||
}
|
||||
|
||||
return job, nil
|
||||
}
|
||||
|
||||
type WriteYamlInput struct {
|
||||
Object interface{}
|
||||
Path string
|
||||
}
|
||||
|
||||
func writeYaml(input WriteYamlInput) error {
|
||||
common.LogDebug(fmt.Sprintf("Printing resource: %s", input.Path))
|
||||
data, err := yaml.Marshal(input.Object)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error marshalling chart: %w", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(input.Path, data, os.FileMode(0644))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error writing chart: %w", err)
|
||||
}
|
||||
|
||||
if os.Getenv("DOKKU_TRACE") == "1" {
|
||||
common.CatFile(input.Path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
30
plugins/scheduler-k3s/templates/chart/certificate.yaml
Normal file
30
plugins/scheduler-k3s/templates/chart/certificate.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
{{- $processName := "PROCESS_NAME" }}
|
||||
{{- $config := .Values.processes.PROCESS_NAME }}
|
||||
{{- if and $config.web.tls.enabled $config.web.domains }}
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
annotations:
|
||||
dokku.com/managed: "true"
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
app.kubernetes.io/part-of: {{ $.Values.global.app_name }}
|
||||
name: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
namespace: {{ $.Values.global.namespace }}
|
||||
spec:
|
||||
issuerRef:
|
||||
kind: ClusterIssuer
|
||||
name: {{ $config.web.tls.issuer_name }}
|
||||
secretName: tls-{{ $.Values.global.app_name }}-{{ $processName }}
|
||||
secretTemplate:
|
||||
annotations:
|
||||
dokku.com/managed: "true"
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
app.kubernetes.io/part-of: {{ $.Values.global.app_name }}
|
||||
dnsNames:
|
||||
{{- range $config.web.domains }}
|
||||
- {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
21
plugins/scheduler-k3s/templates/chart/cluster-issuer.yaml
Normal file
21
plugins/scheduler-k3s/templates/chart/cluster-issuer.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
{{- range $name, $config := .Values.cluster_issuers }}
|
||||
{{- if $config.enabled }}
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
annotations:
|
||||
dokku.com/managed: "true"
|
||||
name: {{ $config.name }}
|
||||
spec:
|
||||
acme:
|
||||
email: {{ $config.email }}
|
||||
server: {{ $config.server }}
|
||||
privateKeySecretRef:
|
||||
name: {{ $config.name }}
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: {{ $config.ingress_class }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
101
plugins/scheduler-k3s/templates/chart/cron-job.yaml
Normal file
101
plugins/scheduler-k3s/templates/chart/cron-job.yaml
Normal file
@@ -0,0 +1,101 @@
|
||||
{{- $processName := "CRON_ID" }}
|
||||
{{- $config := .Values.processes.CRON_ID }}
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
annotations:
|
||||
app.kubernetes.io/version: {{ $.Values.global.deploment_id | quote }}
|
||||
dokku.com/builder-type: {{ $.Values.global.image.type }}
|
||||
dokku.com/cron-id: {{ $config.cron.id }}
|
||||
dokku.com/job-suffix: {{ $config.cron.suffix }}
|
||||
dokku.com/managed: "true"
|
||||
kubectl.kubernetes.io/default-container: {{ $.Values.global.app_name }}-cron
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ $.Values.global.app_name }}-cron-{{ $config.cron.suffix }}
|
||||
app.kubernetes.io/name: cron
|
||||
app.kubernetes.io/part-of: {{ $.Values.global.app_name }}
|
||||
dokku.com/cron-id: {{ $config.cron.id }}
|
||||
name: {{ $.Values.global.app_name }}-cron-{{ $config.cron.suffix }}
|
||||
namespace: default
|
||||
spec:
|
||||
concurrencyPolicy: Allow
|
||||
failedJobsHistoryLimit: 10
|
||||
jobTemplate:
|
||||
metadata:
|
||||
annotations:
|
||||
app.kubernetes.io/version: {{ $.Values.global.deploment_id | quote }}
|
||||
dokku.com/builder-type: {{ $.Values.global.image.type }}
|
||||
dokku.com/cron-id: {{ $config.cron.id }}
|
||||
dokku.com/job-suffix: {{ $config.cron.suffix }}
|
||||
dokku.com/managed: "true"
|
||||
kubectl.kubernetes.io/default-container: {{ $.Values.global.app_name }}-cron
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ $.Values.global.app_name }}-cron-{{ $config.cron.suffix }}
|
||||
app.kubernetes.io/name: cron
|
||||
app.kubernetes.io/part-of: {{ $.Values.global.app_name }}
|
||||
dokku.com/cron-id: {{ $config.cron.id }}
|
||||
spec:
|
||||
backoffLimit: 0
|
||||
podReplacementPolicy: Failed
|
||||
ttlSecondsAfterFinished: 60
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
app.kubernetes.io/version: {{ $.Values.global.deploment_id | quote }}
|
||||
dokku.com/builder-type: {{ $.Values.global.image.type }}
|
||||
dokku.com/cron-id: {{ $config.cron.id }}
|
||||
dokku.com/job-suffix: {{ $config.cron.suffix }}
|
||||
dokku.com/managed: "true"
|
||||
kubectl.kubernetes.io/default-container: {{ $.Values.global.app_name }}-cron
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ $.Values.global.app_name }}-cron-{{ $config.cron.suffix }}
|
||||
app.kubernetes.io/name: cron
|
||||
app.kubernetes.io/part-of: {{ $.Values.global.app_name }}
|
||||
dokku.com/cron-id: {{ $config.cron.id }}
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
{{- range $config.args }}
|
||||
- {{ . }}
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: env-{{ $.Values.global.app_name }}.{{ $.Values.global.deploment_id }}
|
||||
optional: true
|
||||
image: {{ $.Values.global.image.name }}
|
||||
imagePullPolicy: Always
|
||||
name: {{ $.Values.global.app_name }}-cron
|
||||
{{- if and $config.resources (or $config.resources.limits $config.resources.requests) }}
|
||||
resources:
|
||||
{{- if $config.resources.limits }}
|
||||
limits:
|
||||
{{- if $config.resources.limits.cpu }}
|
||||
cpu: {{ $config.resources.limits.cpu }}
|
||||
{{- end }}
|
||||
{{- if $config.resources.limits.memory }}
|
||||
memory: {{ $config.resources.limits.memory }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if $config.resources.requests }}
|
||||
requests:
|
||||
{{- if $config.resources.requests.cpu }}
|
||||
cpu: {{ $config.resources.requests.cpu }}
|
||||
{{- end }}
|
||||
{{- if $config.resources.requests.memory }}
|
||||
memory: {{ $config.resources.requests.memory }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if $.Values.global.image.working_dir }}
|
||||
workingDir: {{ $.Values.global.image.working_dir }}
|
||||
{{- end }}
|
||||
{{- if $.Values.global.image.image_pull_secrets }}
|
||||
imagePullSecrets: {{ $.Values.global.image.image_pull_secrets }}
|
||||
{{- end }}
|
||||
restartPolicy: Never
|
||||
schedule: {{ $config.cron.schedule }}
|
||||
startingDeadlineSeconds: 60
|
||||
successfulJobsHistoryLimit: 10
|
||||
suspend: false
|
||||
timeZone: Etc/UTC
|
||||
94
plugins/scheduler-k3s/templates/chart/deployment.yaml
Normal file
94
plugins/scheduler-k3s/templates/chart/deployment.yaml
Normal file
@@ -0,0 +1,94 @@
|
||||
{{- $processName := "PROCESS_NAME" }}
|
||||
{{- $config := .Values.processes.PROCESS_NAME }}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
app.kubernetes.io/version: {{ $.Values.global.deploment_id | quote }}
|
||||
dokku.com/builder-type: {{ $.Values.global.image.type }}
|
||||
dokku.com/managed: "true"
|
||||
kubectl.kubernetes.io/default-container: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
app.kubernetes.io/name: {{ $processName }}
|
||||
app.kubernetes.io/part-of: {{ $.Values.global.app_name }}
|
||||
name: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: {{ $config.replicas }}
|
||||
revisionHistoryLimit: 5
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/instance: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
app.kubernetes.io/name: {{ $processName }}
|
||||
app.kubernetes.io/part-of: {{ $.Values.global.app_name }}
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
app.kubernetes.io/version: {{ $.Values.global.deploment_id | quote }}
|
||||
dokku.com/builder-type: {{ $.Values.global.image.type }}
|
||||
dokku.com/managed: "true"
|
||||
kubectl.kubernetes.io/default-container: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
app.kubernetes.io/name: {{ $processName }}
|
||||
app.kubernetes.io/part-of: {{ $.Values.global.app_name }}
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
{{- range $config.args }}
|
||||
- {{ . }}
|
||||
{{- end }}
|
||||
{{- if eq $processName "web" }}
|
||||
env:
|
||||
- name: PORT
|
||||
value: "{{ $.Values.global.primary_port }}"
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: env-{{ $.Values.global.app_name }}.{{ $.Values.global.deploment_id }}
|
||||
optional: true
|
||||
image: {{ $.Values.global.image.name }}
|
||||
imagePullPolicy: Always
|
||||
name: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
{{- if eq $processName "web" }}
|
||||
ports:
|
||||
{{- range $pdx, $port_map := $config.web.port_maps }}
|
||||
- containerPort: {{ $port_map.container_port }}
|
||||
name: {{ $port_map.name }}
|
||||
protocol: {{ $port_map.protocol }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if and $config.resources (or $config.resources.limits $config.resources.requests) }}
|
||||
resources:
|
||||
{{- if $config.resources.limits }}
|
||||
limits:
|
||||
{{- if $config.resources.limits.cpu }}
|
||||
cpu: {{ $config.resources.limits.cpu }}
|
||||
{{- end }}
|
||||
{{- if $config.resources.limits.memory }}
|
||||
memory: {{ $config.resources.limits.memory }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if $config.resources.requests }}
|
||||
requests:
|
||||
{{- if $config.resources.requests.cpu }}
|
||||
cpu: {{ $config.resources.requests.cpu }}
|
||||
{{- end }}
|
||||
{{- if $config.resources.requests.memory }}
|
||||
memory: {{ $config.resources.requests.memory }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if $config.healthchecks.startup }}
|
||||
startupProbe:
|
||||
{{ $config.healthchecks.startup | toJson | indent 10 }}
|
||||
{{- end }}
|
||||
{{- if $.Values.global.image.working_dir }}
|
||||
workingDir: {{ $.Values.global.image.working_dir }}
|
||||
{{- end }}
|
||||
{{- if $.Values.global.image.image_pull_secrets }}
|
||||
imagePullSecrets: {{ $.Values.global.image.image_pull_secrets }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
annotations:
|
||||
dokku.com/managed: "true"
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ $.Values.global.app_name }}-redirect-to-https
|
||||
app.kubernetes.io/part-of: {{ $.Values.global.app_name }}
|
||||
name: {{ $.Values.global.app_name}}-redirect-to-https
|
||||
namespace: {{ $.Values.global.namespace }}
|
||||
spec:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
45
plugins/scheduler-k3s/templates/chart/ingress-route.yaml
Normal file
45
plugins/scheduler-k3s/templates/chart/ingress-route.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
{{- $processName := "PROCESS_NAME" }}
|
||||
{{- $config := .Values.processes.PROCESS_NAME }}
|
||||
{{- if $config.web.domains }}
|
||||
{{- range $pdx, $port_map := $config.web.port_maps }}
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
annotations:
|
||||
dokku.com/managed: "true"
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
app.kubernetes.io/name: {{ $processName }}
|
||||
app.kubernetes.io/part-of: {{ $.Values.global.app_name }}
|
||||
name: {{ $.Values.global.app_name }}-{{ $processName }}-{{ $port_map.name }}
|
||||
namespace: {{ $.Values.global.namespace }}
|
||||
spec:
|
||||
entryPoints:
|
||||
{{- if and $config.web.tls.enabled (eq $port_map.scheme "https") }}
|
||||
- websecure
|
||||
{{- else }}
|
||||
- web
|
||||
{{- end }}
|
||||
routes:
|
||||
{{- range $ddx, $domain := $config.web.domains }}
|
||||
- kind: Rule
|
||||
match: Host(`{{ $domain }}`)
|
||||
{{- if and $config.web.tls.enabled (and (eq $port_map.scheme "http") $port_map.redirect_to_https) }}
|
||||
middlewares:
|
||||
- name: {{ $.Values.global.app_name}}-redirect-to-https
|
||||
namespace: {{ $.Values.global.namespace }}
|
||||
{{- end }}
|
||||
services:
|
||||
- name: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
namespace: {{ $.Values.global.namespace }}
|
||||
passHostHeader: true
|
||||
port: {{ $port_map.name }}
|
||||
scheme: http
|
||||
{{- end }}
|
||||
{{- if and $config.web.tls.enabled (eq $port_map.scheme "https") }}
|
||||
tls:
|
||||
secretName: tls-{{ $.Values.global.app_name }}-{{ $processName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
16
plugins/scheduler-k3s/templates/chart/secret.yaml
Normal file
16
plugins/scheduler-k3s/templates/chart/secret.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
annotations:
|
||||
app.kubernetes.io/version: {{ $.Values.global.deploment_id | quote }}
|
||||
dokku.com/managed: "true"
|
||||
labels:
|
||||
app.kubernetes.io/instance: env-{{ $.Values.global.app_name }}.{{ $.Values.global.deploment_id }}
|
||||
app.kubernetes.io/name: env-{{ $.Values.global.app_name }}
|
||||
app.kubernetes.io/part-of: {{ $.Values.global.app_name }}
|
||||
name: env-{{ $.Values.global.app_name }}.{{ $.Values.global.deploment_id }}
|
||||
namespace: {{ $.Values.global.namespace }}
|
||||
{{- with .Values.global.secrets }}
|
||||
data:
|
||||
{{- toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
26
plugins/scheduler-k3s/templates/chart/service.yaml
Normal file
26
plugins/scheduler-k3s/templates/chart/service.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
{{- $processName := "PROCESS_NAME" }}
|
||||
{{- $config := .Values.processes.PROCESS_NAME }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations:
|
||||
dokku.com/managed: "true"
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
app.kubernetes.io/name: {{ $processName }}
|
||||
app.kubernetes.io/part-of: {{ $.Values.global.app_name }}
|
||||
name: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
namespace: default
|
||||
spec:
|
||||
ports:
|
||||
{{- range $pdx, $port_map := $config.web.port_maps }}
|
||||
- name: {{ $port_map.name }}
|
||||
port: {{ $port_map.host_port }}
|
||||
protocol: TCP
|
||||
targetPort: {{ $port_map.container_port }}
|
||||
{{- end }}
|
||||
selector:
|
||||
app.kubernetes.io/instance: {{ $.Values.global.app_name }}-{{ $processName }}
|
||||
app.kubernetes.io/name: {{ $processName }}
|
||||
app.kubernetes.io/part-of: {{ $.Values.global.app_name }}
|
||||
@@ -0,0 +1,28 @@
|
||||
installCRDs: true
|
||||
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Equal
|
||||
value: "true"
|
||||
effect: "NoSchedule"
|
||||
|
||||
webhook:
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Equal
|
||||
value: "true"
|
||||
effect: "NoSchedule"
|
||||
|
||||
cainjector:
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Equal
|
||||
value: "true"
|
||||
effect: "NoSchedule"
|
||||
|
||||
startupapicheck:
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Equal
|
||||
value: "true"
|
||||
effect: "NoSchedule"
|
||||
41
plugins/scheduler-k3s/templates/helm-config/longhorn.yaml
Normal file
41
plugins/scheduler-k3s/templates/helm-config/longhorn.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
defaultSettings:
|
||||
taintToleration: "CriticalAddonsOnly=true:NoSchedule"
|
||||
|
||||
longhornManager:
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Equal
|
||||
value: "true"
|
||||
effect: "NoSchedule"
|
||||
|
||||
longhornDriver:
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Equal
|
||||
value: "true"
|
||||
effect: "NoSchedule"
|
||||
|
||||
longhornUI:
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Equal
|
||||
value: "true"
|
||||
effect: "NoSchedule"
|
||||
|
||||
longhornConversionWebhook:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Equal
|
||||
value: "true"
|
||||
effect: "NoSchedule"
|
||||
|
||||
longhornAdmissionWebhook:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Equal
|
||||
value: "true"
|
||||
effect: "NoSchedule"
|
||||
|
||||
longhornRecoveryBackend:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Equal
|
||||
value: "true"
|
||||
effect: "NoSchedule"
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
apiVersion: helm.cattle.io/v1
|
||||
kind: HelmChart
|
||||
metadata:
|
||||
name: traefik-crd
|
||||
namespace: kube-system
|
||||
spec:
|
||||
bootstrap: true
|
||||
---
|
||||
apiVersion: helm.cattle.io/v1
|
||||
kind: HelmChart
|
||||
metadata:
|
||||
name: traefik
|
||||
namespace: kube-system
|
||||
spec:
|
||||
bootstrap: true
|
||||
1220
plugins/scheduler-k3s/triggers.go
Normal file
1220
plugins/scheduler-k3s/triggers.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user