Added some ctl boilerplate
This commit is contained in:
142
clitools/pkg/apis/monok8s/v1alpha1/types.go
Normal file
142
clitools/pkg/apis/monok8s/v1alpha1/types.go
Normal file
@@ -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
|
||||
}
|
||||
64
clitools/pkg/bootstrap/registry.go
Normal file
64
clitools/pkg/bootstrap/registry.go
Normal file
@@ -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
|
||||
}
|
||||
58
clitools/pkg/bootstrap/runner.go
Normal file
58
clitools/pkg/bootstrap/runner.go
Normal file
@@ -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)
|
||||
}
|
||||
51
clitools/pkg/cmd/agent/agent.go
Normal file
51
clitools/pkg/cmd/agent/agent.go
Normal file
@@ -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
|
||||
54
clitools/pkg/cmd/apply/apply.go
Normal file
54
clitools/pkg/cmd/apply/apply.go
Normal file
@@ -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
|
||||
30
clitools/pkg/cmd/checkconfig/checkconfig.go
Normal file
30
clitools/pkg/cmd/checkconfig/checkconfig.go
Normal file
@@ -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
|
||||
}
|
||||
31
clitools/pkg/cmd/create/create.go
Normal file
31
clitools/pkg/cmd/create/create.go
Normal file
@@ -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
|
||||
}
|
||||
34
clitools/pkg/cmd/initcmd/init.go
Normal file
34
clitools/pkg/cmd/initcmd/init.go
Normal file
@@ -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
|
||||
}
|
||||
30
clitools/pkg/cmd/internal/internal.go
Normal file
30
clitools/pkg/cmd/internal/internal.go
Normal file
@@ -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
|
||||
}
|
||||
41
clitools/pkg/cmd/root/root.go
Normal file
41
clitools/pkg/cmd/root/root.go
Normal file
@@ -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
|
||||
}
|
||||
134
clitools/pkg/config/config.go
Normal file
134
clitools/pkg/config/config.go
Normal file
@@ -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
|
||||
}
|
||||
71
clitools/pkg/crds/crds.go
Normal file
71
clitools/pkg/crds/crds.go
Normal file
@@ -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 }
|
||||
50
clitools/pkg/kube/clients.go
Normal file
50
clitools/pkg/kube/clients.go
Normal file
@@ -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
|
||||
}
|
||||
15
clitools/pkg/node/context.go
Normal file
15
clitools/pkg/node/context.go
Normal file
@@ -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
|
||||
22
clitools/pkg/node/crio.go
Normal file
22
clitools/pkg/node/crio.go
Normal file
@@ -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
|
||||
}
|
||||
36
clitools/pkg/node/kubeadm.go
Normal file
36
clitools/pkg/node/kubeadm.go
Normal file
@@ -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 <file>")
|
||||
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
|
||||
}
|
||||
12
clitools/pkg/node/kubelet.go
Normal file
12
clitools/pkg/node/kubelet.go
Normal file
@@ -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
|
||||
}
|
||||
37
clitools/pkg/node/metadata.go
Normal file
37
clitools/pkg/node/metadata.go
Normal file
@@ -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
|
||||
}
|
||||
91
clitools/pkg/node/network.go
Normal file
91
clitools/pkg/node/network.go
Normal file
@@ -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
|
||||
}
|
||||
27
clitools/pkg/node/prereqs.go
Normal file
27
clitools/pkg/node/prereqs.go
Normal file
@@ -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
|
||||
}
|
||||
55
clitools/pkg/system/helpers.go
Normal file
55
clitools/pkg/system/helpers.go
Normal file
@@ -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
|
||||
}
|
||||
346
clitools/pkg/system/runner.go
Normal file
346
clitools/pkg/system/runner.go
Normal file
@@ -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...)
|
||||
}
|
||||
54
clitools/pkg/templates/templates.go
Normal file
54
clitools/pkg/templates/templates.go
Normal file
@@ -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
|
||||
`
|
||||
Reference in New Issue
Block a user