From 87aa1d4b0ba1879529d978e39ff497cb38c30aaa52711210b9d3d67d72520a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9F=E9=85=8C=20=E9=B5=AC=E5=85=84?= Date: Fri, 27 Mar 2026 18:34:53 +0800 Subject: [PATCH] Added some ctl boilerplate --- .gitignore | 1 + README.md | 48 ++- build.env | 2 +- clitools/cmd/ctl/main.go | 15 + clitools/go.mod | 67 ++++ clitools/go.sum | 200 +++++++++++ clitools/makefile | 14 + clitools/pkg/apis/monok8s/v1alpha1/types.go | 142 ++++++++ clitools/pkg/bootstrap/registry.go | 64 ++++ clitools/pkg/bootstrap/runner.go | 58 ++++ clitools/pkg/cmd/agent/agent.go | 51 +++ clitools/pkg/cmd/apply/apply.go | 54 +++ clitools/pkg/cmd/checkconfig/checkconfig.go | 30 ++ clitools/pkg/cmd/create/create.go | 31 ++ clitools/pkg/cmd/initcmd/init.go | 34 ++ clitools/pkg/cmd/internal/internal.go | 30 ++ clitools/pkg/cmd/root/root.go | 41 +++ clitools/pkg/config/config.go | 134 ++++++++ clitools/pkg/crds/crds.go | 71 ++++ clitools/pkg/kube/clients.go | 50 +++ clitools/pkg/node/context.go | 15 + clitools/pkg/node/crio.go | 22 ++ clitools/pkg/node/kubeadm.go | 36 ++ clitools/pkg/node/kubelet.go | 12 + clitools/pkg/node/metadata.go | 37 +++ clitools/pkg/node/network.go | 91 +++++ clitools/pkg/node/prereqs.go | 27 ++ clitools/pkg/system/helpers.go | 55 ++++ clitools/pkg/system/runner.go | 346 ++++++++++++++++++++ clitools/pkg/templates/templates.go | 54 +++ 30 files changed, 1813 insertions(+), 19 deletions(-) create mode 100644 clitools/cmd/ctl/main.go create mode 100644 clitools/go.mod create mode 100644 clitools/go.sum create mode 100644 clitools/makefile create mode 100644 clitools/pkg/apis/monok8s/v1alpha1/types.go create mode 100644 clitools/pkg/bootstrap/registry.go create mode 100644 clitools/pkg/bootstrap/runner.go create mode 100644 clitools/pkg/cmd/agent/agent.go create mode 100644 clitools/pkg/cmd/apply/apply.go create mode 100644 clitools/pkg/cmd/checkconfig/checkconfig.go create mode 100644 clitools/pkg/cmd/create/create.go create mode 100644 clitools/pkg/cmd/initcmd/init.go create mode 100644 clitools/pkg/cmd/internal/internal.go create mode 100644 clitools/pkg/cmd/root/root.go create mode 100644 clitools/pkg/config/config.go create mode 100644 clitools/pkg/crds/crds.go create mode 100644 clitools/pkg/kube/clients.go create mode 100644 clitools/pkg/node/context.go create mode 100644 clitools/pkg/node/crio.go create mode 100644 clitools/pkg/node/kubeadm.go create mode 100644 clitools/pkg/node/kubelet.go create mode 100644 clitools/pkg/node/metadata.go create mode 100644 clitools/pkg/node/network.go create mode 100644 clitools/pkg/node/prereqs.go create mode 100644 clitools/pkg/system/helpers.go create mode 100644 clitools/pkg/system/runner.go create mode 100644 clitools/pkg/templates/templates.go diff --git a/.gitignore b/.gitignore index a86299f..dca5a56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +clitools/bin packages/ out/ *.swp diff --git a/README.md b/README.md index 66c24b5..86b3d31 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,17 @@ # monok8s -Kubernetes node firmware (built with Linux) for Mono Gateway Development Kit +Alpine-based Kubernetes cluster image for Mono's Gateway Development Kit https://docs.mono.si/gateway-development-kit/getting-started ## DISCLAIMER USE AT YOUR OWN RISKS. I leverage ChatGPT heavily for this. I am testing them all by myself right now. +## IMPORTANT NOTES + * The 3 RJ45 ports are label in eth1, eth2, eth0 respectively by the kernel (left to right) + * So `ip addr eth0` is your right most port + * If the fan stopped spinning. Unplug ASAP! Otherwise CPU temp goes to the moon. + ## Build Find the latest package versions and update build.env @@ -80,27 +85,34 @@ make itb # Builds out/board.itb (contains the kernel and the initramfs) - Read-only OS ## Upgrade process -Rough idea -```bash -./configure -# - asks for some config for kubelet -# - Join a cluster? Start a cluster? - -make release - -# Copy the new image to the upgrade-scheduler -kubectl cp -n kube-system upgrade-scheduler:/tmp/upgrade.img - -# Upgrade scheduler reads the file that issue a self-reboot -reboot - -# uboot to boot into partition B +We use a CRD with an agent to handle this. Our versions follows upstream's. +kubectl apply -f upgrade.yaml +```yaml +apiVersion: k8s.mono.si/v1alpha1 +kind: OSUpgrade +metadata: + name: "my-ugrade" +spec: + version: "v1.35.1" # Or just "latest" + imageURL: "https://updates.example.com/monok8s-1.2.3.img.zst" + checksum: "sha256:..." ``` -```yaml -PENDING +kubectl get osugrades +``` +NAME TARGET STATUS AGE +my-upgrade-1 latest pending 1m +my-upgrade-2 v1.35.3 accepted 1m # latest gets realized into a version number +their-upgrade v1.33.2 succeeded 1m +``` + +kubectl get osupgradeprogress +``` +NODE SOURCE STATUS +my-node my-upgrade-2 active +their-node my-upgrade-2 completed ``` ## NOTES diff --git a/build.env b/build.env index ca04aed..979202f 100644 --- a/build.env +++ b/build.env @@ -6,7 +6,7 @@ TAG=dev # The Linux kernel, from NXP NXP_VERSION=lf-6.18.2-1.0.0 CRIO_VERSION=cri-o.arm64.v1.35.1 -KUBE_VERSION=v1.35.3 +KUBE_VERSION=v1.35.1 # Mono's tutorial said fsl-ls1046a-rdb.dtb but our shipped board is not that one # We need fsl-ls1046a-rdb-sdk.dtb here diff --git a/clitools/cmd/ctl/main.go b/clitools/cmd/ctl/main.go new file mode 100644 index 0000000..ed05cba --- /dev/null +++ b/clitools/cmd/ctl/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + "os" + + "undecided.project/monok8s/pkg/cmd/root" +) + +func main() { + if err := root.NewRootCmd().Execute(); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } +} diff --git a/clitools/go.mod b/clitools/go.mod new file mode 100644 index 0000000..132991e --- /dev/null +++ b/clitools/go.mod @@ -0,0 +1,67 @@ +module undecided.project/monok8s + +go 1.24.0 + +require ( + github.com/spf13/cobra v1.9.1 + gopkg.in/yaml.v3 v3.0.1 + k8s.io/apiextensions-apiserver v0.34.0 + k8s.io/apimachinery v0.34.0 + k8s.io/cli-runtime v0.34.0 + k8s.io/client-go v0.34.0 + k8s.io/klog/v2 v2.130.1 +) + +require ( + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/mailru/easyjson v0.7.7 // 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.3-0.20250322232337-35a7c28c31ee // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/xlab/treeprint v1.2.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.9.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + k8s.io/api v0.34.0 // indirect + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/kustomize/api v0.20.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/clitools/go.sum b/clitools/go.sum new file mode 100644 index 0000000..801d9e3 --- /dev/null +++ b/clitools/go.sum @@ -0,0 +1,200 @@ +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +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-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +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/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +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.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +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/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/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/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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/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/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/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/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.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +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= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +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.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +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.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +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/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +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.v3 v3.0.0-20200313102051-9f266ea9e77c/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= +k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE= +k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug= +k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= +k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= +k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0= +k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/cli-runtime v0.34.0 h1:N2/rUlJg6TMEBgtQ3SDRJwa8XyKUizwjlOknT1mB2Cw= +k8s.io/cli-runtime v0.34.0/go.mod h1:t/skRecS73Piv+J+FmWIQA2N2/rDjdYSQzEE67LUUs8= +k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo= +k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= +sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= +sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= +sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/clitools/makefile b/clitools/makefile new file mode 100644 index 0000000..fac06f2 --- /dev/null +++ b/clitools/makefile @@ -0,0 +1,14 @@ +VERSION ?= dev + +BIN_DIR := bin + +build: + mkdir -p $(BIN_DIR) + go build -o $(BIN_DIR)/ctl-$(VERSION) ./cmd/ctl + +run: + go run ./cmd/ctl + +clean: + rm -rf $(BIN_DIR) + diff --git a/clitools/pkg/apis/monok8s/v1alpha1/types.go b/clitools/pkg/apis/monok8s/v1alpha1/types.go new file mode 100644 index 0000000..51ddb30 --- /dev/null +++ b/clitools/pkg/apis/monok8s/v1alpha1/types.go @@ -0,0 +1,142 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const ( + Group = "monok8s.io" + Version = "v1alpha1" +) + +var ( + SchemeGroupVersion = schema.GroupVersion{Group: Group, Version: Version} + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +type MonoKSConfig struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"` + Spec MonoKSConfigSpec `json:"spec,omitempty" yaml:"spec,omitempty"` + Status MonoKSConfigStatus `json:"status,omitempty" yaml:"status,omitempty"` +} + +type MonoKSConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []MonoKSConfig `json:"items"` +} + +type MonoKSConfigSpec struct { + KubernetesVersion string `json:"kubernetesVersion,omitempty" yaml:"kubernetesVersion,omitempty"` + NodeName string `json:"nodeName,omitempty" yaml:"nodeName,omitempty"` + ClusterName string `json:"clusterName,omitempty" yaml:"clusterName,omitempty"` + ClusterDomain string `json:"clusterDomain,omitempty" yaml:"clusterDomain,omitempty"` + PodSubnet string `json:"podSubnet,omitempty" yaml:"podSubnet,omitempty"` + ServiceSubnet string `json:"serviceSubnet,omitempty" yaml:"serviceSubnet,omitempty"` + APIServerAdvertiseAddress string `json:"apiServerAdvertiseAddress,omitempty" yaml:"apiServerAdvertiseAddress,omitempty"` + APIServerEndpoint string `json:"apiServerEndpoint,omitempty" yaml:"apiServerEndpoint,omitempty"` + ContainerRuntimeEndpoint string `json:"containerRuntimeEndpoint,omitempty" yaml:"containerRuntimeEndpoint,omitempty"` + BootstrapMode string `json:"bootstrapMode,omitempty" yaml:"bootstrapMode,omitempty"` + JoinKind string `json:"joinKind,omitempty" yaml:"joinKind,omitempty"` + BootstrapToken string `json:"bootstrapToken,omitempty" yaml:"bootstrapToken,omitempty"` + DiscoveryTokenCACertHash string `json:"discoveryTokenCACertHash,omitempty" yaml:"discoveryTokenCACertHash,omitempty"` + ControlPlaneCertKey string `json:"controlPlaneCertKey,omitempty" yaml:"controlPlaneCertKey,omitempty"` + CNIPlugin string `json:"cniPlugin,omitempty" yaml:"cniPlugin,omitempty"` + AllowSchedulingOnControlPlane bool `json:"allowSchedulingOnControlPlane,omitempty" yaml:"allowSchedulingOnControlPlane,omitempty"` + SkipImageCheck bool `json:"skipImageCheck,omitempty" yaml:"skipImageCheck,omitempty"` + KubeProxyNodePortAddresses []string `json:"kubeProxyNodePortAddresses,omitempty" yaml:"kubeProxyNodePortAddresses,omitempty"` + SubjectAltNames []string `json:"subjectAltNames,omitempty" yaml:"subjectAltNames,omitempty"` + NodeLabels map[string]string `json:"nodeLabels,omitempty" yaml:"nodeLabels,omitempty"` + NodeAnnotations map[string]string `json:"nodeAnnotations,omitempty" yaml:"nodeAnnotations,omitempty"` + Network NetworkSpec `json:"network,omitempty" yaml:"network,omitempty"` +} + +type NetworkSpec struct { + Hostname string `json:"hostname,omitempty" yaml:"hostname,omitempty"` + ManagementIface string `json:"managementIface,omitempty" yaml:"managementIface,omitempty"` + ManagementCIDR string `json:"managementCIDR,omitempty" yaml:"managementCIDR,omitempty"` + ManagementGW string `json:"managementGateway,omitempty" yaml:"managementGateway,omitempty"` + DNSNameservers []string `json:"dnsNameservers,omitempty" yaml:"dnsNameservers,omitempty"` + DNSSearchDomains []string `json:"dnsSearchDomains,omitempty" yaml:"dnsSearchDomains,omitempty"` +} + +type MonoKSConfigStatus struct { + Phase string `json:"phase,omitempty"` + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` + AppliedSteps []string `json:"appliedSteps,omitempty"` +} + +type OSUpgrade struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"` + Spec OSUpgradeSpec `json:"spec,omitempty" yaml:"spec,omitempty"` + Status OSUpgradeStatus `json:"status,omitempty" yaml:"status,omitempty"` +} + +type OSUpgradeList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []OSUpgrade `json:"items"` +} + +type OSUpgradeSpec struct { + Version string `json:"version,omitempty" yaml:"version,omitempty"` + ImageURL string `json:"imageURL,omitempty" yaml:"imageURL,omitempty"` + TargetPartition string `json:"targetPartition,omitempty" yaml:"targetPartition,omitempty"` + NodeSelector []string `json:"nodeSelector,omitempty" yaml:"nodeSelector,omitempty"` + Force bool `json:"force,omitempty" yaml:"force,omitempty"` +} + +type OSUpgradeStatus struct { + Phase string `json:"phase,omitempty"` + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &MonoKSConfig{}, + &MonoKSConfigList{}, + &OSUpgrade{}, + &OSUpgradeList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} + +func (in *MonoKSConfig) DeepCopyObject() runtime.Object { + if in == nil { + return nil + } + out := *in + return &out +} + +func (in *MonoKSConfigList) DeepCopyObject() runtime.Object { + if in == nil { + return nil + } + out := *in + return &out +} + +func (in *OSUpgrade) DeepCopyObject() runtime.Object { + if in == nil { + return nil + } + out := *in + return &out +} + +func (in *OSUpgradeList) DeepCopyObject() runtime.Object { + if in == nil { + return nil + } + out := *in + return &out +} diff --git a/clitools/pkg/bootstrap/registry.go b/clitools/pkg/bootstrap/registry.go new file mode 100644 index 0000000..372a73f --- /dev/null +++ b/clitools/pkg/bootstrap/registry.go @@ -0,0 +1,64 @@ +package bootstrap + +import ( + "fmt" + + "undecided.project/monok8s/pkg/node" +) + +type Registry struct { + steps map[string]node.Step +} + +func NewRegistry(ctx *node.NodeContext) *Registry { + netCfg := node.NetworkConfig{ + MgmtIface: ctx.Config.Spec.Network.ManagementIface, + MgmtAddress: ctx.Config.Spec.Network.ManagementCIDR, + MgmtGateway: ctx.Config.Spec.Network.ManagementGW, + } + + return &Registry{ + steps: map[string]node.Step{ + "check_prereqs": node.CheckPrereqs, + "validate_network_requirements": node.ValidateNetworkRequirements, + "install_cni_if_requested": node.InstallCNIIfRequested, + "start_crio": node.StartCRIO, + "check_crio_running": node.CheckCRIORunning, + "wait_for_existing_cluster_if_needed": node.WaitForExistingClusterIfNeeded, + "decide_bootstrap_action": node.DecideBootstrapAction, + "check_required_images": node.CheckRequiredImages, + "generate_kubeadm_config": node.GenerateKubeadmConfig, + "run_kubeadm_init": node.RunKubeadmInit, + "restart_kubelet": node.RestartKubelet, + "apply_local_node_metadata_if_possible": node.ApplyLocalNodeMetadataIfPossible, + "allow_single_node_scheduling": node.AllowSingleNodeScheduling, + "ensure_ip_forward": node.EnsureIPForward, + "configure_mgmt_interface": node.ConfigureMgmtInterface(netCfg), + "configure_dns": node.ConfigureDNS, + "set_hostname_if_needed": node.SetHostnameIfNeeded, + "print_summary": node.PrintSummary, + "reconcile_control_plane": node.ReconcileControlPlane, + "check_upgrade_prereqs": node.CheckUpgradePrereqs, + "run_kubeadm_upgrade_apply": node.RunKubeadmUpgradeApply, + "run_kubeadm_join": node.RunKubeadmJoin, + "reconcile_node": node.ReconcileNode, + "run_kubeadm_upgrade_node": node.RunKubeadmUpgradeNode, + }, + } +} + +func (r *Registry) MustGet(name string) node.Step { + step, ok := r.steps[name] + if !ok { + panic(fmt.Sprintf("unknown step %q", name)) + } + return step +} + +func (r *Registry) Get(name string) (node.Step, error) { + step, ok := r.steps[name] + if !ok { + return nil, fmt.Errorf("unknown step %q", name) + } + return step, nil +} diff --git a/clitools/pkg/bootstrap/runner.go b/clitools/pkg/bootstrap/runner.go new file mode 100644 index 0000000..f2a3c59 --- /dev/null +++ b/clitools/pkg/bootstrap/runner.go @@ -0,0 +1,58 @@ +package bootstrap + +import ( + "context" + + monov1alpha1 "undecided.project/monok8s/pkg/apis/monok8s/v1alpha1" + "undecided.project/monok8s/pkg/node" + "undecided.project/monok8s/pkg/system" +) + +type Runner struct { + NodeCtx *node.NodeContext + Registry *Registry +} + +func NewRunner(cfg *monov1alpha1.MonoKSConfig) *Runner { + runnerCfg := system.RunnerConfig{} + nctx := &node.NodeContext{ + Config: cfg, + System: system.NewRunner(runnerCfg), + } + return &Runner{ + NodeCtx: nctx, + Registry: NewRegistry(nctx), + } +} + +func (r *Runner) Init(ctx context.Context) error { + for _, name := range []string{ + "check_prereqs", + "validate_network_requirements", + "install_cni_if_requested", + "start_crio", + "check_crio_running", + "wait_for_existing_cluster_if_needed", + "decide_bootstrap_action", + "check_required_images", + "generate_kubeadm_config", + "run_kubeadm_init", + "restart_kubelet", + "apply_local_node_metadata_if_possible", + "allow_single_node_scheduling", + "print_summary", + } { + if err := r.RunNamedStep(ctx, name); err != nil { + return err + } + } + return nil +} + +func (r *Runner) RunNamedStep(ctx context.Context, name string) error { + step, err := r.Registry.Get(name) + if err != nil { + return err + } + return step(ctx, r.NodeCtx) +} diff --git a/clitools/pkg/cmd/agent/agent.go b/clitools/pkg/cmd/agent/agent.go new file mode 100644 index 0000000..832dc9c --- /dev/null +++ b/clitools/pkg/cmd/agent/agent.go @@ -0,0 +1,51 @@ +package agent + +import ( + "context" + "fmt" + "time" + + monov1alpha1 "undecided.project/monok8s/pkg/apis/monok8s/v1alpha1" + "undecided.project/monok8s/pkg/kube" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/klog/v2" +) + +func NewCmdAgent(flags *genericclioptions.ConfigFlags) *cobra.Command { + var namespace string + cmd := &cobra.Command{ + Use: "agent", + Short: "Watch OSUpgrade resources and do nothing for now", + RunE: func(cmd *cobra.Command, _ []string) error { + clients, err := kube.NewClients(flags) + if err != nil { + return err + } + gvr := schema.GroupVersionResource{Group: monov1alpha1.Group, Version: monov1alpha1.Version, Resource: "osupgrades"} + ctx := cmd.Context() + for { + list, err := clients.Dynamic.Resource(gvr).Namespace(namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return err + } + klog.InfoS("agent tick", "namespace", namespace, "items", len(list.Items)) + for _, item := range list.Items { + klog.InfoS("observed osupgrade", "name", item.GetName(), "resourceVersion", item.GetResourceVersion()) + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(15 * time.Second): + } + } + }, + } + cmd.Flags().StringVar(&namespace, "namespace", "kube-system", "namespace to watch") + return cmd +} + +var _ = context.Background +var _ = fmt.Sprintf diff --git a/clitools/pkg/cmd/apply/apply.go b/clitools/pkg/cmd/apply/apply.go new file mode 100644 index 0000000..e2330bb --- /dev/null +++ b/clitools/pkg/cmd/apply/apply.go @@ -0,0 +1,54 @@ +package apply + +import ( + "context" + "fmt" + + "undecided.project/monok8s/pkg/crds" + "undecided.project/monok8s/pkg/kube" + "github.com/spf13/cobra" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/klog/v2" +) + +func NewCmdApply(flags *genericclioptions.ConfigFlags) *cobra.Command { + cmd := &cobra.Command{Use: "apply", Short: "Apply MonoK8s resources"} + cmd.AddCommand(newCmdApplyCRDs(flags)) + return cmd +} + +func newCmdApplyCRDs(flags *genericclioptions.ConfigFlags) *cobra.Command { + return &cobra.Command{ + Use: "crds", + Short: "Register the MonoKSConfig and OSUpgrade CRDs", + RunE: func(cmd *cobra.Command, _ []string) error { + clients, err := kube.NewClients(flags) + if err != nil { + return err + } + ctx := context.Background() + for _, wanted := range crds.Definitions() { + _, err := clients.APIExtensions.ApiextensionsV1().CustomResourceDefinitions().Create(ctx, wanted, metav1.CreateOptions{}) + if apierrors.IsAlreadyExists(err) { + current, getErr := clients.APIExtensions.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, wanted.Name, metav1.GetOptions{}) + if getErr != nil { + return getErr + } + wanted.ResourceVersion = current.ResourceVersion + _, err = clients.APIExtensions.ApiextensionsV1().CustomResourceDefinitions().Update(ctx, wanted, metav1.UpdateOptions{}) + } + if err != nil { + return err + } + klog.InfoS("crd applied", "name", wanted.Name) + } + _, _ = fmt.Fprintln(cmd.OutOrStdout(), "CRDs applied") + return nil + }, + } +} + +var _ *apiextensionsv1.CustomResourceDefinition diff --git a/clitools/pkg/cmd/checkconfig/checkconfig.go b/clitools/pkg/cmd/checkconfig/checkconfig.go new file mode 100644 index 0000000..634fbaa --- /dev/null +++ b/clitools/pkg/cmd/checkconfig/checkconfig.go @@ -0,0 +1,30 @@ +package checkconfig + +import ( + "fmt" + + "undecided.project/monok8s/pkg/config" + "github.com/spf13/cobra" +) + +func NewCmdCheckConfig() *cobra.Command { + var configPath string + cmd := &cobra.Command{ + Use: "checkconfig", + Short: "Validate a MonoKSConfig", + RunE: func(cmd *cobra.Command, _ []string) error { + path, err := (config.Loader{}).ResolvePath(configPath) + if err != nil { + return err + } + cfg, err := (config.Loader{}).Load(path) + if err != nil { + return err + } + fmt.Fprintf(cmd.OutOrStdout(), "OK: %s (%s / %s)\n", path, cfg.Spec.NodeName, cfg.Spec.KubernetesVersion) + return nil + }, + } + cmd.Flags().StringVarP(&configPath, "config", "c", "", "path to MonoKSConfig yaml") + return cmd +} diff --git a/clitools/pkg/cmd/create/create.go b/clitools/pkg/cmd/create/create.go new file mode 100644 index 0000000..bc78ef9 --- /dev/null +++ b/clitools/pkg/cmd/create/create.go @@ -0,0 +1,31 @@ +package create + +import ( + "fmt" + + "undecided.project/monok8s/pkg/templates" + "github.com/spf13/cobra" +) + +func NewCmdCreate() *cobra.Command { + cmd := &cobra.Command{Use: "create", Short: "Create starter resources"} + cmd.AddCommand( + &cobra.Command{ + Use: "config", + Short: "Print a MonoKSConfig template", + RunE: func(cmd *cobra.Command, _ []string) error { + _, err := fmt.Fprint(cmd.OutOrStdout(), templates.MonoKSConfigYAML) + return err + }, + }, + &cobra.Command{ + Use: "osupgrade", + Short: "Print an OSUpgrade template", + RunE: func(cmd *cobra.Command, _ []string) error { + _, err := fmt.Fprint(cmd.OutOrStdout(), templates.OSUpgradeYAML) + return err + }, + }, + ) + return cmd +} diff --git a/clitools/pkg/cmd/initcmd/init.go b/clitools/pkg/cmd/initcmd/init.go new file mode 100644 index 0000000..4950ea8 --- /dev/null +++ b/clitools/pkg/cmd/initcmd/init.go @@ -0,0 +1,34 @@ +package initcmd + +import ( + "context" + + "undecided.project/monok8s/pkg/bootstrap" + "undecided.project/monok8s/pkg/config" + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/klog/v2" +) + +func NewCmdInit(_ *genericclioptions.ConfigFlags) *cobra.Command { + var configPath string + cmd := &cobra.Command{ + Use: "init", + Short: "Equivalent of apply-node-config + bootstrap-cluster", + RunE: func(cmd *cobra.Command, _ []string) error { + path, err := (config.Loader{}).ResolvePath(configPath) + if err != nil { + return err + } + cfg, err := (config.Loader{}).Load(path) + if err != nil { + return err + } + klog.InfoS("starting init", "config", path, "node", cfg.Spec.NodeName) + return bootstrap.NewRunner(cfg).Init(cmd.Context()) + }, + } + cmd.Flags().StringVarP(&configPath, "config", "c", "", "path to MonoKSConfig yaml") + _ = context.Background() + return cmd +} diff --git a/clitools/pkg/cmd/internal/internal.go b/clitools/pkg/cmd/internal/internal.go new file mode 100644 index 0000000..24f3684 --- /dev/null +++ b/clitools/pkg/cmd/internal/internal.go @@ -0,0 +1,30 @@ +package internal + +import ( + "undecided.project/monok8s/pkg/bootstrap" + "undecided.project/monok8s/pkg/config" + "github.com/spf13/cobra" +) + +func NewCmdInternal() *cobra.Command { + var configPath string + cmd := &cobra.Command{Use: "internal", Hidden: true} + cmd.AddCommand(&cobra.Command{ + Use: "run-step STEP", + Short: "Run one internal step for testing", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + path, err := (config.Loader{}).ResolvePath(configPath) + if err != nil { + return err + } + cfg, err := (config.Loader{}).Load(path) + if err != nil { + return err + } + return bootstrap.NewRunner(cfg).RunNamedStep(cmd.Context(), args[0]) + }, + }) + cmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "path to MonoKSConfig yaml") + return cmd +} diff --git a/clitools/pkg/cmd/root/root.go b/clitools/pkg/cmd/root/root.go new file mode 100644 index 0000000..c1e1541 --- /dev/null +++ b/clitools/pkg/cmd/root/root.go @@ -0,0 +1,41 @@ +package root + +import ( + "flag" + + agentcmd "undecided.project/monok8s/pkg/cmd/agent" + applycmd "undecided.project/monok8s/pkg/cmd/apply" + checkconfigcmd "undecided.project/monok8s/pkg/cmd/checkconfig" + createcmd "undecided.project/monok8s/pkg/cmd/create" + initcmd "undecided.project/monok8s/pkg/cmd/initcmd" + internalcmd "undecided.project/monok8s/pkg/cmd/internal" + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/klog/v2" +) + +func NewRootCmd() *cobra.Command { + flags := genericclioptions.NewConfigFlags(true) + + cmd := &cobra.Command{ + Use: "ctl", + Short: "MonoK8s control tool", + SilenceUsage: true, + SilenceErrors: true, + PersistentPreRun: func(*cobra.Command, []string) { + klog.InitFlags(nil) + _ = flag.Set("logtostderr", "true") + }, + } + + flags.AddFlags(cmd.PersistentFlags()) + cmd.AddCommand( + initcmd.NewCmdInit(flags), + checkconfigcmd.NewCmdCheckConfig(), + createcmd.NewCmdCreate(), + applycmd.NewCmdApply(flags), + agentcmd.NewCmdAgent(flags), + internalcmd.NewCmdInternal(), + ) + return cmd +} diff --git a/clitools/pkg/config/config.go b/clitools/pkg/config/config.go new file mode 100644 index 0000000..f944e60 --- /dev/null +++ b/clitools/pkg/config/config.go @@ -0,0 +1,134 @@ +package config + +import ( + "errors" + "fmt" + "os" + "strings" + + monov1alpha1 "undecided.project/monok8s/pkg/apis/monok8s/v1alpha1" + "gopkg.in/yaml.v3" +) + +const EnvVar = "MONOKSCONFIG" + +type Loader struct{} + +func (Loader) ResolvePath(flagValue string) (string, error) { + if strings.TrimSpace(flagValue) != "" { + return flagValue, nil + } + if env := strings.TrimSpace(os.Getenv(EnvVar)); env != "" { + return env, nil + } + return "", fmt.Errorf("config path not provided; pass -c or set %s", EnvVar) +} + +func (Loader) Load(path string) (*monov1alpha1.MonoKSConfig, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var cfg monov1alpha1.MonoKSConfig + if err := yaml.Unmarshal(data, &cfg); err != nil { + return nil, err + } + if cfg.Kind == "" { + cfg.Kind = "MonoKSConfig" + } + if cfg.APIVersion == "" { + cfg.APIVersion = monov1alpha1.Group + "/" + monov1alpha1.Version + } + ApplyDefaults(&cfg) + if err := Validate(&cfg); err != nil { + return nil, err + } + return &cfg, nil +} + +func ApplyDefaults(cfg *monov1alpha1.MonoKSConfig) { + if cfg.Spec.PodSubnet == "" { + cfg.Spec.PodSubnet = "10.244.0.0/16" + } + if cfg.Spec.ServiceSubnet == "" { + cfg.Spec.ServiceSubnet = "10.96.0.0/12" + } + if cfg.Spec.ClusterName == "" { + cfg.Spec.ClusterName = "monok8s" + } + if cfg.Spec.ClusterDomain == "" { + cfg.Spec.ClusterDomain = "cluster.local" + } + if cfg.Spec.ContainerRuntimeEndpoint == "" { + cfg.Spec.ContainerRuntimeEndpoint = "unix:///var/run/crio/crio.sock" + } + if cfg.Spec.BootstrapMode == "" { + cfg.Spec.BootstrapMode = "init" + } + if cfg.Spec.JoinKind == "" { + cfg.Spec.JoinKind = "worker" + } + if cfg.Spec.CNIPlugin == "" { + cfg.Spec.CNIPlugin = "none" + } + if len(cfg.Spec.KubeProxyNodePortAddresses) == 0 { + cfg.Spec.KubeProxyNodePortAddresses = []string{"primary"} + } +} + +func Validate(cfg *monov1alpha1.MonoKSConfig) error { + var problems []string + if cfg.Kind != "MonoKSConfig" { + problems = append(problems, "kind must be MonoKSConfig") + } + if cfg.APIVersion != monov1alpha1.Group+"/"+monov1alpha1.Version { + problems = append(problems, "apiVersion must be "+monov1alpha1.Group+"/"+monov1alpha1.Version) + } + if strings.TrimSpace(cfg.Spec.KubernetesVersion) == "" { + problems = append(problems, "spec.kubernetesVersion is required") + } + if strings.TrimSpace(cfg.Spec.NodeName) == "" { + problems = append(problems, "spec.nodeName is required") + } + if strings.TrimSpace(cfg.Spec.APIServerAdvertiseAddress) == "" { + problems = append(problems, "spec.apiServerAdvertiseAddress is required") + } + if strings.TrimSpace(cfg.Spec.Network.Hostname) == "" { + problems = append(problems, "spec.network.hostname is required") + } + if strings.TrimSpace(cfg.Spec.Network.ManagementIface) == "" { + problems = append(problems, "spec.network.managementIface is required") + } + if !strings.Contains(cfg.Spec.Network.ManagementCIDR, "/") { + problems = append(problems, "spec.network.managementCIDR must include a CIDR prefix") + } + if cfg.Spec.BootstrapMode != "init" && cfg.Spec.BootstrapMode != "join" { + problems = append(problems, "spec.bootstrapMode must be init or join") + } + if cfg.Spec.JoinKind != "worker" && cfg.Spec.JoinKind != "control-plane" { + problems = append(problems, "spec.joinKind must be worker or control-plane") + } + for _, ns := range cfg.Spec.Network.DNSNameservers { + if ns == "10.96.0.10" { + problems = append(problems, "spec.network.dnsNameservers must not include cluster DNS service IP 10.96.0.10") + } + } + if cfg.Spec.BootstrapMode == "join" { + if cfg.Spec.APIServerEndpoint == "" { + problems = append(problems, "spec.apiServerEndpoint is required for join mode") + } + if cfg.Spec.BootstrapToken == "" { + problems = append(problems, "spec.bootstrapToken is required for join mode") + } + if cfg.Spec.DiscoveryTokenCACertHash == "" { + problems = append(problems, "spec.discoveryTokenCACertHash is required for join mode") + } + if cfg.Spec.JoinKind == "control-plane" && cfg.Spec.ControlPlaneCertKey == "" { + problems = append(problems, "spec.controlPlaneCertKey is required for control-plane join") + } + } + if len(problems) > 0 { + return errors.New(strings.Join(problems, "; ")) + } + return nil +} diff --git a/clitools/pkg/crds/crds.go b/clitools/pkg/crds/crds.go new file mode 100644 index 0000000..9942a3a --- /dev/null +++ b/clitools/pkg/crds/crds.go @@ -0,0 +1,71 @@ +package crds + +import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Definitions() []*apiextensionsv1.CustomResourceDefinition { + return []*apiextensionsv1.CustomResourceDefinition{ + monoKSConfigCRD(), + osUpgradeCRD(), + } +} + +func monoKSConfigCRD() *apiextensionsv1.CustomResourceDefinition { + return &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "monoksconfigs.monok8s.io"}, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "monok8s.io", + Scope: apiextensionsv1.NamespaceScoped, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: "monoksconfigs", + Singular: "monoksconfig", + Kind: "MonoKSConfig", + ShortNames: []string{"mkscfg"}, + }, + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{ + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Properties: map[string]apiextensionsv1.JSONSchemaProps{ + "spec": {Type: "object", XPreserveUnknownFields: boolPtr(true)}, + "status": {Type: "object", XPreserveUnknownFields: boolPtr(true)}, + }, + }}, + }}, + }, + } +} + +func osUpgradeCRD() *apiextensionsv1.CustomResourceDefinition { + return &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "osupgrades.monok8s.io"}, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "monok8s.io", + Scope: apiextensionsv1.NamespaceScoped, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: "osupgrades", + Singular: "osupgrade", + Kind: "OSUpgrade", + ShortNames: []string{"osup"}, + }, + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{ + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Properties: map[string]apiextensionsv1.JSONSchemaProps{ + "spec": {Type: "object", XPreserveUnknownFields: boolPtr(true)}, + "status": {Type: "object", XPreserveUnknownFields: boolPtr(true)}, + }, + }}, + }}, + }, + } +} + +func boolPtr(v bool) *bool { return &v } diff --git a/clitools/pkg/kube/clients.go b/clitools/pkg/kube/clients.go new file mode 100644 index 0000000..e75dd04 --- /dev/null +++ b/clitools/pkg/kube/clients.go @@ -0,0 +1,50 @@ +package kube + +import ( + "fmt" + + monov1alpha1 "undecided.project/monok8s/pkg/apis/monok8s/v1alpha1" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/dynamic" + kubernetes "k8s.io/client-go/kubernetes" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" +) + +type Clients struct { + Config *rest.Config + Kubernetes kubernetes.Interface + Dynamic dynamic.Interface + APIExtensions apiextensionsclientset.Interface + RESTClientGetter genericclioptions.RESTClientGetter +} + +func NewClients(flags *genericclioptions.ConfigFlags) (*Clients, error) { + cfg, err := flags.ToRESTConfig() + if err != nil { + return nil, fmt.Errorf("build rest config: %w", err) + } + kubeClient, err := kubernetes.NewForConfig(cfg) + if err != nil { + return nil, fmt.Errorf("build kubernetes client: %w", err) + } + dyn, err := dynamic.NewForConfig(cfg) + if err != nil { + return nil, fmt.Errorf("build dynamic client: %w", err) + } + ext, err := apiextensionsclientset.NewForConfig(cfg) + if err != nil { + return nil, fmt.Errorf("build apiextensions client: %w", err) + } + return &Clients{Config: cfg, Kubernetes: kubeClient, Dynamic: dyn, APIExtensions: ext, RESTClientGetter: flags}, nil +} + +func Scheme() *runtime.Scheme { + scheme := runtime.NewScheme() + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(monov1alpha1.AddToScheme(scheme)) + return scheme +} diff --git a/clitools/pkg/node/context.go b/clitools/pkg/node/context.go new file mode 100644 index 0000000..d308b4f --- /dev/null +++ b/clitools/pkg/node/context.go @@ -0,0 +1,15 @@ +package node + +import ( + "context" + + monov1alpha1 "undecided.project/monok8s/pkg/apis/monok8s/v1alpha1" + "undecided.project/monok8s/pkg/system" +) + +type NodeContext struct { + Config *monov1alpha1.MonoKSConfig + System *system.Runner +} + +type Step func(context.Context, *NodeContext) error diff --git a/clitools/pkg/node/crio.go b/clitools/pkg/node/crio.go new file mode 100644 index 0000000..a99f261 --- /dev/null +++ b/clitools/pkg/node/crio.go @@ -0,0 +1,22 @@ +package node + +import ( + "context" + + "k8s.io/klog/v2" +) + +func InstallCNIIfRequested(context.Context, *NodeContext) error { + klog.Info("install_cni_if_requested: TODO implement bridge/none CNI toggling") + return nil +} + +func StartCRIO(context.Context, *NodeContext) error { + klog.Info("start_crio: TODO implement rc-service crio start") + return nil +} + +func CheckCRIORunning(context.Context, *NodeContext) error { + klog.Info("check_crio_running: TODO implement crictl readiness checks") + return nil +} diff --git a/clitools/pkg/node/kubeadm.go b/clitools/pkg/node/kubeadm.go new file mode 100644 index 0000000..1a3b757 --- /dev/null +++ b/clitools/pkg/node/kubeadm.go @@ -0,0 +1,36 @@ +package node + +import ( + "context" + + "k8s.io/klog/v2" +) + +func WaitForExistingClusterIfNeeded(context.Context, *NodeContext) error { + klog.Info("wait_for_existing_cluster_if_needed: TODO implement kubelet/admin.conf waits") + return nil +} +func CheckRequiredImages(context.Context, *NodeContext) error { + klog.Info("check_required_images: TODO implement kubeadm image list + crictl image presence") + return nil +} +func GenerateKubeadmConfig(context.Context, *NodeContext) error { + klog.Info("generate_kubeadm_config: TODO render kubeadm v1beta4 config from MonoKSConfig") + return nil +} +func RunKubeadmInit(context.Context, *NodeContext) error { + klog.Info("run_kubeadm_init: TODO implement kubeadm init --config ") + return nil +} +func RunKubeadmUpgradeApply(context.Context, *NodeContext) error { + klog.Info("run_kubeadm_upgrade_apply: TODO implement kubeadm upgrade apply") + return nil +} +func RunKubeadmJoin(context.Context, *NodeContext) error { + klog.Info("run_kubeadm_join: TODO implement kubeadm join") + return nil +} +func RunKubeadmUpgradeNode(context.Context, *NodeContext) error { + klog.Info("run_kubeadm_upgrade_node: TODO implement kubeadm upgrade node") + return nil +} diff --git a/clitools/pkg/node/kubelet.go b/clitools/pkg/node/kubelet.go new file mode 100644 index 0000000..a7003fd --- /dev/null +++ b/clitools/pkg/node/kubelet.go @@ -0,0 +1,12 @@ +package node + +import ( + "context" + + "k8s.io/klog/v2" +) + +func RestartKubelet(context.Context, *NodeContext) error { + klog.Info("restart_kubelet: TODO implement rc-service kubelet restart") + return nil +} diff --git a/clitools/pkg/node/metadata.go b/clitools/pkg/node/metadata.go new file mode 100644 index 0000000..cbd869b --- /dev/null +++ b/clitools/pkg/node/metadata.go @@ -0,0 +1,37 @@ +package node + +import ( + "context" + + "k8s.io/klog/v2" +) + +func ApplyLocalNodeMetadataIfPossible(context.Context, *NodeContext) error { + klog.Info("apply_local_node_metadata_if_possible: TODO implement node labels/annotations") + return nil +} + +func AllowSingleNodeScheduling(context.Context, *NodeContext) error { + klog.Info("allow_single_node_scheduling: TODO implement control-plane taint removal") + return nil +} + +func SetHostnameIfNeeded(context.Context, *NodeContext) error { + klog.Info("set_hostname_if_needed: TODO implement hostname and /etc/hostname reconciliation") + return nil +} + +func PrintSummary(context.Context, *NodeContext) error { + klog.Info("print_summary: TODO emit final summary") + return nil +} + +func ReconcileControlPlane(context.Context, *NodeContext) error { + klog.Info("reconcile_control_plane: TODO implement existing CP reconciliation") + return nil +} + +func ReconcileNode(context.Context, *NodeContext) error { + klog.Info("reconcile_node: TODO implement existing joined node reconciliation") + return nil +} diff --git a/clitools/pkg/node/network.go b/clitools/pkg/node/network.go new file mode 100644 index 0000000..28609b3 --- /dev/null +++ b/clitools/pkg/node/network.go @@ -0,0 +1,91 @@ +package node + +import ( + "context" + "fmt" + "net" + "strings" + + system "undecided.project/monok8s/pkg/system" + "k8s.io/klog/v2" +) + +type NetworkConfig struct { + MgmtIface string + MgmtAddress string + MgmtGateway string +} + +func ConfigureMgmtInterface(cfg NetworkConfig) Step { + return func(ctx context.Context, nctx *NodeContext) error { + if strings.TrimSpace(cfg.MgmtIface) == "" { + return fmt.Errorf("mgmt interface is required") + } + if strings.TrimSpace(cfg.MgmtAddress) == "" { + return fmt.Errorf("mgmt address is required") + } + ip, ipNet, err := net.ParseCIDR(cfg.MgmtAddress) + if err != nil { + return fmt.Errorf("invalid mgmt address %q: %w", cfg.MgmtAddress, err) + } + wantIP := ip.String() + if gw := strings.TrimSpace(cfg.MgmtGateway); gw != "" && net.ParseIP(gw) == nil { + return fmt.Errorf("invalid mgmt gateway %q", gw) + } + + if _, err := nctx.System.Run(ctx, "ip", "link", "show", "dev", cfg.MgmtIface); err != nil { + return fmt.Errorf("interface not found: %s: %w", cfg.MgmtIface, err) + } + if _, err := nctx.System.Run(ctx, "ip", "link", "set", "dev", cfg.MgmtIface, "up"); err != nil { + return fmt.Errorf("failed to bring up interface %s: %w", cfg.MgmtIface, err) + } + + hasAddr, err := interfaceHasIPv4(ctx, nctx, cfg.MgmtIface, wantIP) + if err != nil { + return fmt.Errorf("failed checking existing address on %s: %w", cfg.MgmtIface, err) + } + if hasAddr { + klog.Infof("address already present on %s: %s", cfg.MgmtIface, cfg.MgmtAddress) + } else { + if _, err := nctx.System.Run(ctx, "ip", "addr", "add", ipNet.String(), "dev", cfg.MgmtIface); err != nil { + return fmt.Errorf("failed assigning %s to %s: %w", ipNet.String(), cfg.MgmtIface, err) + } + } + + if gw := strings.TrimSpace(cfg.MgmtGateway); gw != "" { + if _, err := nctx.System.Run(ctx, "ip", "route", "replace", "default", "via", gw, "dev", cfg.MgmtIface); err != nil { + return fmt.Errorf("failed setting default route via %s dev %s: %w", gw, cfg.MgmtIface, err) + } + } + return nil + } +} + +func EnsureIPForward(ctx context.Context, n *NodeContext) error { + return system.EnsureSysctl(ctx, n.System, "net.ipv4.ip_forward", "1") +} + +func ConfigureDNS(context.Context, *NodeContext) error { + klog.Info("configure_dns: TODO implement resolv.conf rendering") + return nil +} + +func interfaceHasIPv4(ctx context.Context, nctx *NodeContext, iface, wantIP string) (bool, error) { + res, err := nctx.System.Run(ctx, "ip", "-o", "-4", "addr", "show", "dev", iface) + if err != nil { + return false, err + } + for _, line := range strings.Split(res.Stdout, "\n") { + fields := strings.Fields(strings.TrimSpace(line)) + for i := 0; i < len(fields)-1; i++ { + if fields[i] != "inet" { + continue + } + ip, _, err := net.ParseCIDR(fields[i+1]) + if err == nil && ip.String() == wantIP { + return true, nil + } + } + } + return false, nil +} diff --git a/clitools/pkg/node/prereqs.go b/clitools/pkg/node/prereqs.go new file mode 100644 index 0000000..b6599c4 --- /dev/null +++ b/clitools/pkg/node/prereqs.go @@ -0,0 +1,27 @@ +package node + +import ( + "context" + + "k8s.io/klog/v2" +) + +func CheckPrereqs(context.Context, *NodeContext) error { + klog.Info("check_prereqs: TODO implement command discovery and runtime validation") + return nil +} + +func ValidateNetworkRequirements(context.Context, *NodeContext) error { + klog.Info("validate_network_requirements: TODO implement local IP and API reachability checks") + return nil +} + +func CheckUpgradePrereqs(context.Context, *NodeContext) error { + klog.Info("check_upgrade_prereqs: TODO implement kubeadm version / skew checks") + return nil +} + +func DecideBootstrapAction(_ context.Context, nctx *NodeContext) error { + klog.InfoS("decide_bootstrap_action", "bootstrapMode", nctx.Config.Spec.BootstrapMode, "joinKind", nctx.Config.Spec.JoinKind) + return nil +} diff --git a/clitools/pkg/system/helpers.go b/clitools/pkg/system/helpers.go new file mode 100644 index 0000000..6b13973 --- /dev/null +++ b/clitools/pkg/system/helpers.go @@ -0,0 +1,55 @@ +package system + +import ( + "context" + "fmt" + "os" + "strings" +) + +const DefaultSecond = 1_000_000_000 + +func EnsureServiceRunning(ctx context.Context, r *Runner, svc string) error { + if _, err := r.Run(ctx, " rc-service", svc, "status"); err == nil { + return nil + } + + _, err := r.RunRetry(ctx, RetryOptions{ + Attempts: 3, + Delay: 2 * DefaultSecond, + }, "rc-service", svc, "start") + if err != nil { + return fmt.Errorf("failed to start service %q: %w", svc, err) + } + + _, err = r.Run(ctx, "rc-service", svc, "status") + if err != nil { + return fmt.Errorf("service %q still not healthy after start: %w", svc, err) + } + return nil +} + +func EnsureSysctl(ctx context.Context, r *Runner, key, want string) error { + _, err := r.Run(ctx, "sysctl", "-w", key+"="+want) + if err != nil { + return fmt.Errorf("failed setting sysctl %s=%s: %w", key, want, err) + } + + // verify + path := "/proc/sys/" + strings.ReplaceAll(key, ".", "/") + raw, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed verifying sysctl %s: %w", key, err) + } + if strings.TrimSpace(string(raw)) != want { + return fmt.Errorf("sysctl %s not applied, expected %s got %s", + key, want, strings.TrimSpace(string(raw))) + } + + return nil +} + +func EnsureDir(ctx context.Context, r *Runner, path string, mode string) error { + _, err := r.Run(ctx, "install", "-d", "-m", mode, path) + return err +} diff --git a/clitools/pkg/system/runner.go b/clitools/pkg/system/runner.go new file mode 100644 index 0000000..4d3cda4 --- /dev/null +++ b/clitools/pkg/system/runner.go @@ -0,0 +1,346 @@ +package system + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "os" + "os/exec" + "strings" + "sync" + "time" +) + +type Logger interface { + Printf(format string, args ...any) +} + +type RunnerConfig struct { + DefaultTimeout time.Duration + StreamOutput bool + Logger Logger +} + +type Runner struct { + cfg RunnerConfig +} + +func NewRunner(cfg RunnerConfig) *Runner { + if cfg.DefaultTimeout <= 0 { + cfg.DefaultTimeout = 30 * time.Second + } + return &Runner{cfg: cfg} +} + +type Result struct { + Name string + Args []string + ExitCode int + Stdout string + Stderr string + Duration time.Duration + StartTime time.Time + EndTime time.Time +} + +type RunOptions struct { + Dir string + Env []string + Timeout time.Duration + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer + Quiet bool + RedactEnv []string +} + +type RetryOptions struct { + Attempts int + Delay time.Duration +} + +func (r *Runner) Run(ctx context.Context, name string, args ...string) (*Result, error) { + return r.RunWithOptions(ctx, name, args, RunOptions{}) +} + +func (r *Runner) RunWithOptions(ctx context.Context, name string, args []string, opt RunOptions) (*Result, error) { + if strings.TrimSpace(name) == "" { + return nil, errors.New("command name cannot be empty") + } + + timeout := opt.Timeout + if timeout <= 0 { + timeout = r.cfg.DefaultTimeout + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + cmd := exec.CommandContext(ctx, name, args...) + cmd.Dir = opt.Dir + cmd.Env = mergeEnv(os.Environ(), opt.Env) + cmd.Stdin = opt.Stdin + + var stdoutBuf bytes.Buffer + var stderrBuf bytes.Buffer + + stdoutW := io.Writer(&stdoutBuf) + stderrW := io.Writer(&stderrBuf) + + if opt.Stdout != nil { + stdoutW = io.MultiWriter(stdoutW, opt.Stdout) + } else if r.cfg.StreamOutput && !opt.Quiet { + stdoutW = io.MultiWriter(stdoutW, os.Stdout) + } + + if opt.Stderr != nil { + stderrW = io.MultiWriter(stderrW, opt.Stderr) + } else if r.cfg.StreamOutput && !opt.Quiet { + stderrW = io.MultiWriter(stderrW, os.Stderr) + } + + cmd.Stdout = stdoutW + cmd.Stderr = stderrW + + start := time.Now() + if r.cfg.Logger != nil { + r.cfg.Logger.Printf("run: %s", formatCmd(name, args)) + } + + err := cmd.Run() + end := time.Now() + + res := &Result{ + Name: name, + Args: append([]string(nil), args...), + Stdout: stdoutBuf.String(), + Stderr: stderrBuf.String(), + Duration: end.Sub(start), + StartTime: start, + EndTime: end, + ExitCode: exitCode(err), + } + + if err != nil { + if ctx.Err() == context.DeadlineExceeded { + return res, fmt.Errorf("command timed out after %s: %s", timeout, formatCmd(name, args)) + } + return res, fmt.Errorf("command failed (exit=%d): %s\nstderr:\n%s", + res.ExitCode, formatCmd(name, args), trimBlock(res.Stderr)) + } + + return res, nil +} + +func (r *Runner) RunRetry(ctx context.Context, retry RetryOptions, name string, args ...string) (*Result, error) { + return r.RunRetryWithOptions(ctx, retry, name, args, RunOptions{}) +} + +func (r *Runner) RunRetryWithOptions(ctx context.Context, retry RetryOptions, name string, args []string, opt RunOptions) (*Result, error) { + if retry.Attempts <= 0 { + retry.Attempts = 1 + } + if retry.Delay < 0 { + retry.Delay = 0 + } + + var lastRes *Result + var lastErr error + + for attempt := 1; attempt <= retry.Attempts; attempt++ { + res, err := r.RunWithOptions(ctx, name, args, opt) + lastRes, lastErr = res, err + if err == nil { + return res, nil + } + + if r.cfg.Logger != nil { + r.cfg.Logger.Printf("attempt %d/%d failed: %v", attempt, retry.Attempts, err) + } + + if attempt == retry.Attempts { + break + } + + select { + case <-ctx.Done(): + return lastRes, ctx.Err() + case <-time.After(retry.Delay): + } + } + + return lastRes, lastErr +} + +type StepFunc func(ctx context.Context, r *Runner) error + +type Step struct { + Name string + Description string + Retry RetryOptions + Run StepFunc +} + +type StepEvent struct { + Name string + StartTime time.Time + EndTime time.Time + Duration time.Duration + Err error +} + +type StepReporter interface { + StepStarted(event StepEvent) + StepFinished(event StepEvent) +} + +type Phase struct { + Name string + Steps []Step +} + +func (r *Runner) RunPhase(ctx context.Context, phase Phase, reporter StepReporter) error { + for _, step := range phase.Steps { + start := time.Now() + if reporter != nil { + reporter.StepStarted(StepEvent{ + Name: step.Name, + StartTime: start, + }) + } + + var err error + if step.Retry.Attempts > 0 { + err = runStepWithRetry(ctx, r, step) + } else { + err = step.Run(ctx, r) + } + + end := time.Now() + if reporter != nil { + reporter.StepFinished(StepEvent{ + Name: step.Name, + StartTime: start, + EndTime: end, + Duration: end.Sub(start), + Err: err, + }) + } + + if err != nil { + return fmt.Errorf("phase %q step %q failed: %w", phase.Name, step.Name, err) + } + } + return nil +} + +func runStepWithRetry(ctx context.Context, r *Runner, step Step) error { + attempts := step.Retry.Attempts + if attempts <= 0 { + attempts = 1 + } + + var lastErr error + for i := 1; i <= attempts; i++ { + lastErr = step.Run(ctx, r) + if lastErr == nil { + return nil + } + if i == attempts { + break + } + if r.cfg.Logger != nil { + r.cfg.Logger.Printf("step %q attempt %d/%d failed: %v", step.Name, i, attempts, lastErr) + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(step.Retry.Delay): + } + } + return lastErr +} + +func CheckCommandExists(name string) error { + _, err := exec.LookPath(name) + if err != nil { + return fmt.Errorf("required command not found in PATH: %s", name) + } + return nil +} + +func mergeEnv(base []string, extra []string) []string { + if len(extra) == 0 { + return base + } + m := map[string]string{} + for _, kv := range base { + k, v, ok := strings.Cut(kv, "=") + if ok { + m[k] = v + } + } + for _, kv := range extra { + k, v, ok := strings.Cut(kv, "=") + if ok { + m[k] = v + } + } + out := make([]string, 0, len(m)) + for k, v := range m { + out = append(out, k+"="+v) + } + return out +} + +func formatCmd(name string, args []string) string { + parts := make([]string, 0, len(args)+1) + parts = append(parts, shellQuote(name)) + for _, a := range args { + parts = append(parts, shellQuote(a)) + } + return strings.Join(parts, " ") +} + +func shellQuote(s string) string { + if s == "" { + return "''" + } + if !strings.ContainsAny(s, " \t\n'\"\\$`!&|;<>()[]{}*?~") { + return s + } + return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'" +} + +func trimBlock(s string) string { + s = strings.TrimSpace(s) + if s == "" { + return "(empty)" + } + return s +} + +func exitCode(err error) int { + if err == nil { + return 0 + } + + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + return exitErr.ExitCode() + } + + return -1 +} + +type StdLogger struct { + mu sync.Mutex +} + +func (l *StdLogger) Printf(format string, args ...any) { + l.mu.Lock() + defer l.mu.Unlock() + fmt.Fprintf(os.Stderr, format+"\n", args...) +} diff --git a/clitools/pkg/templates/templates.go b/clitools/pkg/templates/templates.go new file mode 100644 index 0000000..a1708f2 --- /dev/null +++ b/clitools/pkg/templates/templates.go @@ -0,0 +1,54 @@ +package templates + +const MonoKSConfigYAML = `apiVersion: monok8s.io/v1alpha1 +kind: MonoKSConfig +metadata: + name: example + namespace: kube-system +spec: + kubernetesVersion: v1.35.3 + nodeName: monok8s-master-1 + clusterName: monok8s + clusterDomain: cluster.local + podSubnet: 10.244.0.0/16 + serviceSubnet: 10.96.0.0/12 + apiServerAdvertiseAddress: 10.0.0.10 + apiServerEndpoint: 10.0.0.10:6443 + containerRuntimeEndpoint: unix:///var/run/crio/crio.sock + bootstrapMode: init + joinKind: worker + cniPlugin: none + allowSchedulingOnControlPlane: true + skipImageCheck: false + kubeProxyNodePortAddresses: + - primary + subjectAltNames: + - 10.0.0.10 + nodeLabels: + node-role.kubernetes.io/control-plane: "" + nodeAnnotations: {} + network: + hostname: monok8s-master-1 + managementIface: eth0 + managementCIDR: 10.0.0.10/24 + managementGateway: 10.0.0.1 + dnsNameservers: + - 1.1.1.1 + - 8.8.8.8 + dnsSearchDomains: + - lan +` + +const OSUpgradeYAML = `apiVersion: monok8s.io/v1alpha1 +kind: OSUpgrade +metadata: + name: example + namespace: kube-system +spec: + version: v0.0.1 + imageURL: https://example.invalid/images/monok8s-v0.0.1.img.zst + targetPartition: B + nodeSelector: + - monok8s-master-1 + force: false +`