diff --git a/cmd/kk/app/options/precheck.go b/cmd/kk/app/options/precheck.go index 3e5e6e2d..695b121e 100644 --- a/cmd/kk/app/options/precheck.go +++ b/cmd/kk/app/options/precheck.go @@ -19,11 +19,8 @@ package options import ( "fmt" - "github.com/google/uuid" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/rand" cliflag "k8s.io/component-base/cli/flag" kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" @@ -62,15 +59,9 @@ func (o *PreCheckOptions) Flags() cliflag.NamedFlagSets { func (o *PreCheckOptions) Complete(cmd *cobra.Command, args []string) (*kubekeyv1.Pipeline, error) { kk := &kubekeyv1.Pipeline{ - TypeMeta: metav1.TypeMeta{ - Kind: "Pipeline", - APIVersion: "kubekey.kubesphere.io/v1alpha1", - }, ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("precheck-%s", rand.String(6)), - Namespace: metav1.NamespaceDefault, - UID: types.UID(uuid.NewString()), - CreationTimestamp: metav1.Now(), + GenerateName: "precheck-", + Namespace: metav1.NamespaceDefault, Annotations: map[string]string{ kubekeyv1.BuiltinsProjectAnnotation: "", }, diff --git a/cmd/kk/app/options/run.go b/cmd/kk/app/options/run.go index ce6d83e5..92098a2b 100644 --- a/cmd/kk/app/options/run.go +++ b/cmd/kk/app/options/run.go @@ -21,11 +21,8 @@ import ( "fmt" "strings" - "github.com/google/uuid" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/rand" cliflag "k8s.io/component-base/cli/flag" "k8s.io/klog/v2" @@ -109,16 +106,10 @@ func (o *KubekeyRunOptions) Flags() cliflag.NamedFlagSets { func (o *KubekeyRunOptions) Complete(cmd *cobra.Command, args []string) (*kubekeyv1.Pipeline, error) { kk := &kubekeyv1.Pipeline{ - TypeMeta: metav1.TypeMeta{ - Kind: "Pipeline", - APIVersion: "kubekey.kubesphere.io/v1alpha1", - }, ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("run-command-%s", rand.String(6)), - Namespace: metav1.NamespaceDefault, - UID: types.UID(uuid.NewString()), - CreationTimestamp: metav1.Now(), - Annotations: map[string]string{}, + GenerateName: "run-", + Namespace: metav1.NamespaceDefault, + Annotations: map[string]string{}, }, } // complete playbook. now only support one playbook diff --git a/cmd/kk/app/run.go b/cmd/kk/app/run.go index 64024e1f..a8434a05 100644 --- a/cmd/kk/app/run.go +++ b/cmd/kk/app/run.go @@ -77,11 +77,11 @@ func run(ctx context.Context, kk *kubekeyv1.Pipeline, configFile string, invento config := &kubekeyv1.Config{} cdata, err := os.ReadFile(configFile) if err != nil { - klog.Errorf("read config file error %v", err) + klog.ErrorS(err, "read config file error") return err } if err := yaml.Unmarshal(cdata, config); err != nil { - klog.Errorf("unmarshal config file error %v", err) + klog.ErrorS(err, "unmarshal config file error") return err } if config.Namespace == "" { @@ -100,7 +100,7 @@ func run(ctx context.Context, kk *kubekeyv1.Pipeline, configFile string, invento inventory := &kubekeyv1.Inventory{} idata, err := os.ReadFile(inventoryFile) if err := yaml.Unmarshal(idata, inventory); err != nil { - klog.Errorf("unmarshal inventory file error %v", err) + klog.ErrorS(err, "unmarshal inventory file error") return err } if inventory.Namespace == "" { @@ -114,9 +114,14 @@ func run(ctx context.Context, kk *kubekeyv1.Pipeline, configFile string, invento APIVersion: inventory.APIVersion, ResourceVersion: inventory.ResourceVersion, } - return manager.NewCommandManager(manager.CommandManagerOptions{ + mgr, err := manager.NewCommandManager(manager.CommandManagerOptions{ Pipeline: kk, Config: config, Inventory: inventory, - }).Run(ctx) + }) + if err != nil { + klog.ErrorS(err, "Create command manager error") + return err + } + return mgr.Run(ctx) } diff --git a/go.mod b/go.mod index ce6841db..73c211a0 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,10 @@ module github.com/kubesphere/kubekey/v4 -go 1.20 +go 1.21 require ( github.com/flosch/pongo2/v6 v6.0.0 + github.com/fsnotify/fsnotify v1.7.0 github.com/go-git/go-git/v5 v5.11.0 github.com/google/gops v0.3.28 github.com/google/uuid v1.5.0 @@ -11,46 +12,56 @@ require ( github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.17.0 + golang.org/x/crypto v0.18.0 golang.org/x/time v0.5.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.29.0 - k8s.io/apimachinery v0.29.0 - k8s.io/client-go v0.29.0 - k8s.io/component-base v0.29.0 - k8s.io/klog/v2 v2.110.1 + k8s.io/api v0.29.1 + k8s.io/apimachinery v0.29.1 + k8s.io/apiserver v0.29.1 + k8s.io/client-go v0.29.1 + k8s.io/component-base v0.29.1 + k8s.io/klog/v2 v2.120.1 k8s.io/utils v0.0.0-20240102154912-e7106e64919e - sigs.k8s.io/controller-runtime v0.16.3 + sigs.k8s.io/controller-runtime v0.17.0 + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 sigs.k8s.io/yaml v1.4.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.11.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.2 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/evanphx/json-patch v5.7.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.7.0 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/evanphx/json-patch v5.8.1+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.8.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/swag v0.22.7 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/cel-go v0.17.7 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -59,7 +70,6 @@ require ( github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kr/fs v0.1.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -68,27 +78,47 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/common v0.46.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect github.com/skeema/knownhosts v1.2.1 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect + go.etcd.io/etcd/api/v3 v3.5.11 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.11 // indirect + go.etcd.io/etcd/client/v3 v3.5.11 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/sdk v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/oauth2 v0.15.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.16.1 // indirect + golang.org/x/tools v0.17.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect + google.golang.org/grpc v1.60.1 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiextensions-apiserver v0.29.0 // indirect - k8s.io/kube-openapi v0.0.0-20240103195357-a9f8850cb432 // indirect + k8s.io/apiextensions-apiserver v0.29.1 // indirect + k8s.io/kube-openapi v0.0.0-20240117194847-208609032b15 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) diff --git a/go.sum b/go.sum index 9bea54c4..b74730be 100644 --- a/go.sum +++ b/go.sum @@ -1,53 +1,82 @@ +cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE= -github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 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/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= -github.com/emicklei/go-restful/v3 v3.11.1 h1:S+9bSbua1z3FgCnV0KKOSSZ3mDthb5NyEPL5gEpCvyk= -github.com/emicklei/go-restful/v3 v3.11.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/emicklei/go-restful/v3 v3.11.2 h1:1onLa9DcsMYO9P+CXaL0dStDqQ2EHHXLiz+BtnqkLAU= +github.com/emicklei/go-restful/v3 v3.11.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= -github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= -github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/evanphx/json-patch v5.8.1+incompatible h1:2toJaoe7/rNa1zpeQx0UnVEjqk6z2ecyA20V/zg8vTU= +github.com/evanphx/json-patch v5.8.1+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.8.1 h1:iPEdwg0XayoS+E7Mth9JxwUtOgyVxnDTXHtKhZPlZxA= +github.com/evanphx/json-patch/v5 v5.8.1/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU= github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= @@ -55,14 +84,22 @@ github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHa github.com/go-openapi/swag v0.22.7 h1:JWrc1uc/P9cSomxfnsFSVWoE1FW6bNbrVPmpQYpCcR8= github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/cel-go v0.17.7 h1:6ebJFzu1xO2n7TLtN+UBqShGBhlD85bhvglh5DpcfqQ= +github.com/google/cel-go v0.17.7/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -75,14 +112,27 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gops v0.3.28 h1:2Xr57tqKAmQYRAfG12E+yLcoa2Y42UJo2lOrUFL9ark= github.com/google/gops v0.3.28/go.mod h1:6f6+Nl8LcHrzJwi8+p0ii+vmBFSlB4f8cOOkTJ7sk4c= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= 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= @@ -95,13 +145,13 @@ github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 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= @@ -109,8 +159,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= +github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -123,38 +175,87 @@ github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+ github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= +github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= +go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/etcd/api/v3 v3.5.11 h1:B54KwXbWDHyD3XYAwprxNzTe7vlhR69LuBgZnMVvS7E= +go.etcd.io/etcd/api/v3 v3.5.11/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= +go.etcd.io/etcd/client/pkg/v3 v3.5.11 h1:bT2xVspdiCj2910T0V+/KHcVKjkUrCZVtk8J2JF2z1A= +go.etcd.io/etcd/client/pkg/v3 v3.5.11/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= +go.etcd.io/etcd/client/v2 v2.305.10 h1:MrmRktzv/XF8CvtQt+P6wLUlURaNpSDJHFZhe//2QE4= +go.etcd.io/etcd/client/v2 v2.305.10/go.mod h1:m3CKZi69HzilhVqtPDcjhSGp+kA1OmbNn0qamH80xjA= +go.etcd.io/etcd/client/v3 v3.5.11 h1:ajWtgoNSZJ1gmS8k+icvPtqsqEav+iUorF7b0qozgUU= +go.etcd.io/etcd/client/v3 v3.5.11/go.mod h1:a6xQUEqFJ8vztO1agJh/KQKOMfFI8og52ZconzcDJwE= +go.etcd.io/etcd/pkg/v3 v3.5.10 h1:WPR8K0e9kWl1gAhB5A7gEa5ZBTNkT9NdNWrR8Qpo1CM= +go.etcd.io/etcd/pkg/v3 v3.5.10/go.mod h1:TKTuCKKcF1zxmfKWDkfz5qqYaE3JncKKZPFf8c1nFUs= +go.etcd.io/etcd/raft/v3 v3.5.10 h1:cgNAYe7xrsrn/5kXMSaH8kM/Ky8mAdMqGOxyYwpP0LA= +go.etcd.io/etcd/raft/v3 v3.5.10/go.mod h1:odD6kr8XQXTy9oQnyMPBOr0TVe+gT0neQhElQ6jbGRc= +go.etcd.io/etcd/server/v3 v3.5.10 h1:4NOGyOwD5sUZ22PiWYKmfxqoeh72z6EhYjNosKGLmZg= +go.etcd.io/etcd/server/v3 v3.5.10/go.mod h1:gBplPHfs6YI0L+RpGkTQO7buDbHv5HJGG/Bst0/zIPo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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= @@ -163,10 +264,10 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -184,16 +285,17 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -210,16 +312,16 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -238,8 +340,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= 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= @@ -248,6 +350,14 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= +google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac h1:OZkkudMUu9LVQMCoRUbI/1p5VCo9BOrlvkqMvWtqa6s= +google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= @@ -255,8 +365,11 @@ google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -266,24 +379,28 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= -k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= -k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= -k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= -k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= -k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= -k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= -k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= -k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s= -k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= -k8s.io/kube-openapi v0.0.0-20240103195357-a9f8850cb432 h1:+XYBQU3ZKUu60H6fEnkitTTabGoKfIG8zczhZBENu9o= -k8s.io/kube-openapi v0.0.0-20240103195357-a9f8850cb432/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= +k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= +k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= +k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw= +k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU= +k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= +k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/apiserver v0.29.1 h1:e2wwHUfEmMsa8+cuft8MT56+16EONIEK8A/gpBSco+g= +k8s.io/apiserver v0.29.1/go.mod h1:V0EpkTRrJymyVT3M49we8uh2RvXf7fWC5XLB0P3SwRw= +k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= +k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= +k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw= +k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240117194847-208609032b15 h1:m6dl1pkxz3HuE2mP9MUYPCCGyy6IIFlv/vTlLBDxIwA= +k8s.io/kube-openapi v0.0.0-20240117194847-208609032b15/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= -sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 h1:/U5vjBbQn3RChhv7P11uhYvCSm5G2GaIi5AIGBS6r4c= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4= +sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s= +sigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/hack/update-goimports.sh b/hack/update-goimports.sh index 2174e3a1..3e3df807 100755 --- a/hack/update-goimports.sh +++ b/hack/update-goimports.sh @@ -43,4 +43,4 @@ cd "${KUBE_ROOT}" || exit 1 IFS=$'\n' read -r -d '' -a files < <( find . -type f -name '*.go' -not -path "./vendor/*" -not -path "./pkg/client/*" -not -name "zz_generated.deepcopy.go" && printf '\0' ) -"goimports" -w -local kubesphere.io/kubesphere "${files[@]}" +"goimports" -w -local github.com/kubesphere/kubekey "${files[@]}" diff --git a/hack/verify-goimports.sh b/hack/verify-goimports.sh index 6f8e26da..87075dd7 100755 --- a/hack/verify-goimports.sh +++ b/hack/verify-goimports.sh @@ -43,7 +43,7 @@ cd "${KUBE_ROOT}" || exit 1 IFS=$'\n' read -r -d '' -a files < <( find . -type f -name '*.go' -not -path "./vendor/*" -not -path "./pkg/apis/*" -not -path "./pkg/client/*" -not -name "zz_generated.deepcopy.go" && printf '\0' ) -output=$(goimports -local kubesphere.io/kubesphere -l "${files[@]}") +output=$(goimports -local github.com/kubesphere/kubekey -l "${files[@]}") if [ "${output}" != "" ]; then echo "The following files are not import formatted" diff --git a/pkg/apis/kubekey/v1alpha1/conversion.go b/pkg/apis/kubekey/v1alpha1/conversion.go new file mode 100644 index 00000000..86fde9bf --- /dev/null +++ b/pkg/apis/kubekey/v1alpha1/conversion.go @@ -0,0 +1,39 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" +) + +// AddConversionFuncs adds the conversion functions to the given scheme. +// NOTE: ownerReferences:pipeline is valid in proxy client. +func AddConversionFuncs(scheme *runtime.Scheme) error { + return scheme.AddFieldLabelConversionFunc( + SchemeGroupVersion.WithKind("Task"), + func(label, value string) (string, string, error) { + switch label { + case "metadata.name", "metadata.namespace", "ownerReferences:pipeline": + return label, value, nil + default: + return "", "", fmt.Errorf("field label %q not supported for Task", label) + } + }, + ) +} diff --git a/pkg/apis/kubekey/v1alpha1/task_types.go b/pkg/apis/kubekey/v1alpha1/task_types.go index a4c5a8ca..6ee42eb5 100644 --- a/pkg/apis/kubekey/v1alpha1/task_types.go +++ b/pkg/apis/kubekey/v1alpha1/task_types.go @@ -116,3 +116,7 @@ func (t Task) IsSucceed() bool { func (t Task) IsFailed() bool { return t.Status.Phase == TaskPhaseFailed && t.Spec.Retries <= t.Status.RestartCount } + +func init() { + SchemeBuilder.Register(&Task{}, &TaskList{}) +} diff --git a/pkg/connector/local_connector.go b/pkg/connector/local_connector.go index e0286708..a2f7abf8 100644 --- a/pkg/connector/local_connector.go +++ b/pkg/connector/local_connector.go @@ -43,17 +43,17 @@ func (c *localConnector) CopyFile(ctx context.Context, local []byte, remoteFile // create remote file if _, err := os.Stat(filepath.Dir(remoteFile)); err != nil && os.IsNotExist(err) { if err := os.MkdirAll(filepath.Dir(remoteFile), mode); err != nil { - klog.ErrorS(err, "Failed to create remote dir", "remote_file", remoteFile) + klog.V(4).ErrorS(err, "Failed to create remote dir", "remote_file", remoteFile) return err } } rf, err := os.Create(remoteFile) if err != nil { - klog.ErrorS(err, "Failed to create remote file", "remote_file", remoteFile) + klog.V(4).ErrorS(err, "Failed to create remote file", "remote_file", remoteFile) return err } if _, err := rf.Write(local); err != nil { - klog.ErrorS(err, "Failed to write content to remote file", "remote_file", remoteFile) + klog.V(4).ErrorS(err, "Failed to write content to remote file", "remote_file", remoteFile) return err } return rf.Chmod(mode) @@ -63,11 +63,11 @@ func (c *localConnector) FetchFile(ctx context.Context, remoteFile string, local var err error file, err := os.Open(remoteFile) if err != nil { - klog.ErrorS(err, "Failed to read remote file failed", "remote_file", remoteFile) + klog.V(4).ErrorS(err, "Failed to read remote file failed", "remote_file", remoteFile) return err } if _, err := io.Copy(local, file); err != nil { - klog.ErrorS(err, "Failed to copy remote file to local", "remote_file", remoteFile) + klog.V(4).ErrorS(err, "Failed to copy remote file to local", "remote_file", remoteFile) return err } return nil diff --git a/pkg/connector/ssh_connector.go b/pkg/connector/ssh_connector.go index 949a394a..efcbf2db 100644 --- a/pkg/connector/ssh_connector.go +++ b/pkg/connector/ssh_connector.go @@ -24,6 +24,7 @@ import ( "os" "path/filepath" "strconv" + "time" "github.com/pkg/sftp" "golang.org/x/crypto/ssh" @@ -56,9 +57,10 @@ func (c *sshConnector) Init(ctx context.Context) error { User: pointer.StringDeref(c.User, ""), Auth: auth, HostKeyCallback: ssh.InsecureIgnoreHostKey(), + Timeout: 30 * time.Second, }) if err != nil { - klog.ErrorS(err, "Dial ssh server failed", "host", c.Host, "port", *c.Port) + klog.V(4).ErrorS(err, "Dial ssh server failed", "host", c.Host, "port", *c.Port) return err } c.client = sshClient @@ -74,26 +76,26 @@ func (c *sshConnector) CopyFile(ctx context.Context, src []byte, remoteFile stri // create sftp client sftpClient, err := sftp.NewClient(c.client) if err != nil { - klog.ErrorS(err, "Failed to create sftp client") + klog.V(4).ErrorS(err, "Failed to create sftp client") return err } defer sftpClient.Close() // create remote file if _, err := sftpClient.Stat(filepath.Dir(remoteFile)); err != nil && os.IsNotExist(err) { if err := sftpClient.MkdirAll(filepath.Dir(remoteFile)); err != nil { - klog.ErrorS(err, "Failed to create remote dir", "remote_file", remoteFile) + klog.V(4).ErrorS(err, "Failed to create remote dir", "remote_file", remoteFile) return err } } rf, err := sftpClient.Create(remoteFile) if err != nil { - klog.ErrorS(err, "Failed to create remote file", "remote_file", remoteFile) + klog.V(4).ErrorS(err, "Failed to create remote file", "remote_file", remoteFile) return err } defer rf.Close() if _, err = rf.Write(src); err != nil { - klog.ErrorS(err, "Failed to write content to remote file", "remote_file", remoteFile) + klog.V(4).ErrorS(err, "Failed to write content to remote file", "remote_file", remoteFile) return err } return rf.Chmod(mode) @@ -103,18 +105,18 @@ func (c *sshConnector) FetchFile(ctx context.Context, remoteFile string, local i // create sftp client sftpClient, err := sftp.NewClient(c.client) if err != nil { - klog.ErrorS(err, "Failed to create sftp client", "remote_file", remoteFile) + klog.V(4).ErrorS(err, "Failed to create sftp client", "remote_file", remoteFile) return err } defer sftpClient.Close() rf, err := sftpClient.Open(remoteFile) if err != nil { - klog.ErrorS(err, "Failed to open file", "remote_file", remoteFile) + klog.V(4).ErrorS(err, "Failed to open file", "remote_file", remoteFile) return err } defer rf.Close() if _, err := io.Copy(local, rf); err != nil { - klog.ErrorS(err, "Failed to copy file", "remote_file", remoteFile) + klog.V(4).ErrorS(err, "Failed to copy file", "remote_file", remoteFile) return err } return nil @@ -124,7 +126,7 @@ func (c *sshConnector) ExecuteCommand(ctx context.Context, cmd string) ([]byte, // create ssh session session, err := c.client.NewSession() if err != nil { - klog.ErrorS(err, "Failed to create ssh session") + klog.V(4).ErrorS(err, "Failed to create ssh session") return nil, err } defer session.Close() diff --git a/pkg/const/context.go b/pkg/const/context.go index e06246c3..233f7b58 100644 --- a/pkg/const/context.go +++ b/pkg/const/context.go @@ -20,8 +20,7 @@ package _const // use in marshal playbook.Block const ( - CtxBlockHosts = "block-hosts" - CtxBlockRole = "block-role" - CtxBlockWhen = "block-when" - CtxBlockTaskUID = "block-task-uid" + CtxBlockHosts = "block-hosts" + CtxBlockRole = "block-role" + CtxBlockWhen = "block-when" ) diff --git a/pkg/const/helper.go b/pkg/const/helper.go index d2a8524c..ed2d76f8 100644 --- a/pkg/const/helper.go +++ b/pkg/const/helper.go @@ -19,13 +19,6 @@ package _const import ( "path/filepath" "sync" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/klog/v2" - - kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" - kubekeyv1alpha1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1alpha1" ) var workDirOnce = &sync.Once{} @@ -42,32 +35,7 @@ func GetWorkDir() string { return workDir } -func ResourceFromObject(obj runtime.Object) string { - switch obj.(type) { - case *kubekeyv1.Pipeline, *kubekeyv1.PipelineList: - return RuntimePipelineDir - case *kubekeyv1.Config, *kubekeyv1.ConfigList: - return RuntimeConfigDir - case *kubekeyv1.Inventory, *kubekeyv1.InventoryList: - return RuntimeInventoryDir - case *kubekeyv1alpha1.Task, *kubekeyv1alpha1.TaskList: - return RuntimePipelineTaskDir - default: - return "" - } -} - -func RuntimeDirFromObject(obj runtime.Object) string { - resource := ResourceFromObject(obj) - if resource == "" { - klog.Errorf("unsupported object type: %s", obj.GetObjectKind().GroupVersionKind().String()) - return "" - } - mo, ok := obj.(metav1.Object) - - if !ok { - klog.Errorf("Failed convert to metav1.Object: %s", obj.GetObjectKind().GroupVersionKind().String()) - return "" - } - return filepath.Join(workDir, RuntimeDir, mo.GetNamespace(), resource, mo.GetName()) +// GetRuntimeDir returns the absolute path of the runtime directory. +func GetRuntimeDir() string { + return filepath.Join(workDir, RuntimeDir) } diff --git a/pkg/const/helper_test.go b/pkg/const/helper_test.go index 7e2a94e3..42ac33ff 100644 --- a/pkg/const/helper_test.go +++ b/pkg/const/helper_test.go @@ -20,10 +20,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - - kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" - kubekeyv1alpha1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1alpha1" ) func TestWorkDir(t *testing.T) { @@ -36,15 +32,3 @@ func TestWorkDir(t *testing.T) { SetWorkDir("/tmp2") assert.Equal(t, "/tmp", GetWorkDir()) } - -func TestResourceFromObject(t *testing.T) { - assert.Equal(t, RuntimePipelineDir, ResourceFromObject(&kubekeyv1.Pipeline{})) - assert.Equal(t, RuntimePipelineDir, ResourceFromObject(&kubekeyv1.PipelineList{})) - assert.Equal(t, RuntimeConfigDir, ResourceFromObject(&kubekeyv1.Config{})) - assert.Equal(t, RuntimeConfigDir, ResourceFromObject(&kubekeyv1.ConfigList{})) - assert.Equal(t, RuntimeInventoryDir, ResourceFromObject(&kubekeyv1.Inventory{})) - assert.Equal(t, RuntimeInventoryDir, ResourceFromObject(&kubekeyv1.InventoryList{})) - assert.Equal(t, RuntimePipelineTaskDir, ResourceFromObject(&kubekeyv1alpha1.Task{})) - assert.Equal(t, RuntimePipelineTaskDir, ResourceFromObject(&kubekeyv1alpha1.TaskList{})) - assert.Equal(t, "", ResourceFromObject(&unstructured.Unstructured{})) -} diff --git a/pkg/const/scheme.go b/pkg/const/scheme.go new file mode 100644 index 00000000..a43da72e --- /dev/null +++ b/pkg/const/scheme.go @@ -0,0 +1,48 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package _const + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + + kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" + kubekeyv1alpha1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1alpha1" +) + +var ( + // Scheme is the default instance of runtime.Scheme to which types in the Kubernetes API are already registered. + // NOTE: If you are copying this file to start a new api group, STOP! Copy the + // extensions group instead. This Scheme is special and should appear ONLY in + // the api group, unless you really know what you're doing. + // TODO(lavalamp): make the above error impossible. + Scheme = newScheme() + + // Codecs provides access to encoding and decoding for the scheme + Codecs = serializer.NewCodecFactory(Scheme) + + // ParameterCodec handles versioning of objects that are converted to query parameters. + ParameterCodec = runtime.NewParameterCodec(Scheme) +) + +func newScheme() *runtime.Scheme { + s := runtime.NewScheme() + kubekeyv1.AddToScheme(s) + kubekeyv1alpha1.AddToScheme(s) + kubekeyv1alpha1.AddConversionFuncs(s) + return s +} diff --git a/pkg/const/workdir.go b/pkg/const/workdir.go index 3d3a3b97..3d593d2d 100644 --- a/pkg/const/workdir.go +++ b/pkg/const/workdir.go @@ -31,24 +31,24 @@ workdir/ | | | | |-- files/ | | | |-- ansible-project2/ -| | +| |-- ... | |-- runtime/ -| |-- namespace/ +|-- group/version/ | | |-- pipelines/ -| | | |-- pipelineName/ +| | | |-- namespace/ | | | | |-- pipeline.yaml -| | | | |-- variable/ +| | | | |-- /pipelineName/variable/ | | | | | |-- location.json | | | | | |-- hostname.json | | |-- tasks/ -| | | |-- taskName/ +| | | |-- namespace/ | | | | |-- task.yaml | | |-- configs/ -| | | |-- configName/ -| | | | |-- config.yaml +| | | |-- namespace/ +| | | | | |-- config.yaml | | |-- inventories/ -| | | |-- inventoryName/ +| | | |-- namespace/ | | | | |-- inventory.yaml */ @@ -89,13 +89,12 @@ const ProjectRolesFilesDir = "files" // RuntimeDir is a fixed directory name under workdir, used to store the runtime data of the current task execution. const RuntimeDir = "runtime" -// namespace is the namespace for resource of Pipeline,Task,Config,Inventory. +// the resources dir store as etcd key. +// like: /prefix/group/version/resource/namespace/name // RuntimePipelineDir store Pipeline resources const RuntimePipelineDir = "pipelines" -// pipelineName is the name of Pipeline resource - // pipeline.yaml is the data of Pipeline resource // RuntimePipelineVariableDir is a fixed directory name under runtime, used to store the task execution parameters. @@ -104,25 +103,17 @@ const RuntimePipelineVariableDir = "variable" // RuntimePipelineVariableLocationFile is a location variable file under RuntimePipelineVariableDir const RuntimePipelineVariableLocationFile = "location.json" -// hostname.json is host variable file under RuntimePipelineVariableDir. Each host has a separate file. - // RuntimePipelineTaskDir is a fixed directory name under runtime, used to store the task execution status. const RuntimePipelineTaskDir = "tasks" -// taskName is the name of Task resource - // task.yaml is the data of Task resource // RuntimeConfigDir store Config resources const RuntimeConfigDir = "configs" -// configName is the name of Config resource - // config.yaml is the data of Config resource // RuntimeInventoryDir store Inventory resources const RuntimeInventoryDir = "inventories" -// inventoryName is the name of Inventory resource - // inventory.yaml is the data of Inventory resource diff --git a/pkg/controllers/pipeline_controller.go b/pkg/controllers/pipeline_controller.go index df0e3eaa..b5e4968b 100644 --- a/pkg/controllers/pipeline_controller.go +++ b/pkg/controllers/pipeline_controller.go @@ -20,24 +20,32 @@ import ( "context" "fmt" "os" - "path/filepath" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + ctrlfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer" kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" + kubekeyv1alpha1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1alpha1" _const "github.com/kubesphere/kubekey/v4/pkg/const" "github.com/kubesphere/kubekey/v4/pkg/task" ) +const ( + pipelineFinalizer = "kubekey.kubesphere.io/pipeline" +) + type PipelineReconciler struct { ctrlclient.Client record.EventRecorder TaskController task.Controller + + ctrlfinalizer.Finalizers } func (r PipelineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -56,30 +64,47 @@ func (r PipelineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct if pipeline.DeletionTimestamp != nil { klog.V(5).InfoS("pipeline is deleting", "pipeline", req.String()) + if controllerutil.ContainsFinalizer(pipeline, pipelineFinalizer) { + r.clean(ctx, pipeline) + // remove finalizer + + } + return ctrl.Result{}, nil } + if !controllerutil.ContainsFinalizer(pipeline, pipelineFinalizer) { + excepted := pipeline.DeepCopy() + controllerutil.AddFinalizer(pipeline, pipelineFinalizer) + if err := r.Client.Patch(ctx, pipeline, ctrlclient.MergeFrom(excepted)); err != nil { + klog.V(5).ErrorS(err, "update pipeline error", "pipeline", req.String()) + return ctrl.Result{}, err + } + } + switch pipeline.Status.Phase { case "": excepted := pipeline.DeepCopy() pipeline.Status.Phase = kubekeyv1.PipelinePhasePending if err := r.Client.Status().Patch(ctx, pipeline, ctrlclient.MergeFrom(excepted)); err != nil { - klog.ErrorS(err, "update pipeline error", "pipeline", req.String()) + klog.V(5).ErrorS(err, "update pipeline error", "pipeline", req.String()) return ctrl.Result{}, err } case kubekeyv1.PipelinePhasePending: excepted := pipeline.DeepCopy() pipeline.Status.Phase = kubekeyv1.PipelinePhaseRunning if err := r.Client.Status().Patch(ctx, pipeline, ctrlclient.MergeFrom(excepted)); err != nil { - klog.ErrorS(err, "update pipeline error", "pipeline", req.String()) + klog.V(5).ErrorS(err, "update pipeline error", "pipeline", req.String()) return ctrl.Result{}, err } case kubekeyv1.PipelinePhaseRunning: return r.dealRunningPipeline(ctx, pipeline) case kubekeyv1.PipelinePhaseFailed: - r.clean(ctx, pipeline) + // do nothing case kubekeyv1.PipelinePhaseSucceed: - r.clean(ctx, pipeline) + if !pipeline.Spec.Debug { + r.clean(ctx, pipeline) + } } return ctrl.Result{}, nil } @@ -95,14 +120,14 @@ func (r *PipelineReconciler) dealRunningPipeline(ctx context.Context, pipeline * defer func() { // update pipeline status if err := r.Client.Status().Patch(ctx, pipeline, ctrlclient.MergeFrom(cp)); err != nil { - klog.ErrorS(err, "update pipeline error", "pipeline", ctrlclient.ObjectKeyFromObject(pipeline)) + klog.V(5).ErrorS(err, "update pipeline error", "pipeline", ctrlclient.ObjectKeyFromObject(pipeline)) } }() if err := r.TaskController.AddTasks(ctx, task.AddTaskOptions{ Pipeline: pipeline, }); err != nil { - klog.ErrorS(err, "add task error", "pipeline", ctrlclient.ObjectKeyFromObject(pipeline)) + klog.V(5).ErrorS(err, "add task error", "pipeline", ctrlclient.ObjectKeyFromObject(pipeline)) pipeline.Status.Phase = kubekeyv1.PipelinePhaseFailed pipeline.Status.Reason = fmt.Sprintf("add task to controller failed: %v", err) return ctrl.Result{}, err @@ -113,12 +138,15 @@ func (r *PipelineReconciler) dealRunningPipeline(ctx context.Context, pipeline * // clean runtime directory func (r *PipelineReconciler) clean(ctx context.Context, pipeline *kubekeyv1.Pipeline) { - if !pipeline.Spec.Debug && pipeline.Status.Phase == kubekeyv1.PipelinePhaseSucceed { - klog.V(5).InfoS("clean runtimeDir", "pipeline", ctrlclient.ObjectKeyFromObject(pipeline)) - // clean runtime directory - if err := os.RemoveAll(filepath.Join(_const.GetWorkDir(), _const.RuntimeDir)); err != nil { - klog.ErrorS(err, "clean runtime directory error", "runtime dir", filepath.Join(_const.GetWorkDir(), _const.RuntimeDir), "pipeline", ctrlclient.ObjectKeyFromObject(pipeline)) - } + klog.V(5).InfoS("clean runtimeDir", "pipeline", ctrlclient.ObjectKeyFromObject(pipeline)) + // delete reference task + taskList := &kubekeyv1alpha1.TaskList{} + if err := r.Client.List(ctx, taskList, ctrlclient.MatchingFields{}); err != nil { + klog.V(5).ErrorS(err, "list task error", "pipeline", ctrlclient.ObjectKeyFromObject(pipeline)) + return + } + if err := os.RemoveAll(_const.GetRuntimeDir()); err != nil { + klog.V(5).ErrorS(err, "clean runtime directory error", "runtime dir", _const.GetRuntimeDir(), "pipeline", ctrlclient.ObjectKeyFromObject(pipeline)) } } diff --git a/pkg/controllers/task_controller.go b/pkg/controllers/task_controller.go index 4dfdcd7b..24c424f7 100644 --- a/pkg/controllers/task_controller.go +++ b/pkg/controllers/task_controller.go @@ -57,7 +57,7 @@ func (r *TaskReconciler) Reconcile(ctx context.Context, request ctrl.Request) (c // get task var task = &kubekeyv1alpha1.Task{} if err := r.Client.Get(ctx, request.NamespacedName, task); err != nil { - klog.ErrorS(err, "get task error", "task", request.String()) + klog.V(5).ErrorS(err, "get task error", "task", request.String()) return ctrl.Result{}, nil } @@ -76,7 +76,7 @@ func (r *TaskReconciler) Reconcile(ctx context.Context, request ctrl.Request) (c klog.V(5).InfoS("pipeline is deleted, skip", "task", request.String()) return ctrl.Result{}, nil } - klog.ErrorS(err, "get pipeline error", "task", request.String(), "pipeline", types.NamespacedName{Namespace: task.Namespace, Name: ref.Name}.String()) + klog.V(5).ErrorS(err, "get pipeline error", "task", request.String(), "pipeline", types.NamespacedName{Namespace: task.Namespace, Name: ref.Name}.String()) return ctrl.Result{}, err } break @@ -92,7 +92,7 @@ func (r *TaskReconciler) Reconcile(ctx context.Context, request ctrl.Request) (c var v variable.Variable vars, ok, err := r.VariableCache.GetByKey(string(pipeline.UID)) if err != nil { - klog.ErrorS(err, "get variable error", "task", request.String()) + klog.V(5).ErrorS(err, "get variable error", "task", request.String()) return ctrl.Result{}, err } if ok { @@ -104,40 +104,32 @@ func (r *TaskReconciler) Reconcile(ctx context.Context, request ctrl.Request) (c Pipeline: *pipeline, }) if err != nil { - klog.ErrorS(err, "create variable error", "task", request.String()) + klog.V(5).ErrorS(err, "create variable error", "task", request.String()) return ctrl.Result{}, err } if err := r.VariableCache.Add(nv); err != nil { - klog.ErrorS(err, "add variable to store error", "task", request.String()) + klog.V(5).ErrorS(err, "add variable to store error", "task", request.String()) return ctrl.Result{}, err } v = nv } defer func() { + if task.IsComplete() { + klog.Infof("[Task %s] is complete.Result is: %s", request.String(), task.Status.Phase) + } var nsTasks = &kubekeyv1alpha1.TaskList{} klog.V(5).InfoS("update pipeline status", "task", request.String(), "pipeline", ctrlclient.ObjectKeyFromObject(pipeline).String()) - if err := r.Client.List(ctx, nsTasks, ctrlclient.InNamespace(task.Namespace)); err != nil { - klog.ErrorS(err, "list task error", "task", request.String()) + if err := r.Client.List(ctx, nsTasks, ctrlclient.InNamespace(task.Namespace), ctrlclient.MatchingFields{ + "ownerReferences:pipeline": ctrlclient.ObjectKeyFromObject(pipeline).String(), + }); err != nil { + klog.V(5).ErrorS(err, "list task error", "task", request.String()) return } - // filter by ownerReference - for i := len(nsTasks.Items) - 1; i >= 0; i-- { - var hasOwner bool - for _, ref := range nsTasks.Items[i].OwnerReferences { - if ref.UID == pipeline.UID && ref.Kind == "Pipeline" { - hasOwner = true - } - } - - if !hasOwner { - nsTasks.Items = append(nsTasks.Items[:i], nsTasks.Items[i+1:]...) - } - } cp := pipeline.DeepCopy() converter.CalculatePipelineStatus(nsTasks, pipeline) if err := r.Client.Status().Patch(ctx, pipeline, ctrlclient.MergeFrom(cp)); err != nil { - klog.ErrorS(err, "update pipeline status error", "task", request.String(), "pipeline", ctrlclient.ObjectKeyFromObject(pipeline).String()) + klog.V(5).ErrorS(err, "update pipeline status error", "task", request.String(), "pipeline", ctrlclient.ObjectKeyFromObject(pipeline).String()) } }() @@ -146,8 +138,8 @@ func (r *TaskReconciler) Reconcile(ctx context.Context, request ctrl.Request) (c if task.Spec.Retries > task.Status.RestartCount { task.Status.Phase = kubekeyv1alpha1.TaskPhasePending task.Status.RestartCount++ - if err := r.Client.Update(ctx, task); err != nil { - klog.ErrorS(err, "update task error", "task", request.String()) + if err := r.Client.Status().Update(ctx, task); err != nil { + klog.V(5).ErrorS(err, "update task error", "task", request.String()) return ctrl.Result{}, err } } @@ -177,33 +169,22 @@ func (r *TaskReconciler) dealPendingTask(ctx context.Context, options taskReconc LocationUID: string(options.Task.UID), }) if err != nil { - klog.ErrorS(err, "find dependency error", "task", ctrlclient.ObjectKeyFromObject(options.Task).String()) + klog.V(5).ErrorS(err, "find dependency error", "task", ctrlclient.ObjectKeyFromObject(options.Task).String()) return ctrl.Result{}, err } dt, ok := dl.(variable.DependencyTask) if !ok { - klog.ErrorS(err, "failed to convert dependency", "task", ctrlclient.ObjectKeyFromObject(options.Task).String()) + klog.V(5).ErrorS(err, "failed to convert dependency", "task", ctrlclient.ObjectKeyFromObject(options.Task).String()) return ctrl.Result{}, fmt.Errorf("[Task %s] failed to convert dependency", ctrlclient.ObjectKeyFromObject(options.Task).String()) } var nsTasks = &kubekeyv1alpha1.TaskList{} - if err := r.Client.List(ctx, nsTasks, ctrlclient.InNamespace(options.Task.Namespace)); err != nil { - klog.ErrorS(err, "list task error", "task", ctrlclient.ObjectKeyFromObject(options.Task).String(), err) + if err := r.Client.List(ctx, nsTasks, ctrlclient.InNamespace(options.Task.Namespace), ctrlclient.MatchingFields{ + "ownerReferences:pipeline": ctrlclient.ObjectKeyFromObject(options.Pipeline).String(), + }); err != nil { + klog.V(5).ErrorS(err, "list task error", "task", ctrlclient.ObjectKeyFromObject(options.Task).String(), err) return ctrl.Result{}, err } - // filter by ownerReference - for i := len(nsTasks.Items) - 1; i >= 0; i-- { - var hasOwner bool - for _, ref := range nsTasks.Items[i].OwnerReferences { - if ref.UID == options.Pipeline.UID && ref.Kind == "Pipeline" { - hasOwner = true - } - } - - if !hasOwner { - nsTasks.Items = append(nsTasks.Items[:i], nsTasks.Items[i+1:]...) - } - } var dts []kubekeyv1alpha1.Task for _, t := range nsTasks.Items { if slices.Contains(dt.Tasks, string(t.UID)) { @@ -217,14 +198,14 @@ func (r *TaskReconciler) dealPendingTask(ctx context.Context, options taskReconc case kubekeyv1alpha1.TaskPhaseRunning: // update task phase to running options.Task.Status.Phase = kubekeyv1alpha1.TaskPhaseRunning - if err := r.Client.Update(ctx, options.Task); err != nil { - klog.ErrorS(err, "update task to Running error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) + if err := r.Client.Status().Update(ctx, options.Task); err != nil { + klog.V(5).ErrorS(err, "update task to Running error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) } return ctrl.Result{Requeue: true}, nil case kubekeyv1alpha1.TaskPhaseSkipped: options.Task.Status.Phase = kubekeyv1alpha1.TaskPhaseSkipped - if err := r.Client.Update(ctx, options.Task); err != nil { - klog.ErrorS(err, "update task to Skipped error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) + if err := r.Client.Status().Update(ctx, options.Task); err != nil { + klog.V(5).ErrorS(err, "update task to Skipped error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) } return ctrl.Result{}, nil default: @@ -234,11 +215,8 @@ func (r *TaskReconciler) dealPendingTask(ctx context.Context, options taskReconc func (r *TaskReconciler) dealRunningTask(ctx context.Context, options taskReconcileOptions) (ctrl.Result, error) { // find task in location - klog.InfoS("dealRunningTask begin", "task", ctrlclient.ObjectKeyFromObject(options.Task)) - defer klog.Info("dealRunningTask end, task phase", "task", ctrlclient.ObjectKeyFromObject(options.Task), "phase", options.Task.Status.Phase) - if err := r.executeTask(ctx, options); err != nil { - klog.ErrorS(err, "execute task error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) + klog.V(5).ErrorS(err, "execute task error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) return ctrl.Result{}, nil } return ctrl.Result{}, nil @@ -251,8 +229,8 @@ func (r *TaskReconciler) executeTask(ctx context.Context, options taskReconcileO defer func() { cd.EndTimestamp = metav1.Now() options.Task.Status.Conditions = append(options.Task.Status.Conditions, cd) - if err := r.Client.Update(ctx, options.Task); err != nil { - klog.ErrorS(err, "update task status error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) + if err := r.Client.Status().Update(ctx, options.Task); err != nil { + klog.V(5).ErrorS(err, "update task status error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) } }() @@ -276,7 +254,7 @@ func (r *TaskReconciler) executeTask(ctx context.Context, options taskReconcileO if options.Task.Spec.Register != "" { puid, err := options.Variable.Get(variable.ParentLocation{LocationUID: string(options.Task.UID)}) if err != nil { - klog.ErrorS(err, "get location error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) + klog.V(5).ErrorS(err, "get location error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) return } // set variable to parent location @@ -290,7 +268,7 @@ func (r *TaskReconciler) executeTask(ctx context.Context, options taskReconcileO }, }, }); err != nil { - klog.ErrorS(err, "register task result to variable error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) + klog.V(5).ErrorS(err, "register task result to variable error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) return } } @@ -301,7 +279,7 @@ func (r *TaskReconciler) executeTask(ctx context.Context, options taskReconcileO LocationUID: string(options.Task.UID), }) if err != nil { - klog.ErrorS(err, "get location variable error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) + klog.V(5).ErrorS(err, "get location variable error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) stderr = err.Error() return } @@ -309,7 +287,7 @@ func (r *TaskReconciler) executeTask(ctx context.Context, options taskReconcileO if len(options.Task.Spec.When) > 0 { ok, err := tmpl.ParseBool(lg.(variable.VariableData), options.Task.Spec.When) if err != nil { - klog.ErrorS(err, "parse when condition error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) + klog.V(5).ErrorS(err, "parse when condition error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) stderr = err.Error() return } @@ -334,7 +312,7 @@ func (r *TaskReconciler) executeTask(ctx context.Context, options taskReconcileO case string: item, err = tmpl.ParseString(lg.(variable.VariableData), item.(string)) if err != nil { - klog.ErrorS(err, "parse loop vars error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) + klog.V(5).ErrorS(err, "parse loop vars error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) stderr = err.Error() return } @@ -342,7 +320,7 @@ func (r *TaskReconciler) executeTask(ctx context.Context, options taskReconcileO for k, v := range item.(variable.VariableData) { sv, err := tmpl.ParseString(lg.(variable.VariableData), v.(string)) if err != nil { - klog.ErrorS(err, "parse loop vars error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) + klog.V(5).ErrorS(err, "parse loop vars error", "task", ctrlclient.ObjectKeyFromObject(options.Task)) stderr = err.Error() return } @@ -402,7 +380,7 @@ func (r *TaskReconciler) executeModule(ctx context.Context, task *kubekeyv1alpha LocationUID: string(task.UID), }) if err != nil { - klog.ErrorS(err, "get location variable error", "task", ctrlclient.ObjectKeyFromObject(task)) + klog.V(5).ErrorS(err, "get location variable error", "task", ctrlclient.ObjectKeyFromObject(task)) return "", err.Error() } @@ -410,7 +388,7 @@ func (r *TaskReconciler) executeModule(ctx context.Context, task *kubekeyv1alpha if len(task.Spec.FailedWhen) > 0 { ok, err := tmpl.ParseBool(lg.(variable.VariableData), task.Spec.FailedWhen) if err != nil { - klog.ErrorS(err, "validate FailedWhen condition error", "task", ctrlclient.ObjectKeyFromObject(task)) + klog.V(5).ErrorS(err, "validate FailedWhen condition error", "task", ctrlclient.ObjectKeyFromObject(task)) return "", err.Error() } if ok { diff --git a/pkg/converter/converter.go b/pkg/converter/converter.go index a41d6ee8..9e757cb5 100644 --- a/pkg/converter/converter.go +++ b/pkg/converter/converter.go @@ -28,12 +28,8 @@ import ( "gopkg.in/yaml.v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/json" - "k8s.io/apimachinery/pkg/util/rand" "k8s.io/klog/v2" - "k8s.io/utils/pointer" - ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" kkcorev1 "github.com/kubesphere/kubekey/v4/pkg/apis/core/v1" kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" @@ -48,23 +44,23 @@ func MarshalPlaybook(baseFS fs.FS, pbPath string) (*kkcorev1.Playbook, error) { // convert playbook to kkcorev1.Playbook pb := &kkcorev1.Playbook{} if err := loadPlaybook(baseFS, pbPath, pb); err != nil { - klog.ErrorS(err, "Load playbook failed", "playbook", pbPath) + klog.V(4).ErrorS(err, "Load playbook failed", "playbook", pbPath) return nil, err } // convertRoles if err := convertRoles(baseFS, pbPath, pb); err != nil { - klog.ErrorS(err, "ConvertRoles error", "playbook", pbPath) + klog.V(4).ErrorS(err, "ConvertRoles error", "playbook", pbPath) return nil, err } if err := convertIncludeTasks(baseFS, pbPath, pb); err != nil { - klog.ErrorS(err, "ConvertIncludeTasks error", "playbook", pbPath) + klog.V(4).ErrorS(err, "ConvertIncludeTasks error", "playbook", pbPath) return nil, err } if err := pb.Validate(); err != nil { - klog.ErrorS(err, "Validate playbook failed", "playbook", pbPath) + klog.V(4).ErrorS(err, "Validate playbook failed", "playbook", pbPath) return nil, err } return pb, nil @@ -75,12 +71,12 @@ func loadPlaybook(baseFS fs.FS, pbPath string, pb *kkcorev1.Playbook) error { // baseDir is the local ansible project dir which playbook belong to pbData, err := fs.ReadFile(baseFS, pbPath) if err != nil { - klog.ErrorS(err, "Read playbook failed", "playbook", pbPath) + klog.V(4).ErrorS(err, "Read playbook failed", "playbook", pbPath) return err } var plays []kkcorev1.Play if err := yaml.Unmarshal(pbData, &plays); err != nil { - klog.ErrorS(err, "Unmarshal playbook failed", "playbook", pbPath) + klog.V(4).ErrorS(err, "Unmarshal playbook failed", "playbook", pbPath) return err } @@ -108,12 +104,12 @@ func loadPlaybook(baseFS fs.FS, pbPath string, pb *kkcorev1.Playbook) error { rdata, err := fs.ReadFile(baseFS, mainTask) if err != nil { - klog.ErrorS(err, "Read role failed", "playbook", pbPath, "role", r.Role) + klog.V(4).ErrorS(err, "Read role failed", "playbook", pbPath, "role", r.Role) return err } var blocks []kkcorev1.Block if err := yaml.Unmarshal(rdata, &blocks); err != nil { - klog.ErrorS(err, "Unmarshal role failed", "playbook", pbPath, "role", r.Role) + klog.V(4).ErrorS(err, "Unmarshal role failed", "playbook", pbPath, "role", r.Role) return err } p.Roles[i].Block = blocks @@ -141,12 +137,12 @@ func convertRoles(baseFS fs.FS, pbPath string, pb *kkcorev1.Playbook) error { rdata, err := fs.ReadFile(baseFS, mainTask) if err != nil { - klog.ErrorS(err, "Read role failed", "playbook", pbPath, "role", r.Role) + klog.V(4).ErrorS(err, "Read role failed", "playbook", pbPath, "role", r.Role) return err } var blocks []kkcorev1.Block if err := yaml.Unmarshal(rdata, &blocks); err != nil { - klog.ErrorS(err, "Unmarshal role failed", "playbook", pbPath, "role", r.Role) + klog.V(4).ErrorS(err, "Unmarshal role failed", "playbook", pbPath, "role", r.Role) return err } p.Roles[i].Block = blocks @@ -156,12 +152,12 @@ func convertRoles(baseFS fs.FS, pbPath string, pb *kkcorev1.Playbook) error { if mainDefault != "" { mainData, err := fs.ReadFile(baseFS, mainDefault) if err != nil { - klog.ErrorS(err, "Read defaults variable for role error", "playbook", pbPath, "role", r.Role) + klog.V(4).ErrorS(err, "Read defaults variable for role error", "playbook", pbPath, "role", r.Role) return err } var vars variable.VariableData if err := yaml.Unmarshal(mainData, &vars); err != nil { - klog.ErrorS(err, "Unmarshal defaults variable for role error", "playbook", pbPath, "role", r.Role) + klog.V(4).ErrorS(err, "Unmarshal defaults variable for role error", "playbook", pbPath, "role", r.Role) return err } p.Roles[i].Vars = vars @@ -177,22 +173,22 @@ func convertIncludeTasks(baseFS fs.FS, pbPath string, pb *kkcorev1.Playbook) err var pbBase = filepath.Dir(filepath.Dir(pbPath)) for _, play := range pb.Play { if err := fileToBlock(baseFS, pbBase, play.PreTasks); err != nil { - klog.ErrorS(err, "Convert pre_tasks error", "playbook", pbPath) + klog.V(4).ErrorS(err, "Convert pre_tasks error", "playbook", pbPath) return err } if err := fileToBlock(baseFS, pbBase, play.Tasks); err != nil { - klog.ErrorS(err, "Convert tasks error", "playbook", pbPath) + klog.V(4).ErrorS(err, "Convert tasks error", "playbook", pbPath) return err } if err := fileToBlock(baseFS, pbBase, play.PostTasks); err != nil { - klog.ErrorS(err, "Convert post_tasks error", "playbook", pbPath) + klog.V(4).ErrorS(err, "Convert post_tasks error", "playbook", pbPath) return err } for _, r := range play.Roles { roleBase := project.GetRoleBaseFromPlaybook(baseFS, pbPath, r.Role) if err := fileToBlock(baseFS, filepath.Join(roleBase, _const.ProjectRolesTasksDir), r.Block); err != nil { - klog.ErrorS(err, "Convert role error", "playbook", pbPath, "role", r.Role) + klog.V(4).ErrorS(err, "Convert role error", "playbook", pbPath, "role", r.Role) return err } } @@ -205,27 +201,27 @@ func fileToBlock(baseFS fs.FS, baseDir string, blocks []kkcorev1.Block) error { if b.IncludeTasks != "" { data, err := fs.ReadFile(baseFS, filepath.Join(baseDir, b.IncludeTasks)) if err != nil { - klog.ErrorS(err, "Read includeTask file error", "name", b.Name, "file_path", filepath.Join(baseDir, b.IncludeTasks)) + klog.V(4).ErrorS(err, "Read includeTask file error", "name", b.Name, "file_path", filepath.Join(baseDir, b.IncludeTasks)) return err } var bs []kkcorev1.Block if err := yaml.Unmarshal(data, &bs); err != nil { - klog.ErrorS(err, "Unmarshal includeTask data error", "name", b.Name, "file_path", filepath.Join(baseDir, b.IncludeTasks)) + klog.V(4).ErrorS(err, "Unmarshal includeTask data error", "name", b.Name, "file_path", filepath.Join(baseDir, b.IncludeTasks)) return err } b.Block = bs blocks[i] = b } if err := fileToBlock(baseFS, baseDir, b.Block); err != nil { - klog.ErrorS(err, "Convert block error", "name", b.Name) + klog.V(4).ErrorS(err, "Convert block error", "name", b.Name) return err } if err := fileToBlock(baseFS, baseDir, b.Rescue); err != nil { - klog.ErrorS(err, "Convert rescue error", "name", b.Name) + klog.V(4).ErrorS(err, "Convert rescue error", "name", b.Name) return err } if err := fileToBlock(baseFS, baseDir, b.Always); err != nil { - klog.ErrorS(err, "Convert always error", "name", b.Name) + klog.V(4).ErrorS(err, "Convert always error", "name", b.Name) return err } } @@ -233,7 +229,7 @@ func fileToBlock(baseFS fs.FS, baseDir string, blocks []kkcorev1.Block) error { } // MarshalBlock marshal block to task -func MarshalBlock(ctx context.Context, block kkcorev1.Block, owner ctrlclient.Object) *kubekeyv1alpha1.Task { +func MarshalBlock(ctx context.Context, block kkcorev1.Block) *kubekeyv1alpha1.Task { var role string if v := ctx.Value(_const.CtxBlockRole); v != nil { role = v.(string) @@ -242,38 +238,20 @@ func MarshalBlock(ctx context.Context, block kkcorev1.Block, owner ctrlclient.Ob if block.RunOnce { // if run_once. execute on the first task hosts = hosts[:1] } - var uid string - if v := ctx.Value(_const.CtxBlockTaskUID); v != nil { - uid = v.(string) - } var when []string if v := ctx.Value(_const.CtxBlockWhen); v != nil { when = v.([]string) } - task := &kubekeyv1alpha1.Task{ TypeMeta: metav1.TypeMeta{ Kind: "Task", APIVersion: kubekeyv1alpha1.SchemeGroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-%s", owner.GetName(), rand.String(12)), - Namespace: owner.GetNamespace(), - UID: types.UID(uid), CreationTimestamp: metav1.Now(), Annotations: map[string]string{ kubekeyv1alpha1.TaskAnnotationRole: role, }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: owner.GetObjectKind().GroupVersionKind().GroupVersion().String(), - Kind: owner.GetObjectKind().GroupVersionKind().Kind, - Name: owner.GetName(), - UID: owner.GetUID(), - Controller: pointer.Bool(true), - BlockOwnerDeletion: pointer.Bool(true), - }, - }, }, Spec: kubekeyv1alpha1.KubeKeyTaskSpec{ Name: block.Name, @@ -284,14 +262,11 @@ func MarshalBlock(ctx context.Context, block kkcorev1.Block, owner ctrlclient.Ob FailedWhen: block.FailedWhen.Data, Register: block.Register, }, - Status: kubekeyv1alpha1.TaskStatus{ - Phase: kubekeyv1alpha1.TaskPhasePending, - }, } if len(block.Loop) != 0 { data, err := json.Marshal(block.Loop) if err != nil { - klog.ErrorS(err, "Marshal loop failed", "task", task.Name, "block", block.Name) + klog.V(4).ErrorS(err, "Marshal loop failed", "task", task.Name, "block", block.Name) } task.Spec.Loop = runtime.RawExtension{Raw: data} } @@ -319,7 +294,7 @@ func GroupHostBySerial(hosts []string, serial []any) ([][]string, error) { if strings.HasSuffix(a.(string), "%") { b, err := strconv.Atoi(strings.TrimSuffix(a.(string), "%")) if err != nil { - klog.ErrorS(err, "Convert serial to int failed", "serial", a.(string)) + klog.V(4).ErrorS(err, "Convert serial to int failed", "serial", a.(string)) return nil, err } if sp+int(math.Ceil(float64(len(hosts)*b)/100.0)) > len(hosts)-1 { @@ -331,7 +306,7 @@ func GroupHostBySerial(hosts []string, serial []any) ([][]string, error) { } else { b, err := strconv.Atoi(a.(string)) if err != nil { - klog.ErrorS(err, "Convert serial to int failed", "serial", a.(string)) + klog.V(4).ErrorS(err, "Convert serial to int failed", "serial", a.(string)) return nil, err } if sp+b > len(hosts)-1 { @@ -361,7 +336,7 @@ func GroupHostBySerial(hosts []string, serial []any) ([][]string, error) { if strings.HasSuffix(a.(string), "%") { b, err := strconv.Atoi(strings.TrimSuffix(a.(string), "%")) if err != nil { - klog.ErrorS(err, "Convert serial to int failed", "serial", a.(string)) + klog.V(4).ErrorS(err, "Convert serial to int failed", "serial", a.(string)) return nil, err } if sp+int(math.Ceil(float64(len(hosts)*b)/100.0)) > len(hosts)-1 { @@ -373,7 +348,7 @@ func GroupHostBySerial(hosts []string, serial []any) ([][]string, error) { } else { b, err := strconv.Atoi(a.(string)) if err != nil { - klog.ErrorS(err, "Convert serial to int failed", "serial", a.(string)) + klog.V(4).ErrorS(err, "Convert serial to int failed", "serial", a.(string)) return nil, err } if sp+b > len(hosts)-1 { diff --git a/pkg/converter/tmpl/template.go b/pkg/converter/tmpl/template.go index 636be844..e267b5ca 100644 --- a/pkg/converter/tmpl/template.go +++ b/pkg/converter/tmpl/template.go @@ -32,12 +32,12 @@ func ParseBool(v variable.VariableData, inputs []string) (bool, error) { // first convert. intql, err := pongo2.FromString(input) if err != nil { - klog.ErrorS(err, "Failed to get string") + klog.V(4).ErrorS(err, "Failed to get string") return false, err } inres, err := intql.Execute(pongo2.Context(v)) if err != nil { - klog.ErrorS(err, "Failed to execute string") + klog.V(4).ErrorS(err, "Failed to execute string") return false, err } @@ -48,12 +48,12 @@ func ParseBool(v variable.VariableData, inputs []string) (bool, error) { // second convert. tql, err := pongo2.FromString(inres) if err != nil { - klog.ErrorS(err, "failed to get string") + klog.V(4).ErrorS(err, "failed to get string") return false, err } result, err := tql.Execute(pongo2.Context(v)) if err != nil { - klog.ErrorS(err, "failed to execute string") + klog.V(4).ErrorS(err, "failed to execute string") return false, err } klog.V(4).InfoS(" parse template succeed", "result", result) @@ -68,12 +68,12 @@ func ParseBool(v variable.VariableData, inputs []string) (bool, error) { func ParseString(v variable.VariableData, input string) (string, error) { tql, err := pongo2.FromString(input) if err != nil { - klog.ErrorS(err, "Failed to get string") + klog.V(4).ErrorS(err, "Failed to get string") return input, err } result, err := tql.Execute(pongo2.Context(v)) if err != nil { - klog.ErrorS(err, "Failed to execute string") + klog.V(4).ErrorS(err, "Failed to execute string") return input, err } klog.V(4).InfoS(" parse template succeed", "result", result) @@ -83,12 +83,12 @@ func ParseString(v variable.VariableData, input string) (string, error) { func ParseFile(v variable.VariableData, file []byte) (string, error) { tql, err := pongo2.FromBytes(file) if err != nil { - klog.ErrorS(err, "Transfer file to template error") + klog.V(4).ErrorS(err, "Transfer file to template error") return "", err } result, err := tql.Execute(pongo2.Context(v)) if err != nil { - klog.ErrorS(err, "exec template error") + klog.V(4).ErrorS(err, "exec template error") return "", err } klog.V(4).InfoS(" parse template succeed", "result", result) diff --git a/pkg/manager/command_manager.go b/pkg/manager/command_manager.go index 8d02dccd..b286a59a 100644 --- a/pkg/manager/command_manager.go +++ b/pkg/manager/command_manager.go @@ -20,10 +20,10 @@ import ( "context" "fmt" "os" - "path/filepath" "syscall" "time" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/klog/v2" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" @@ -41,13 +41,11 @@ type commandManager struct { *kubekeyv1.Inventory ctrlclient.Client + *runtime.Scheme } func (m *commandManager) Run(ctx context.Context) error { // create config, inventory and pipeline - klog.Infof("[Pipeline %s] start", ctrlclient.ObjectKeyFromObject(m.Pipeline)) - defer klog.Infof("[Pipeline %s] finish", ctrlclient.ObjectKeyFromObject(m.Pipeline)) - if err := m.Client.Create(ctx, m.Config); err != nil { klog.ErrorS(err, "Create config error", "pipeline", ctrlclient.ObjectKeyFromObject(m.Pipeline)) return err @@ -60,24 +58,30 @@ func (m *commandManager) Run(ctx context.Context) error { klog.ErrorS(err, "Create pipeline error", "pipeline", ctrlclient.ObjectKeyFromObject(m.Pipeline)) return err } - + klog.Infof("[Pipeline %s] start", ctrlclient.ObjectKeyFromObject(m.Pipeline)) defer func() { + klog.Infof("[Pipeline %s] finish", ctrlclient.ObjectKeyFromObject(m.Pipeline)) // update pipeline status - if err := m.Client.Update(ctx, m.Pipeline); err != nil { + if err := m.Client.Status().Update(ctx, m.Pipeline); err != nil { klog.ErrorS(err, "Update pipeline error", "pipeline", ctrlclient.ObjectKeyFromObject(m.Pipeline)) } if !m.Pipeline.Spec.Debug && m.Pipeline.Status.Phase == kubekeyv1.PipelinePhaseSucceed { klog.Infof("[Pipeline %s] clean runtime directory", ctrlclient.ObjectKeyFromObject(m.Pipeline)) // clean runtime directory - if err := os.RemoveAll(filepath.Join(_const.GetWorkDir(), _const.RuntimeDir)); err != nil { - klog.ErrorS(err, "Clean runtime directory error", "pipeline", ctrlclient.ObjectKeyFromObject(m.Pipeline), "runtime_dir", filepath.Join(_const.GetWorkDir(), _const.RuntimeDir)) + if err := os.RemoveAll(_const.GetRuntimeDir()); err != nil { + klog.ErrorS(err, "Clean runtime directory error", "pipeline", ctrlclient.ObjectKeyFromObject(m.Pipeline), "runtime_dir", _const.GetRuntimeDir()) } } + // kill by signal + if err := syscall.Kill(os.Getpid(), syscall.SIGTERM); err != nil { + klog.ErrorS(err, "Kill process error", "pipeline", ctrlclient.ObjectKeyFromObject(m.Pipeline)) + } }() klog.Infof("[Pipeline %s] start task controller", ctrlclient.ObjectKeyFromObject(m.Pipeline)) kd, err := task.NewController(task.ControllerOptions{ + Scheme: m.Scheme, VariableCache: variable.Cache, Client: m.Client, TaskReconciler: &controllers.TaskReconciler{ @@ -102,7 +106,7 @@ func (m *commandManager) Run(ctx context.Context) error { return err } // update pipeline status - if err := m.Client.Update(ctx, m.Pipeline); err != nil { + if err := m.Client.Status().Update(ctx, m.Pipeline); err != nil { klog.ErrorS(err, "Update pipeline error", "pipeline", ctrlclient.ObjectKeyFromObject(m.Pipeline)) return err } @@ -119,11 +123,6 @@ func (m *commandManager) Run(ctx context.Context) error { } return false, nil }) - // kill by signal - if err := syscall.Kill(os.Getpid(), syscall.SIGTERM); err != nil { - klog.ErrorS(err, "Kill process error", "pipeline", ctrlclient.ObjectKeyFromObject(m.Pipeline)) - return err - } return nil } diff --git a/pkg/manager/controller_manager.go b/pkg/manager/controller_manager.go index 6ab7abef..b1369345 100644 --- a/pkg/manager/controller_manager.go +++ b/pkg/manager/controller_manager.go @@ -19,15 +19,12 @@ package manager import ( "context" - "k8s.io/apimachinery/pkg/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/config" ctrlcontroller "sigs.k8s.io/controller-runtime/pkg/controller" - ctrlmanager "sigs.k8s.io/controller-runtime/pkg/manager" - kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" + _const "github.com/kubesphere/kubekey/v4/pkg/const" "github.com/kubesphere/kubekey/v4/pkg/controllers" "github.com/kubesphere/kubekey/v4/pkg/proxy" "github.com/kubesphere/kubekey/v4/pkg/task" @@ -42,21 +39,16 @@ type controllerManager struct { func (c controllerManager) Run(ctx context.Context) error { ctrl.SetLogger(klog.NewKlogr()) - scheme := runtime.NewScheme() - // add default scheme - if err := clientgoscheme.AddToScheme(scheme); err != nil { - klog.ErrorS(err, "Add default scheme error") - return err - } - // add kubekey scheme, - // exclude task resource,Because will manager in local - if err := kubekeyv1.AddToScheme(scheme); err != nil { - klog.ErrorS(err, "Add kk scheme error") - return err - } - mgr, err := ctrl.NewManager(config.GetConfigOrDie(), ctrlmanager.Options{ - Scheme: scheme, + restconfig := config.GetConfigOrDie() + proxyTransport, err := proxy.NewProxyTransport(false) + if err != nil { + klog.ErrorS(err, "Create proxy transport error") + return err + } + restconfig.Transport = proxyTransport + mgr, err := ctrl.NewManager(restconfig, ctrl.Options{ + Scheme: _const.Scheme, LeaderElection: c.LeaderElection, LeaderElectionID: "controller-leader-election-kk", }) @@ -66,11 +58,12 @@ func (c controllerManager) Run(ctx context.Context) error { } taskController, err := task.NewController(task.ControllerOptions{ + Scheme: mgr.GetScheme(), VariableCache: variable.Cache, MaxConcurrent: c.MaxConcurrentReconciles, - Client: proxy.NewDelegatingClient(mgr.GetClient()), + Client: mgr.GetClient(), TaskReconciler: &controllers.TaskReconciler{ - Client: proxy.NewDelegatingClient(mgr.GetClient()), + Client: mgr.GetClient(), VariableCache: variable.Cache, }, }) @@ -86,7 +79,7 @@ func (c controllerManager) Run(ctx context.Context) error { } if err := (&controllers.PipelineReconciler{ - Client: proxy.NewDelegatingClient(mgr.GetClient()), + Client: mgr.GetClient(), EventRecorder: mgr.GetEventRecorderFor("pipeline"), TaskController: taskController, }).SetupWithManager(ctx, mgr, controllers.Options{ diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 6caa0040..70f8719d 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -19,7 +19,10 @@ package manager import ( "context" + "k8s.io/klog/v2" + kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" + _const "github.com/kubesphere/kubekey/v4/pkg/const" "github.com/kubesphere/kubekey/v4/pkg/proxy" ) @@ -35,13 +38,19 @@ type CommandManagerOptions struct { *kubekeyv1.Inventory } -func NewCommandManager(o CommandManagerOptions) Manager { +func NewCommandManager(o CommandManagerOptions) (Manager, error) { + client, err := proxy.NewLocalClient() + if err != nil { + klog.V(4).ErrorS(err, "Failed to create local client") + return nil, err + } return &commandManager{ Pipeline: o.Pipeline, Config: o.Config, Inventory: o.Inventory, - Client: proxy.NewDelegatingClient(nil), - } + Client: client, + Scheme: _const.Scheme, + }, nil } type ControllerManagerOptions struct { diff --git a/pkg/modules/command.go b/pkg/modules/command.go index a6c37dee..d83ee234 100644 --- a/pkg/modules/command.go +++ b/pkg/modules/command.go @@ -36,7 +36,7 @@ func ModuleCommand(ctx context.Context, options ExecOptions) (string, string) { conn = connector.NewConnector(options.Host, ha.(variable.VariableData)) } if err := conn.Init(ctx); err != nil { - klog.ErrorS(err, "failed to init connector") + klog.V(4).ErrorS(err, "failed to init connector") return "", err.Error() } defer conn.Close(ctx) diff --git a/pkg/modules/copy.go b/pkg/modules/copy.go index 494e93ff..43d7eeeb 100644 --- a/pkg/modules/copy.go +++ b/pkg/modules/copy.go @@ -49,12 +49,12 @@ func ModuleCopy(ctx context.Context, options ExecOptions) (string, string) { LocationUID: string(options.Task.UID), }) if err != nil { - klog.ErrorS(err, "failed to get location vars") + klog.V(4).ErrorS(err, "failed to get location vars") return "", err.Error() } destStr, err := tmpl.ParseString(lv.(variable.VariableData), *dest) if err != nil { - klog.ErrorS(err, "template parse dest error") + klog.V(4).ErrorS(err, "template parse dest error") return "", err.Error() } @@ -65,13 +65,13 @@ func ModuleCopy(ctx context.Context, options ExecOptions) (string, string) { // get connector ha, err := options.Variable.Get(variable.HostVars{HostName: options.Host}) if err != nil { - klog.ErrorS(err, "failed to get host vars") + klog.V(4).ErrorS(err, "failed to get host vars") return "", err.Error() } conn = connector.NewConnector(options.Host, ha.(variable.VariableData)) } if err := conn.Init(ctx); err != nil { - klog.ErrorS(err, "failed to init connector") + klog.V(4).ErrorS(err, "failed to init connector") return "", err.Error() } defer conn.Close(ctx) @@ -80,7 +80,7 @@ func ModuleCopy(ctx context.Context, options ExecOptions) (string, string) { // convert src srcStr, err := tmpl.ParseString(lv.(variable.VariableData), *src) if err != nil { - klog.ErrorS(err, "template parse src error") + klog.V(4).ErrorS(err, "template parse src error") return "", err.Error() } var baseFS fs.FS @@ -89,7 +89,7 @@ func ModuleCopy(ctx context.Context, options ExecOptions) (string, string) { } else { projectFs, err := project.New(project.Options{Pipeline: &options.Pipeline}).FS(ctx, false) if err != nil { - klog.ErrorS(err, "failed to get project fs") + klog.V(4).ErrorS(err, "failed to get project fs") return "", err.Error() } baseFS = projectFs @@ -98,19 +98,19 @@ func ModuleCopy(ctx context.Context, options ExecOptions) (string, string) { flPath := project.GetFilesFromPlayBook(baseFS, options.Pipeline.Spec.Playbook, roleName, srcStr) fileInfo, err := fs.Stat(baseFS, flPath) if err != nil { - klog.ErrorS(err, "failed to get src file in local") + klog.V(4).ErrorS(err, "failed to get src file in local") return "", err.Error() } if fileInfo.IsDir() { // src is dir if err := fs.WalkDir(baseFS, flPath, func(path string, info fs.DirEntry, err error) error { if err != nil { - klog.ErrorS(err, "failed to walk dir") + klog.V(4).ErrorS(err, "failed to walk dir") return err } rel, err := filepath.Rel(srcStr, path) if err != nil { - klog.ErrorS(err, "failed to get relative path") + klog.V(4).ErrorS(err, "failed to get relative path") return err } if info.IsDir() { @@ -118,7 +118,7 @@ func ModuleCopy(ctx context.Context, options ExecOptions) (string, string) { } fi, err := info.Info() if err != nil { - klog.ErrorS(err, "failed to get file info") + klog.V(4).ErrorS(err, "failed to get file info") return err } mode := fi.Mode() @@ -127,30 +127,30 @@ func ModuleCopy(ctx context.Context, options ExecOptions) (string, string) { } data, err := fs.ReadFile(baseFS, rel) if err != nil { - klog.ErrorS(err, "failed to read file") + klog.V(4).ErrorS(err, "failed to read file") return err } if err := conn.CopyFile(ctx, data, filepath.Join(destStr, rel), mode); err != nil { - klog.ErrorS(err, "failed to copy file", "src", srcStr, "dest", destStr) + klog.V(4).ErrorS(err, "failed to copy file", "src", srcStr, "dest", destStr) return err } return nil }); err != nil { - klog.ErrorS(err, "failed to walk dir") + klog.V(4).ErrorS(err, "failed to walk dir") return "", err.Error() } } else { // src is file data, err := fs.ReadFile(baseFS, flPath) if err != nil { - klog.ErrorS(err, "failed to read file") + klog.V(4).ErrorS(err, "failed to read file") return "", err.Error() } if strings.HasSuffix(destStr, "/") { destStr = destStr + filepath.Base(srcStr) } if err := conn.CopyFile(ctx, data, destStr, fileInfo.Mode()); err != nil { - klog.ErrorS(err, "failed to copy file", "src", srcStr, "dest", destStr) + klog.V(4).ErrorS(err, "failed to copy file", "src", srcStr, "dest", destStr) return "", err.Error() } return "success", "" diff --git a/pkg/modules/debug.go b/pkg/modules/debug.go index bc2a0ad0..8699a73f 100644 --- a/pkg/modules/debug.go +++ b/pkg/modules/debug.go @@ -34,12 +34,12 @@ func ModuleDebug(ctx context.Context, options ExecOptions) (string, string) { LocationUID: string(options.Task.UID), }) if err != nil { - klog.ErrorS(err, "Failed to get location vars") + klog.V(4).ErrorS(err, "Failed to get location vars") return "", err.Error() } result, err := tmpl.ParseString(lg.(variable.VariableData), fmt.Sprintf("{{ %s }}", *v)) if err != nil { - klog.ErrorS(err, "Failed to get var") + klog.V(4).ErrorS(err, "Failed to get var") return "", err.Error() } return result, "" @@ -51,12 +51,12 @@ func ModuleDebug(ctx context.Context, options ExecOptions) (string, string) { LocationUID: string(options.Task.UID), }) if err != nil { - klog.ErrorS(err, "Failed to get location vars") + klog.V(4).ErrorS(err, "Failed to get location vars") return "", err.Error() } result, err := tmpl.ParseString(lg.(variable.VariableData), *v) if err != nil { - klog.ErrorS(err, "Failed to get var") + klog.V(4).ErrorS(err, "Failed to get var") return "", err.Error() } return result, "" diff --git a/pkg/modules/set_fact.go b/pkg/modules/set_fact.go index 8087f3ce..e645b173 100644 --- a/pkg/modules/set_fact.go +++ b/pkg/modules/set_fact.go @@ -32,7 +32,7 @@ func ModuleSetFact(ctx context.Context, options ExecOptions) (string, string) { LocationUID: string(options.Task.UID), }) if err != nil { - klog.ErrorS(err, "failed to get location vars") + klog.V(4).ErrorS(err, "failed to get location vars") return "", err.Error() } @@ -42,7 +42,7 @@ func ModuleSetFact(ctx context.Context, options ExecOptions) (string, string) { case string: factVars[k], err = tmpl.ParseString(lv.(variable.VariableData), v.(string)) if err != nil { - klog.ErrorS(err, "template parse error", "input", v) + klog.V(4).ErrorS(err, "template parse error", "input", v) return "", err.Error() } default: @@ -55,7 +55,7 @@ func ModuleSetFact(ctx context.Context, options ExecOptions) (string, string) { LocationUID: "", Data: factVars, }); err != nil { - klog.ErrorS(err, "merge fact error") + klog.V(4).ErrorS(err, "merge fact error") return "", err.Error() } return "success", "" diff --git a/pkg/modules/template.go b/pkg/modules/template.go index 3e6c0b11..4ac0c2d4 100644 --- a/pkg/modules/template.go +++ b/pkg/modules/template.go @@ -48,17 +48,17 @@ func ModuleTemplate(ctx context.Context, options ExecOptions) (string, string) { LocationUID: string(options.Task.UID), }) if err != nil { - klog.ErrorS(err, "failed to get location vars") + klog.V(4).ErrorS(err, "failed to get location vars") return "", err.Error() } srcStr, err := tmpl.ParseString(lv.(variable.VariableData), *src) if err != nil { - klog.ErrorS(err, "template parse src error", "input", *src) + klog.V(4).ErrorS(err, "template parse src error", "input", *src) return "", err.Error() } destStr, err := tmpl.ParseString(lv.(variable.VariableData), *dest) if err != nil { - klog.ErrorS(err, "template parse dest error", "input", *dest) + klog.V(4).ErrorS(err, "template parse dest error", "input", *dest) return "", err.Error() } @@ -68,7 +68,7 @@ func ModuleTemplate(ctx context.Context, options ExecOptions) (string, string) { } else { projectFs, err := project.New(project.Options{Pipeline: &options.Pipeline}).FS(ctx, false) if err != nil { - klog.ErrorS(err, "failed to get project fs") + klog.V(4).ErrorS(err, "failed to get project fs") return "", err.Error() } baseFS = projectFs @@ -76,7 +76,7 @@ func ModuleTemplate(ctx context.Context, options ExecOptions) (string, string) { roleName := options.Task.Annotations[kubekeyv1alpha1.TaskAnnotationRole] flPath := project.GetTemplatesFromPlayBook(baseFS, options.Pipeline.Spec.Playbook, roleName, srcStr) if _, err := fs.Stat(baseFS, flPath); err != nil { - klog.ErrorS(err, "find src error") + klog.V(4).ErrorS(err, "find src error") return "", err.Error() } @@ -87,13 +87,13 @@ func ModuleTemplate(ctx context.Context, options ExecOptions) (string, string) { // get connector ha, err := options.Variable.Get(variable.HostVars{HostName: options.Host}) if err != nil { - klog.ErrorS(err, "failed to get host vars") + klog.V(4).ErrorS(err, "failed to get host vars") return "", err.Error() } conn = connector.NewConnector(options.Host, ha.(variable.VariableData)) } if err := conn.Init(ctx); err != nil { - klog.ErrorS(err, "failed to init connector") + klog.V(4).ErrorS(err, "failed to init connector") return "", err.Error() } defer conn.Close(ctx) @@ -104,18 +104,18 @@ func ModuleTemplate(ctx context.Context, options ExecOptions) (string, string) { LocationUID: string(options.Task.UID), }) if err != nil { - klog.ErrorS(err, "failed to get location vars") + klog.V(4).ErrorS(err, "failed to get location vars") return "", err.Error() } data, err := fs.ReadFile(baseFS, flPath) if err != nil { - klog.ErrorS(err, "failed to read src file", "file_path", flPath) + klog.V(4).ErrorS(err, "failed to read src file", "file_path", flPath) return "", err.Error() } result, err := tmpl.ParseFile(lg.(variable.VariableData), data) if err != nil { - klog.ErrorS(err, "failed to parse file", "file_path", flPath) + klog.V(4).ErrorS(err, "failed to parse file", "file_path", flPath) return "", err.Error() } @@ -125,7 +125,7 @@ func ModuleTemplate(ctx context.Context, options ExecOptions) (string, string) { mode = fs.FileMode(*v) } if err := conn.CopyFile(ctx, []byte(result), destStr, mode); err != nil { - klog.ErrorS(err, "failed to copy file", "src", flPath, "dest", destStr) + klog.V(4).ErrorS(err, "failed to copy file", "src", flPath, "dest", destStr) return "", err.Error() } return "success", "" diff --git a/pkg/project/project_git.go b/pkg/project/project_git.go index 30f2493b..49a6f662 100644 --- a/pkg/project/project_git.go +++ b/pkg/project/project_git.go @@ -41,7 +41,7 @@ func (r gitProject) FS(ctx context.Context, update bool) (fs.FS, error) { return os.DirFS(r.localDir), nil } if err := r.init(ctx); err != nil { - klog.ErrorS(err, "Init git project error", "project_addr", r.Pipeline.Spec.Project.Addr) + klog.V(4).ErrorS(err, "Init git project error", "project_addr", r.Pipeline.Spec.Project.Addr) return nil, err } return os.DirFS(r.localDir), nil @@ -75,12 +75,12 @@ func (r gitProject) gitClone(ctx context.Context) error { func (r gitProject) gitPull(ctx context.Context) error { open, err := git.PlainOpen(r.localDir) if err != nil { - klog.ErrorS(err, "git open error", "local_dir", r.localDir) + klog.V(4).ErrorS(err, "git open error", "local_dir", r.localDir) return err } wt, err := open.Worktree() if err != nil { - klog.ErrorS(err, "git open worktree error", "local_dir", r.localDir) + klog.V(4).ErrorS(err, "git open worktree error", "local_dir", r.localDir) return err } if err := wt.PullContext(ctx, &git.PullOptions{ @@ -90,7 +90,7 @@ func (r gitProject) gitPull(ctx context.Context) error { Auth: &http.TokenAuth{r.Pipeline.Spec.Project.Token}, InsecureSkipTLS: false, }); err != nil && err != git.NoErrAlreadyUpToDate { - klog.ErrorS(err, "git pull error", "local_dir", r.localDir) + klog.V(4).ErrorS(err, "git pull error", "local_dir", r.localDir) return err } diff --git a/pkg/proxy/admit.go b/pkg/proxy/admit.go new file mode 100644 index 00000000..8750d5a2 --- /dev/null +++ b/pkg/proxy/admit.go @@ -0,0 +1,45 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package proxy + +import ( + "context" + + "k8s.io/apiserver/pkg/admission" +) + +func newAlwaysAdmit() admission.Interface { + return &admit{} +} + +type admit struct { +} + +func (a admit) Validate(ctx context.Context, attr admission.Attributes, obj admission.ObjectInterfaces) (err error) { + return nil +} + +func (a admit) Admit(ctx context.Context, attr admission.Attributes, obj admission.ObjectInterfaces) (err error) { + return nil +} + +func (a admit) Handles(operation admission.Operation) bool { + return true +} + +var _ admission.MutationInterface = admit{} +var _ admission.ValidationInterface = admit{} diff --git a/pkg/proxy/api_resources.go b/pkg/proxy/api_resources.go new file mode 100644 index 00000000..1d77229c --- /dev/null +++ b/pkg/proxy/api_resources.go @@ -0,0 +1,135 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package proxy + +import ( + "fmt" + "net/http" + "reflect" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/endpoints/discovery" + "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" + "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" + "k8s.io/apiserver/pkg/features" + apirest "k8s.io/apiserver/pkg/registry/rest" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/klog/v2" + + _const "github.com/kubesphere/kubekey/v4/pkg/const" +) + +const defaultMinRequestTimeout = 1800 * time.Second + +type apiResources struct { + gv schema.GroupVersion + prefix string + minRequestTimeout time.Duration + + resourceOptions []resourceOptions + list []metav1.APIResource + typer runtime.ObjectTyper + serializer runtime.NegotiatedSerializer +} + +type resourceOptions struct { + path string + storage apirest.Storage + admit admission.Interface +} + +func newApiIResources(gv schema.GroupVersion) *apiResources { + return &apiResources{ + gv: gv, + prefix: "/apis/" + gv.String(), + minRequestTimeout: defaultMinRequestTimeout, + + typer: _const.Scheme, + serializer: _const.Codecs, + } +} + +func (a *apiResources) AddResource(o resourceOptions) error { + if o.admit == nil { + // set default admit + o.admit = newAlwaysAdmit() + } + a.resourceOptions = append(a.resourceOptions, o) + storageVersionProvider, isStorageVersionProvider := o.storage.(apirest.StorageVersionProvider) + var apiResource metav1.APIResource + if utilfeature.DefaultFeatureGate.Enabled(features.StorageVersionHash) && + isStorageVersionProvider && + storageVersionProvider.StorageVersion() != nil { + versioner := storageVersionProvider.StorageVersion() + gvk, err := getStorageVersionKind(versioner, o.storage, a.typer) + if err != nil { + klog.V(4).ErrorS(err, "failed to get storage version kind", "storage", reflect.TypeOf(o.storage)) + return err + } + apiResource.Group = gvk.Group + apiResource.Version = gvk.Version + apiResource.Kind = gvk.Kind + apiResource.StorageVersionHash = discovery.StorageVersionHash(gvk.Group, gvk.Version, gvk.Kind) + } + apiResource.Name = o.path + apiResource.Namespaced = true + apiResource.Verbs = []string{"*"} + if shortNamesProvider, ok := o.storage.(apirest.ShortNamesProvider); ok { + apiResource.ShortNames = shortNamesProvider.ShortNames() + } + if categoriesProvider, ok := o.storage.(apirest.CategoriesProvider); ok { + apiResource.Categories = categoriesProvider.Categories() + } + _, subResource, err := splitSubresource(o.path) + if err != nil { + return err + } + if len(subResource) == 0 { + singularNameProvider, ok := o.storage.(apirest.SingularNameProvider) + if !ok { + return fmt.Errorf("resource %s must implement SingularNameProvider", o.path) + } + apiResource.SingularName = singularNameProvider.GetSingularName() + } + a.list = append(a.list, apiResource) + return nil +} + +func (s *apiResources) handlerApiResources() http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, writer, request, http.StatusOK, + &metav1.APIResourceList{GroupVersion: s.gv.String(), APIResources: s.list}, false) + } +} + +// calculate the storage gvk, the gvk objects are converted to before persisted to the etcd. +func getStorageVersionKind(storageVersioner runtime.GroupVersioner, storage apirest.Storage, typer runtime.ObjectTyper) (schema.GroupVersionKind, error) { + object := storage.New() + fqKinds, _, err := typer.ObjectKinds(object) + if err != nil { + return schema.GroupVersionKind{}, err + } + gvk, ok := storageVersioner.KindForGroupVersionKinds(fqKinds) + if !ok { + return schema.GroupVersionKind{}, fmt.Errorf("cannot find the storage version kind for %v", reflect.TypeOf(object)) + } + return gvk, nil +} diff --git a/pkg/proxy/internal/file_storage.go b/pkg/proxy/internal/file_storage.go new file mode 100644 index 00000000..91d25828 --- /dev/null +++ b/pkg/proxy/internal/file_storage.go @@ -0,0 +1,465 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +import ( + "context" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "sync" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + apistorage "k8s.io/apiserver/pkg/storage" + "k8s.io/apiserver/pkg/storage/storagebackend/factory" + "k8s.io/klog/v2" +) + +// when delete resource, add suffix deleteTag to the file name. +// after delete event is handled, the file will be deleted from disk. +const deleteTag = "-deleted" + +func newFileStorage(prefix string, resource schema.GroupResource, codec runtime.Codec, newFunc func() runtime.Object) (apistorage.Interface, factory.DestroyFunc, error) { + return &fileStorage{ + prefix: prefix, + versioner: apistorage.APIObjectVersioner{}, + resource: resource, + codec: codec, + newFunc: newFunc, + }, func() { + // do nothing + }, nil +} + +type fileStorage struct { + prefix string + versioner apistorage.Versioner + codec runtime.Codec + resource schema.GroupResource + + newFunc func() runtime.Object +} + +var _ apistorage.Interface = &fileStorage{} + +func (s fileStorage) Versioner() apistorage.Versioner { + return s.versioner +} + +func (s fileStorage) Create(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error { + // set resourceVersion to obj + metaObj, err := meta.Accessor(obj) + if err != nil { + klog.V(4).ErrorS(err, "failed to get meta object", "path", filepath.Dir(key)) + return err + } + metaObj.SetResourceVersion("1") + // create file to local disk + if _, err := os.Stat(filepath.Dir(key)); err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(filepath.Dir(key), 0755); err != nil { + klog.V(4).ErrorS(err, "failed to create dir", "path", filepath.Dir(key)) + return err + } + } else { + klog.V(4).ErrorS(err, "failed to check dir", "path", filepath.Dir(key)) + return err + } + } + + data, err := runtime.Encode(s.codec, obj) + if err != nil { + klog.V(4).ErrorS(err, "failed to encode resource file", "path", key) + return err + } + // render to out + if out != nil { + err = decode(s.codec, data, out) + if err != nil { + klog.V(4).ErrorS(err, "failed to decode resource file", "path", key) + return err + } + } + // render to file + if err := os.WriteFile(key+".yaml", data, os.ModePerm); err != nil { + klog.V(4).ErrorS(err, "failed to create resource file", "path", key) + return err + } + return nil +} + +func (s fileStorage) Delete(ctx context.Context, key string, out runtime.Object, preconditions *apistorage.Preconditions, validateDeletion apistorage.ValidateObjectFunc, cachedExistingObject runtime.Object) error { + if cachedExistingObject != nil { + out = cachedExistingObject + } else { + if err := s.Get(ctx, key, apistorage.GetOptions{}, out); err != nil { + klog.V(4).ErrorS(err, "failed to get resource", "path", key) + return err + } + } + + if err := preconditions.Check(key, out); err != nil { + klog.V(4).ErrorS(err, "failed to check preconditions", "path", key) + return err + } + + if err := validateDeletion(ctx, out); err != nil { + klog.V(4).ErrorS(err, "failed to validate deletion", "path", key) + return err + } + + // delete object + // rename file to trigger watcher + if err := os.Rename(key+".yaml", key+".yaml"+deleteTag); err != nil { + klog.V(4).ErrorS(err, "failed to rename resource file", "path", key) + return err + } + return nil +} + +func (s fileStorage) Watch(ctx context.Context, key string, opts apistorage.ListOptions) (watch.Interface, error) { + return newFileWatcher(s.resource, s.codec, key) +} + +func (s fileStorage) Get(ctx context.Context, key string, opts apistorage.GetOptions, out runtime.Object) error { + data, err := os.ReadFile(key + ".yaml") + if err != nil { + klog.V(4).ErrorS(err, "failed to read resource file", "path", key) + return err + } + if err := decode(s.codec, data, out); err != nil { + klog.V(4).ErrorS(err, "failed to decode resource file", "path", key) + return err + } + return nil +} + +func (s fileStorage) GetList(ctx context.Context, key string, opts apistorage.ListOptions, listObj runtime.Object) error { + listPtr, err := meta.GetItemsPtr(listObj) + if err != nil { + return err + } + v, err := conversion.EnforcePtr(listPtr) + if err != nil || v.Kind() != reflect.Slice { + return fmt.Errorf("need ptr to slice: %v", err) + } + + // lastKey in result. + var lastKey string + var hasMore bool + // resourceVersionMatchRule is a function that returns true if the resource version matches the rule. + var resourceVersionMatchRule = func(uint64) bool { + // default rule is to match all resource versions + return true + } + var continueKeyMatchRule = func(key string) bool { + // default rule + return strings.HasSuffix(key, ".yaml") + } + + switch { + case opts.Recursive && len(opts.Predicate.Continue) > 0: + // The format of continueKey is: namespace/resourceName/name.yaml + // continueKey is localPath which resources store. + continueKey, _, err := apistorage.DecodeContinue(opts.Predicate.Continue, key) + if err != nil { + klog.V(4).ErrorS(err, "failed to parse continueKey", "continueKey", opts.Predicate.Continue) + return fmt.Errorf("invalid continue token: %v", err) + } + startReadOnce := sync.Once{} + continueKeyMatchRule = func(key string) bool { + var startRead bool + if key == continueKey { + startReadOnce.Do(func() { + startRead = true + }) + } + // start read after continueKey (not contain). Because it has read in last result. + return startRead && key != continueKey + } + case len(opts.ResourceVersion) > 0: + parsedRV, err := s.versioner.ParseResourceVersion(opts.ResourceVersion) + if err != nil { + return fmt.Errorf("invalid resource version: %v", err) + } + switch opts.ResourceVersionMatch { + case metav1.ResourceVersionMatchNotOlderThan: + resourceVersionMatchRule = func(u uint64) bool { + return u >= parsedRV + } + case metav1.ResourceVersionMatchExact: + resourceVersionMatchRule = func(u uint64) bool { + return u == parsedRV + } + case "": // legacy case + // use default rule. match all resource versions. + default: + return fmt.Errorf("unknown ResourceVersionMatch value: %v", opts.ResourceVersionMatch) + } + } + + switch len(filepath.SplitList(strings.TrimPrefix(key, s.prefix))) { + case 0: // read all namespace's resources + // Traverse the resource storage directory. startRead after continueKey. + // Traverse the resource storage directory. startRead after continueKey. + // get all resources from key. key is runtimeDir + rootEntries, err := os.ReadDir(key) + if err != nil && !os.IsNotExist(err) { + klog.V(4).ErrorS(err, "failed to read runtime dir", "path", key) + return err + } + for _, ns := range rootEntries { + if !ns.IsDir() { + continue + } + // the next dir is namespace. + nsDir := filepath.Join(key, ns.Name()) + entries, err := os.ReadDir(nsDir) + if err != nil { + if os.IsNotExist(err) { + continue + } + klog.V(4).ErrorS(err, "failed to read namespaces dir", "path", nsDir) + return err + } + + for _, e := range entries { + if e.IsDir() { + continue + } + // the next file is resource name. + currentKey := filepath.Join(nsDir, e.Name()) + if !continueKeyMatchRule(currentKey) { + continue + } + data, err := os.ReadFile(currentKey) + if err != nil { + if os.IsNotExist(err) { + continue + } + klog.V(4).ErrorS(err, "failed to read resource file", "path", currentKey) + return err + } + + obj, _, err := s.codec.Decode(data, nil, getNewItem(listObj, v)) + if err != nil { + klog.V(4).ErrorS(err, "failed to decode resource file", "path", currentKey) + return err + } + metaObj, err := meta.Accessor(obj) + if err != nil { + klog.V(4).ErrorS(err, "failed to get meta object", "path", currentKey) + return err + } + rv, err := s.versioner.ParseResourceVersion(metaObj.GetResourceVersion()) + if err != nil { + klog.V(4).ErrorS(err, "failed to parse resource version", "resourceVersion", obj.(metav1.Object).GetResourceVersion()) + return err + } + if !resourceVersionMatchRule(rv) { + continue + } + if matched, err := opts.Predicate.Matches(obj); err == nil && matched { + v.Set(reflect.Append(v, reflect.ValueOf(obj).Elem())) + lastKey = currentKey + } + + if opts.Predicate.Limit != 0 && int64(v.Len()) >= opts.Predicate.Limit { + // got enough results. Stop the loop. + goto RESULT + } + } + } + hasMore = false + case 1: // read a namespace's resources + // Traverse the resource storage directory. startRead after continueKey. + // get all resources from key. key is runtimeDir + rootEntries, err := os.ReadDir(key) + if err != nil && !os.IsNotExist(err) { + klog.V(4).ErrorS(err, "failed to read runtime dir", "path", key) + return err + } + for _, rf := range rootEntries { + if rf.IsDir() { + continue + } + // the next file is resource name. + currentKey := filepath.Join(key, rf.Name()) + if !continueKeyMatchRule(currentKey) { + continue + } + data, err := os.ReadFile(currentKey) + if err != nil { + if os.IsNotExist(err) { + continue + } + klog.V(4).ErrorS(err, "failed to read resource file", "path", currentKey) + return err + } + + obj, _, err := s.codec.Decode(data, nil, getNewItem(listObj, v)) + if err != nil { + klog.V(4).ErrorS(err, "failed to decode resource file", "path", currentKey) + return err + } + metaObj, err := meta.Accessor(obj) + if err != nil { + klog.V(4).ErrorS(err, "failed to get meta object", "path", currentKey) + return err + } + rv, err := s.versioner.ParseResourceVersion(metaObj.GetResourceVersion()) + if err != nil { + klog.V(4).ErrorS(err, "failed to parse resource version", "resourceVersion", obj.(metav1.Object).GetResourceVersion()) + return err + } + if !resourceVersionMatchRule(rv) { + continue + } + if matched, err := opts.Predicate.Matches(obj); err == nil && matched { + v.Set(reflect.Append(v, reflect.ValueOf(obj).Elem())) + lastKey = currentKey + } + + if opts.Predicate.Limit != 0 && int64(v.Len()) >= opts.Predicate.Limit { + // got enough results. Stop the loop. + goto RESULT + } + } + hasMore = false + default: + klog.V(4).ErrorS(nil, "key is invalid", "key", key) + return fmt.Errorf("key is invalid: %s", key) + } + +RESULT: + if v.IsNil() { + // Ensure that we never return a nil Items pointer in the result for consistency. + v.Set(reflect.MakeSlice(v.Type(), 0, 0)) + } + + // instruct the client to begin querying from immediately after the last key we returned + // we never return a key that the client wouldn't be allowed to see + if hasMore { + // we want to start immediately after the last key + next, err := apistorage.EncodeContinue(string(lastKey)+"\x00", key, 0) + if err != nil { + return err + } + // Unable to calculate remainingItemCount currently. + // todo Store the resourceVersion in the file data. No resourceVersion strategy for List Object currently. + // resourceVersion default set 1 + return s.versioner.UpdateList(listObj, 1, next, nil) + } + + // no continuation + // resourceVersion default set 1 + return s.versioner.UpdateList(listObj, 1, "", nil) +} + +func (s fileStorage) GuaranteedUpdate(ctx context.Context, key string, destination runtime.Object, ignoreNotFound bool, preconditions *apistorage.Preconditions, tryUpdate apistorage.UpdateFunc, cachedExistingObject runtime.Object) error { + var oldObj runtime.Object + if cachedExistingObject != nil { + oldObj = cachedExistingObject + } else { + oldObj = s.newFunc() + if err := s.Get(ctx, key, apistorage.GetOptions{IgnoreNotFound: ignoreNotFound}, oldObj); err != nil { + klog.V(4).ErrorS(err, "failed to get resource", "path", key) + return err + } + } + if err := preconditions.Check(key, oldObj); err != nil { + klog.V(4).ErrorS(err, "failed to check preconditions", "path", key) + return err + } + // set resourceVersion to obj + metaObj, err := meta.Accessor(oldObj) + if err != nil { + klog.V(4).ErrorS(err, "failed to get meta object", "path", filepath.Dir(key)) + return err + } + oldVersion, err := s.versioner.ParseResourceVersion(metaObj.GetResourceVersion()) + if err != nil { + klog.V(4).ErrorS(err, "failed to parse resource version", "resourceVersion", metaObj.GetResourceVersion()) + return err + } + out, _, err := tryUpdate(oldObj, apistorage.ResponseMeta{ResourceVersion: oldVersion + 1}) + if err != nil { + klog.V(4).ErrorS(err, "failed to try update", "path", key) + return err + } + + data, err := runtime.Encode(s.codec, out) + if err != nil { + klog.V(4).ErrorS(err, "failed to encode resource file", "path", key) + return err + } + // render to destination + if destination != nil { + err = decode(s.codec, data, destination) + if err != nil { + klog.V(4).ErrorS(err, "failed to decode resource file", "path", key) + return err + } + } + // render to file + if err := os.WriteFile(key+".yaml", data, os.ModePerm); err != nil { + klog.V(4).ErrorS(err, "failed to create resource file", "path", key) + return err + } + return nil +} + +func (s fileStorage) Count(key string) (int64, error) { + return 0, nil +} + +func (s fileStorage) RequestWatchProgress(ctx context.Context) error { + return nil +} + +// decode decodes value of bytes into object. It will also set the object resource version to rev. +// On success, objPtr would be set to the object. +func decode(codec runtime.Codec, value []byte, objPtr runtime.Object) error { + if _, err := conversion.EnforcePtr(objPtr); err != nil { + return fmt.Errorf("unable to convert output object to pointer: %v", err) + } + _, _, err := codec.Decode(value, nil, objPtr) + if err != nil { + return err + } + return nil +} + +func getNewItem(listObj runtime.Object, v reflect.Value) runtime.Object { + // For unstructured lists with a target group/version, preserve the group/version in the instantiated list items + if unstructuredList, isUnstructured := listObj.(*unstructured.UnstructuredList); isUnstructured { + if apiVersion := unstructuredList.GetAPIVersion(); len(apiVersion) > 0 { + return &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": apiVersion}} + } + } + // Otherwise just instantiate an empty item + elem := v.Type().Elem() + return reflect.New(elem).Interface().(runtime.Object) +} diff --git a/pkg/proxy/internal/rest_option.go b/pkg/proxy/internal/rest_option.go new file mode 100644 index 00000000..5ce3c90b --- /dev/null +++ b/pkg/proxy/internal/rest_option.go @@ -0,0 +1,116 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +import ( + "path/filepath" + "sync" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/json" + "k8s.io/apimachinery/pkg/runtime/serializer/versioning" + apigeneric "k8s.io/apiserver/pkg/registry/generic" + apistorage "k8s.io/apiserver/pkg/storage" + cacherstorage "k8s.io/apiserver/pkg/storage/cacher" + "k8s.io/apiserver/pkg/storage/storagebackend" + "k8s.io/apiserver/pkg/storage/storagebackend/factory" + cgcache "k8s.io/client-go/tools/cache" + + _const "github.com/kubesphere/kubekey/v4/pkg/const" +) + +func NewFileRESTOptionsGetter(gv schema.GroupVersion) apigeneric.RESTOptionsGetter { + return &fileRESTOptionsGetter{ + gv: gv, + storageConfig: &storagebackend.Config{ + Type: "", + Prefix: "/", + Transport: storagebackend.TransportConfig{}, + Codec: newYamlCodec(gv), + EncodeVersioner: runtime.NewMultiGroupVersioner(gv), + }, + } +} + +func newYamlCodec(gv schema.GroupVersion) runtime.Codec { + yamlSerializer := json.NewSerializerWithOptions(json.DefaultMetaFactory, _const.Scheme, _const.Scheme, json.SerializerOptions{true, false, false}) + return versioning.NewDefaultingCodecForScheme( + _const.Scheme, + yamlSerializer, + yamlSerializer, + gv, + gv, + ) +} + +type fileRESTOptionsGetter struct { + gv schema.GroupVersion + allowGroups []string + storageConfig *storagebackend.Config +} + +func (f fileRESTOptionsGetter) GetRESTOptions(resource schema.GroupResource) (apigeneric.RESTOptions, error) { + prefix := filepath.Join(_const.GetRuntimeDir(), f.gv.Group, f.gv.Version, resource.Resource) + return apigeneric.RESTOptions{ + StorageConfig: f.storageConfig.ForResource(resource), + Decorator: func(storageConfig *storagebackend.ConfigForResource, resourcePrefix string, + keyFunc func(obj runtime.Object) (string, error), + newFunc func() runtime.Object, + newListFunc func() runtime.Object, + getAttrsFunc apistorage.AttrFunc, + triggerFuncs apistorage.IndexerFuncs, + indexers *cgcache.Indexers) (apistorage.Interface, factory.DestroyFunc, error) { + s, d, err := newFileStorage(prefix, resource, storageConfig.Codec, newFunc) + if err != nil { + return s, d, err + } + + cacherConfig := cacherstorage.Config{ + Storage: s, + Versioner: apistorage.APIObjectVersioner{}, + GroupResource: storageConfig.GroupResource, + ResourcePrefix: resourcePrefix, + KeyFunc: keyFunc, + NewFunc: newFunc, + NewListFunc: newListFunc, + GetAttrsFunc: getAttrsFunc, + IndexerFuncs: triggerFuncs, + Indexers: indexers, + Codec: storageConfig.Codec, + } + cacher, err := cacherstorage.NewCacherFromConfig(cacherConfig) + if err != nil { + return nil, func() {}, err + } + var once sync.Once + destroyFunc := func() { + once.Do(func() { + cacher.Stop() + d() + }) + } + + return cacher, destroyFunc, nil + }, + EnableGarbageCollection: false, + DeleteCollectionWorkers: 0, + ResourcePrefix: prefix, + CountMetricPollPeriod: 0, + StorageObjectCountTracker: nil, + }, nil +} diff --git a/pkg/proxy/internal/watcher.go b/pkg/proxy/internal/watcher.go new file mode 100644 index 00000000..91c52f60 --- /dev/null +++ b/pkg/proxy/internal/watcher.go @@ -0,0 +1,143 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +import ( + "os" + "path/filepath" + "strings" + + "github.com/fsnotify/fsnotify" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/klog/v2" + + _const "github.com/kubesphere/kubekey/v4/pkg/const" +) + +type fileWatcher struct { + resource schema.GroupResource + codec runtime.Codec + newFunc func() runtime.Object + watcher *fsnotify.Watcher + watchEvents chan watch.Event +} + +func newFileWatcher(resource schema.GroupResource, codec runtime.Codec, path string) (watch.Interface, error) { + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(path, os.ModePerm); err != nil { + return nil, err + } + } else { + klog.V(4).ErrorS(err, "failed to stat path", "path", path) + return nil, err + } + } + + watcher, err := fsnotify.NewWatcher() + if err != nil { + klog.V(4).ErrorS(err, "failed to create file watcher", "path", path) + return nil, err + } + if err := watcher.Add(path); err != nil { + klog.V(4).ErrorS(err, "failed to add path to file watcher", "path", path) + return nil, err + } + w := &fileWatcher{ + resource: resource, + codec: codec, + watcher: watcher, + } + + go w.watch() + return w, nil +} + +func (w *fileWatcher) Stop() { + if err := w.watcher.Close(); err != nil { + klog.V(4).ErrorS(err, "failed to close file watcher") + } +} + +func (w *fileWatcher) ResultChan() <-chan watch.Event { + return w.watchEvents +} + +func (w *fileWatcher) watch() { + // stop the watcher + //defer f.Stop() + for { + select { + case event := <-w.watcher.Events: + // filter resource type + klog.V(4).InfoS("receive watcher event", "event", event) + relPath, err := filepath.Rel(_const.GetRuntimeDir(), event.Name) + if err != nil { + continue + } + // the second element is the resource name + pl := filepath.SplitList(relPath) + if len(pl) < 2 || pl[1] != w.resource.Resource { + continue + } + data, err := os.ReadFile(event.Name) + if err != nil { + klog.V(4).ErrorS(err, "failed to read resource file", "event", event) + continue + } + switch event.Op { + case fsnotify.Create: + obj, _, err := w.codec.Decode(data, nil, w.newFunc()) + if err != nil { + klog.V(4).ErrorS(err, "failed to decode resource file", "event", event) + continue + } + w.watchEvents <- watch.Event{ + Type: watch.Added, + Object: obj, + } + case fsnotify.Write: + obj, _, err := w.codec.Decode(data, nil, w.newFunc()) + if err != nil { + klog.V(4).ErrorS(err, "failed to decode resource file", "event", event) + continue + } + if strings.HasSuffix(filepath.Base(event.Name), deleteTag) { + // delete event + w.watchEvents <- watch.Event{ + Type: watch.Deleted, + Object: obj, + } + if err := os.Remove(event.Name); err != nil { + klog.ErrorS(err, "failed to remove file", "event", event) + } + } else { + // update event + w.watchEvents <- watch.Event{ + Type: watch.Modified, + Object: obj, + } + } + } + case err := <-w.watcher.Errors: + klog.V(4).ErrorS(err, "file watcher error") + return + } + } +} diff --git a/pkg/proxy/path_expression.go b/pkg/proxy/path_expression.go new file mode 100644 index 00000000..2c7f327b --- /dev/null +++ b/pkg/proxy/path_expression.go @@ -0,0 +1,111 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package proxy + +// Copyright 2013 Ernest Micklei. All rights reserved. +// Use of this source code is governed by a license +// that can be found in the LICENSE file. + +import ( + "bytes" + "fmt" + "regexp" + "strings" +) + +// PathExpression holds a compiled path expression (RegExp) needed to match against +// Http request paths and to extract path parameter values. +type pathExpression struct { + LiteralCount int // the number of literal characters (means those not resulting from template variable substitution) + VarNames []string // the names of parameters (enclosed by {}) in the path + VarCount int // the number of named parameters (enclosed by {}) in the path + Matcher *regexp.Regexp + Source string // Path as defined by the RouteBuilder + tokens []string +} + +// NewPathExpression creates a PathExpression from the input URL path. +// Returns an error if the path is invalid. +func newPathExpression(path string) (*pathExpression, error) { + expression, literalCount, varNames, varCount, tokens := templateToRegularExpression(path) + compiled, err := regexp.Compile(expression) + if err != nil { + return nil, err + } + return &pathExpression{literalCount, varNames, varCount, compiled, expression, tokens}, nil +} + +// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-370003.7.3 +func templateToRegularExpression(template string) (expression string, literalCount int, varNames []string, varCount int, tokens []string) { + var buffer bytes.Buffer + buffer.WriteString("^") + //tokens = strings.Split(template, "/") + tokens = tokenizePath(template) + for _, each := range tokens { + if each == "" { + continue + } + buffer.WriteString("/") + if strings.HasPrefix(each, "{") { + // check for regular expression in variable + colon := strings.Index(each, ":") + var varName string + if colon != -1 { + // extract expression + varName = strings.TrimSpace(each[1:colon]) + paramExpr := strings.TrimSpace(each[colon+1 : len(each)-1]) + if paramExpr == "*" { // special case + buffer.WriteString("(.*)") + } else { + buffer.WriteString(fmt.Sprintf("(%s)", paramExpr)) // between colon and closing moustache + } + } else { + // plain var + varName = strings.TrimSpace(each[1 : len(each)-1]) + buffer.WriteString("([^/]+?)") + } + varNames = append(varNames, varName) + varCount += 1 + } else { + literalCount += len(each) + encoded := each // TODO URI encode + buffer.WriteString(regexp.QuoteMeta(encoded)) + } + } + return strings.TrimRight(buffer.String(), "/") + "(/.*)?$", literalCount, varNames, varCount, tokens +} + +// Tokenize an URL path using the slash separator ; the result does not have empty tokens +func tokenizePath(path string) []string { + if "/" == path { + return nil + } + if TrimRightSlashEnabled { + // 3.9.0 + return strings.Split(strings.Trim(path, "/"), "/") + } else { + // 3.10.2 + return strings.Split(strings.TrimLeft(path, "/"), "/") + } + +} + +// TrimRightSlashEnabled controls whether +// - path on route building is using path.Join +// - the path of the incoming request is trimmed of its slash suffux. +// Value of true matches the behavior of <= 3.9.0 +var TrimRightSlashEnabled = true diff --git a/pkg/proxy/resources/config/storage.go b/pkg/proxy/resources/config/storage.go new file mode 100644 index 00000000..c38cd682 --- /dev/null +++ b/pkg/proxy/resources/config/storage.go @@ -0,0 +1,60 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "k8s.io/apimachinery/pkg/runtime" + apigeneric "k8s.io/apiserver/pkg/registry/generic" + apiregistry "k8s.io/apiserver/pkg/registry/generic/registry" + apirest "k8s.io/apiserver/pkg/registry/rest" + + kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" +) + +type ConfigStorage struct { + Config *REST +} + +type REST struct { + *apiregistry.Store +} + +func NewStorage(optsGetter apigeneric.RESTOptionsGetter) (ConfigStorage, error) { + store := &apiregistry.Store{ + NewFunc: func() runtime.Object { return &kubekeyv1.Config{} }, + NewListFunc: func() runtime.Object { return &kubekeyv1.ConfigList{} }, + DefaultQualifiedResource: kubekeyv1.SchemeGroupVersion.WithResource("configs").GroupResource(), + SingularQualifiedResource: kubekeyv1.SchemeGroupVersion.WithResource("config").GroupResource(), + + CreateStrategy: Strategy, + UpdateStrategy: Strategy, + DeleteStrategy: Strategy, + ReturnDeletedObject: true, + + TableConvertor: apirest.NewDefaultTableConvertor(kubekeyv1.SchemeGroupVersion.WithResource("configs").GroupResource()), + } + options := &apigeneric.StoreOptions{ + RESTOptions: optsGetter, + } + if err := store.CompleteWithOptions(options); err != nil { + return ConfigStorage{}, err + } + + return ConfigStorage{ + Config: &REST{store}, + }, nil +} diff --git a/pkg/proxy/resources/config/strategy.go b/pkg/proxy/resources/config/strategy.go new file mode 100644 index 00000000..d7863c26 --- /dev/null +++ b/pkg/proxy/resources/config/strategy.go @@ -0,0 +1,92 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + apinames "k8s.io/apiserver/pkg/storage/names" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + + _const "github.com/kubesphere/kubekey/v4/pkg/const" +) + +// ConfigStrategy implements behavior for Pods +type ConfigStrategy struct { + runtime.ObjectTyper + apinames.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Pod +// objects via the REST API. +var Strategy = ConfigStrategy{_const.Scheme, apinames.SimpleNameGenerator} + +// ===CreateStrategy=== + +func (t ConfigStrategy) NamespaceScoped() bool { + return true +} + +func (t ConfigStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { + // do nothing +} + +func (t ConfigStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { + // do nothing + return nil +} + +func (t ConfigStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + // do nothing + return nil +} + +func (t ConfigStrategy) Canonicalize(obj runtime.Object) { + // do nothing +} + +// ===UpdateStrategy=== + +func (t ConfigStrategy) AllowCreateOnUpdate() bool { + return false +} + +func (t ConfigStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + // do nothing +} + +func (t ConfigStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + // do nothing + return nil +} + +func (t ConfigStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + // do nothing + return nil +} + +func (t ConfigStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// ===ResetFieldsStrategy=== + +func (t ConfigStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + return nil +} diff --git a/pkg/proxy/resources/inventory/storage.go b/pkg/proxy/resources/inventory/storage.go new file mode 100644 index 00000000..aba48028 --- /dev/null +++ b/pkg/proxy/resources/inventory/storage.go @@ -0,0 +1,60 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package inventory + +import ( + "k8s.io/apimachinery/pkg/runtime" + apigeneric "k8s.io/apiserver/pkg/registry/generic" + apiregistry "k8s.io/apiserver/pkg/registry/generic/registry" + apirest "k8s.io/apiserver/pkg/registry/rest" + + kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" +) + +type InventoryStorage struct { + Inventory *REST +} + +type REST struct { + *apiregistry.Store +} + +func NewStorage(optsGetter apigeneric.RESTOptionsGetter) (InventoryStorage, error) { + store := &apiregistry.Store{ + NewFunc: func() runtime.Object { return &kubekeyv1.Inventory{} }, + NewListFunc: func() runtime.Object { return &kubekeyv1.InventoryList{} }, + DefaultQualifiedResource: kubekeyv1.SchemeGroupVersion.WithResource("inventories").GroupResource(), + SingularQualifiedResource: kubekeyv1.SchemeGroupVersion.WithResource("inventory").GroupResource(), + + CreateStrategy: Strategy, + UpdateStrategy: Strategy, + DeleteStrategy: Strategy, + ReturnDeletedObject: true, + + TableConvertor: apirest.NewDefaultTableConvertor(kubekeyv1.SchemeGroupVersion.WithResource("inventories").GroupResource()), + } + options := &apigeneric.StoreOptions{ + RESTOptions: optsGetter, + } + if err := store.CompleteWithOptions(options); err != nil { + return InventoryStorage{}, err + } + + return InventoryStorage{ + Inventory: &REST{store}, + }, nil +} diff --git a/pkg/proxy/resources/inventory/strategy.go b/pkg/proxy/resources/inventory/strategy.go new file mode 100644 index 00000000..8e5844aa --- /dev/null +++ b/pkg/proxy/resources/inventory/strategy.go @@ -0,0 +1,92 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package inventory + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + apinames "k8s.io/apiserver/pkg/storage/names" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + + _const "github.com/kubesphere/kubekey/v4/pkg/const" +) + +// pipelineStrategy implements behavior for Pods +type pipelineStrategy struct { + runtime.ObjectTyper + apinames.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Pod +// objects via the REST API. +var Strategy = pipelineStrategy{_const.Scheme, apinames.SimpleNameGenerator} + +// ===CreateStrategy=== + +func (t pipelineStrategy) NamespaceScoped() bool { + return true +} + +func (t pipelineStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { + // do nothing +} + +func (t pipelineStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { + // do nothing + return nil +} + +func (t pipelineStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + // do nothing + return nil +} + +func (t pipelineStrategy) Canonicalize(obj runtime.Object) { + // do nothing +} + +// ===UpdateStrategy=== + +func (t pipelineStrategy) AllowCreateOnUpdate() bool { + return false +} + +func (t pipelineStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + // do nothing +} + +func (t pipelineStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + // do nothing + return nil +} + +func (t pipelineStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + // do nothing + return nil +} + +func (t pipelineStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// ===ResetFieldsStrategy=== + +func (t pipelineStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + return nil +} diff --git a/pkg/proxy/resources/pipeline/storage.go b/pkg/proxy/resources/pipeline/storage.go new file mode 100644 index 00000000..9020d93d --- /dev/null +++ b/pkg/proxy/resources/pipeline/storage.go @@ -0,0 +1,105 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pipeline + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + apigeneric "k8s.io/apiserver/pkg/registry/generic" + apiregistry "k8s.io/apiserver/pkg/registry/generic/registry" + apirest "k8s.io/apiserver/pkg/registry/rest" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + + kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" +) + +type PipelineStorage struct { + Pipeline *REST + PipelineStatus *StatusREST +} + +type REST struct { + *apiregistry.Store +} + +type StatusREST struct { + store *apiregistry.Store +} + +func (r *StatusREST) NamespaceScoped() bool { + return true +} + +// New creates a new Node object. +func (r *StatusREST) New() runtime.Object { + return &kubekeyv1.Pipeline{} +} + +// Destroy cleans up resources on shutdown. +func (r *StatusREST) Destroy() { + // Given that underlying store is shared with REST, + // we don't destroy it here explicitly. +} + +// Get retrieves the object from the storage. It is required to support Patch. +func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return r.store.Get(ctx, name, options) +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx context.Context, name string, objInfo apirest.UpdatedObjectInfo, createValidation apirest.ValidateObjectFunc, updateValidation apirest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + // We are explicitly setting forceAllowCreate to false in the call to the underlying storage because + // subresources should never allow create on update. + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options) +} + +// GetResetFields implements rest.ResetFieldsStrategy +func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + return r.store.GetResetFields() +} + +func (r *StatusREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return r.store.ConvertToTable(ctx, object, tableOptions) +} +func NewStorage(optsGetter apigeneric.RESTOptionsGetter) (PipelineStorage, error) { + store := &apiregistry.Store{ + NewFunc: func() runtime.Object { return &kubekeyv1.Pipeline{} }, + NewListFunc: func() runtime.Object { return &kubekeyv1.PipelineList{} }, + DefaultQualifiedResource: kubekeyv1.SchemeGroupVersion.WithResource("pipelines").GroupResource(), + SingularQualifiedResource: kubekeyv1.SchemeGroupVersion.WithResource("pipeline").GroupResource(), + + CreateStrategy: Strategy, + UpdateStrategy: Strategy, + DeleteStrategy: Strategy, + ReturnDeletedObject: true, + + TableConvertor: apirest.NewDefaultTableConvertor(kubekeyv1.SchemeGroupVersion.WithResource("pipelines").GroupResource()), + } + options := &apigeneric.StoreOptions{ + RESTOptions: optsGetter, + } + if err := store.CompleteWithOptions(options); err != nil { + return PipelineStorage{}, err + } + + return PipelineStorage{ + Pipeline: &REST{store}, + PipelineStatus: &StatusREST{store}, + }, nil +} diff --git a/pkg/proxy/resources/pipeline/strategy.go b/pkg/proxy/resources/pipeline/strategy.go new file mode 100644 index 00000000..a05b1516 --- /dev/null +++ b/pkg/proxy/resources/pipeline/strategy.go @@ -0,0 +1,99 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pipeline + +import ( + "context" + "reflect" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + apinames "k8s.io/apiserver/pkg/storage/names" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + + kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" + _const "github.com/kubesphere/kubekey/v4/pkg/const" +) + +// pipelineStrategy implements behavior for Pods +type pipelineStrategy struct { + runtime.ObjectTyper + apinames.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Pod +// objects via the REST API. +var Strategy = pipelineStrategy{_const.Scheme, apinames.SimpleNameGenerator} + +// ===CreateStrategy=== + +func (t pipelineStrategy) NamespaceScoped() bool { + return true +} + +func (t pipelineStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { + // do nothing +} + +func (t pipelineStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { + // do nothing + return nil +} + +func (t pipelineStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + // do nothing + return nil +} + +func (t pipelineStrategy) Canonicalize(obj runtime.Object) { + // do nothing +} + +// ===UpdateStrategy=== + +func (t pipelineStrategy) AllowCreateOnUpdate() bool { + return false +} + +func (t pipelineStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + // do nothing +} + +func (t pipelineStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + // only support update status + task := obj.(*kubekeyv1.Pipeline) + oldTask := old.(*kubekeyv1.Pipeline) + if !reflect.DeepEqual(task.Spec, oldTask.Spec) { + return field.ErrorList{field.Forbidden(field.NewPath("spec"), "spec is immutable")} + } + return nil +} + +func (t pipelineStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + // do nothing + return nil +} + +func (t pipelineStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// ===ResetFieldsStrategy=== + +func (t pipelineStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + return nil +} diff --git a/pkg/proxy/resources/task/storage.go b/pkg/proxy/resources/task/storage.go new file mode 100644 index 00000000..24bc7415 --- /dev/null +++ b/pkg/proxy/resources/task/storage.go @@ -0,0 +1,112 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + apigeneric "k8s.io/apiserver/pkg/registry/generic" + apiregistry "k8s.io/apiserver/pkg/registry/generic/registry" + apirest "k8s.io/apiserver/pkg/registry/rest" + apistorage "k8s.io/apiserver/pkg/storage" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + + kubekeyv1alpha1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1alpha1" +) + +type TaskStorage struct { + Task *REST + TaskStatus *StatusREST +} + +type REST struct { + *apiregistry.Store +} + +type StatusREST struct { + store *apiregistry.Store +} + +func (r *StatusREST) NamespaceScoped() bool { + return true +} + +// New creates a new Node object. +func (r *StatusREST) New() runtime.Object { + return &kubekeyv1alpha1.Task{} +} + +// Destroy cleans up resources on shutdown. +func (r *StatusREST) Destroy() { + // Given that underlying store is shared with REST, + // we don't destroy it here explicitly. +} + +// Get retrieves the object from the storage. It is required to support Patch. +func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return r.store.Get(ctx, name, options) +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx context.Context, name string, objInfo apirest.UpdatedObjectInfo, createValidation apirest.ValidateObjectFunc, updateValidation apirest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + // We are explicitly setting forceAllowCreate to false in the call to the underlying storage because + // subresources should never allow create on update. + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options) +} + +// GetResetFields implements rest.ResetFieldsStrategy +func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + return r.store.GetResetFields() +} + +func (r *StatusREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return r.store.ConvertToTable(ctx, object, tableOptions) +} + +func NewStorage(optsGetter apigeneric.RESTOptionsGetter) (TaskStorage, error) { + store := &apiregistry.Store{ + NewFunc: func() runtime.Object { return &kubekeyv1alpha1.Task{} }, + NewListFunc: func() runtime.Object { return &kubekeyv1alpha1.TaskList{} }, + PredicateFunc: MatchTask, + DefaultQualifiedResource: kubekeyv1alpha1.SchemeGroupVersion.WithResource("tasks").GroupResource(), + SingularQualifiedResource: kubekeyv1alpha1.SchemeGroupVersion.WithResource("task").GroupResource(), + + CreateStrategy: Strategy, + UpdateStrategy: Strategy, + DeleteStrategy: Strategy, + //ResetFieldsStrategy: Strategy, + ReturnDeletedObject: true, + + TableConvertor: apirest.NewDefaultTableConvertor(kubekeyv1alpha1.SchemeGroupVersion.WithResource("tasks").GroupResource()), + } + options := &apigeneric.StoreOptions{ + RESTOptions: optsGetter, + AttrFunc: GetAttrs, + TriggerFunc: map[string]apistorage.IndexerFunc{"ownerReferences:pipeline": OwnerPipelineTriggerFunc}, + Indexers: Indexers(), + } + if err := store.CompleteWithOptions(options); err != nil { + return TaskStorage{}, err + } + + return TaskStorage{ + Task: &REST{store}, + TaskStatus: &StatusREST{store}, + }, nil +} diff --git a/pkg/proxy/resources/task/strategy.go b/pkg/proxy/resources/task/strategy.go new file mode 100644 index 00000000..a55d751b --- /dev/null +++ b/pkg/proxy/resources/task/strategy.go @@ -0,0 +1,194 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "context" + "fmt" + "reflect" + + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation/field" + apigeneric "k8s.io/apiserver/pkg/registry/generic" + apistorage "k8s.io/apiserver/pkg/storage" + apinames "k8s.io/apiserver/pkg/storage/names" + "k8s.io/client-go/tools/cache" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + + kubekeyv1alpha1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1alpha1" + _const "github.com/kubesphere/kubekey/v4/pkg/const" +) + +// taskStrategy implements behavior for Pods +type taskStrategy struct { + runtime.ObjectTyper + apinames.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Pod +// objects via the REST API. +var Strategy = taskStrategy{_const.Scheme, apinames.SimpleNameGenerator} + +// ===CreateStrategy=== + +func (t taskStrategy) NamespaceScoped() bool { + return true +} + +func (t taskStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { + // init status when create + task := obj.(*kubekeyv1alpha1.Task) + task.Status = kubekeyv1alpha1.TaskStatus{ + Phase: kubekeyv1alpha1.TaskPhasePending, + } +} + +func (t taskStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { + // do nothing + return nil +} + +func (t taskStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + // do nothing + return nil +} + +func (t taskStrategy) Canonicalize(obj runtime.Object) { + // do nothing +} + +// ===UpdateStrategy=== + +func (t taskStrategy) AllowCreateOnUpdate() bool { + return false +} + +func (t taskStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + // do nothing +} + +func (t taskStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + // only support update status + task := obj.(*kubekeyv1alpha1.Task) + oldTask := old.(*kubekeyv1alpha1.Task) + if !reflect.DeepEqual(task.Spec, oldTask.Spec) { + return field.ErrorList{field.Forbidden(field.NewPath("spec"), "spec is immutable")} + } + return nil +} + +func (t taskStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + // do nothing + return nil +} + +func (t taskStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// ===ResetFieldsStrategy=== + +func (t taskStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + return nil +} + +// OwnerPipelineIndexFunc return value ownerReference.object is pipeline. +func OwnerPipelineIndexFunc(obj interface{}) ([]string, error) { + task, ok := obj.(*kubekeyv1alpha1.Task) + if !ok { + return nil, fmt.Errorf("not a task") + } + + var index string + for _, reference := range task.OwnerReferences { + if reference.Kind == "Pipeline" { + index = types.NamespacedName{ + Namespace: task.Namespace, + Name: reference.Name, + }.String() + break + } + } + if index == "" { + return nil, fmt.Errorf("task has no ownerReference.pipeline") + } + + return []string{index}, nil +} + +// Indexers returns the indexers for pod storage. +func Indexers() *cache.Indexers { + return &cache.Indexers{ + apistorage.FieldIndex("ownerReferences:pipeline"): OwnerPipelineIndexFunc, + } +} + +// MatchTask returns a generic matcher for a given label and field selector. +func MatchTask(label labels.Selector, field fields.Selector) apistorage.SelectionPredicate { + return apistorage.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: GetAttrs, + IndexFields: []string{"ownerReferences:pipeline"}, + } +} + +// GetAttrs returns labels and fields of a given object for filtering purposes. +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { + task, ok := obj.(*kubekeyv1alpha1.Task) + if !ok { + return nil, nil, fmt.Errorf("not a Task") + } + return labels.Set(task.ObjectMeta.Labels), ToSelectableFields(task), nil +} + +// ToSelectableFields returns a field set that represents the object +// TODO: fields are not labels, and the validation rules for them do not apply. +func ToSelectableFields(task *kubekeyv1alpha1.Task) fields.Set { + // The purpose of allocation with a given number of elements is to reduce + // amount of allocations needed to create the fields.Set. If you add any + // field here or the number of object-meta related fields changes, this should + // be adjusted. + taskSpecificFieldsSet := make(fields.Set, 10) + for _, reference := range task.OwnerReferences { + if reference.Kind == "Pipeline" { + taskSpecificFieldsSet["ownerReferences:pipeline"] = types.NamespacedName{ + Namespace: task.Namespace, + Name: reference.Name, + }.String() + break + } + } + return apigeneric.AddObjectMetaFieldsSet(taskSpecificFieldsSet, &task.ObjectMeta, true) +} + +// OwnerPipelineTriggerFunc returns value ownerReference is pipeline of given object. +func OwnerPipelineTriggerFunc(obj runtime.Object) string { + task := obj.(*kubekeyv1alpha1.Task) + for _, reference := range task.OwnerReferences { + if reference.Kind == "Pipeline" { + return types.NamespacedName{ + Namespace: task.Namespace, + Name: reference.Name, + }.String() + } + } + return "" +} diff --git a/pkg/proxy/router.go b/pkg/proxy/router.go new file mode 100644 index 00000000..692d0ba6 --- /dev/null +++ b/pkg/proxy/router.go @@ -0,0 +1,53 @@ +package proxy + +import "net/http" + +type router struct { + path string // The path of the action + pathExpr *pathExpression // cached compilation of rootPath as RegExp + handlers map[string]http.HandlerFunc +} + +func (r router) matchPath(url string) bool { + return r.pathExpr.Matcher.MatchString(url) +} + +// Types and functions to support the sorting of Dispatchers + +type dispatcherCandidate struct { + router router + finalMatch string + matchesCount int // the number of capturing groups + literalCount int // the number of literal characters (means those not resulting from template variable substitution) + nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’) +} +type sortableDispatcherCandidates struct { + candidates []dispatcherCandidate +} + +func (dc *sortableDispatcherCandidates) Len() int { + return len(dc.candidates) +} +func (dc *sortableDispatcherCandidates) Swap(i, j int) { + dc.candidates[i], dc.candidates[j] = dc.candidates[j], dc.candidates[i] +} +func (dc *sortableDispatcherCandidates) Less(i, j int) bool { + ci := dc.candidates[i] + cj := dc.candidates[j] + // primary key + if ci.matchesCount < cj.matchesCount { + return true + } + if ci.matchesCount > cj.matchesCount { + return false + } + // secundary key + if ci.literalCount < cj.literalCount { + return true + } + if ci.literalCount > cj.literalCount { + return false + } + // tertiary key + return ci.nonDefaultCount < cj.nonDefaultCount +} diff --git a/pkg/proxy/runtime_client.go b/pkg/proxy/runtime_client.go deleted file mode 100644 index fe0effc7..00000000 --- a/pkg/proxy/runtime_client.go +++ /dev/null @@ -1,349 +0,0 @@ -/* -Copyright 2023 The KubeSphere Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package proxy - -import ( - "context" - "fmt" - "io/fs" - "os" - "path/filepath" - - apimeta "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/klog/v2" - ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/apiutil" - "sigs.k8s.io/yaml" - - kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" - kubekeyv1alpha1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1alpha1" - _const "github.com/kubesphere/kubekey/v4/pkg/const" -) - -type delegatingClient struct { - client ctrlclient.Client - scheme *runtime.Scheme -} - -func NewDelegatingClient(client ctrlclient.Client) ctrlclient.Client { - scheme := runtime.NewScheme() - if err := kubekeyv1.AddToScheme(scheme); err != nil { - klog.ErrorS(err, "failed to add scheme", "gv", kubekeyv1.SchemeGroupVersion) - } - kubekeyv1.SchemeBuilder.Register(&kubekeyv1alpha1.Task{}, &kubekeyv1alpha1.TaskList{}) - return &delegatingClient{ - client: client, - scheme: scheme, - } -} - -func (d delegatingClient) Get(ctx context.Context, key ctrlclient.ObjectKey, obj ctrlclient.Object, opts ...ctrlclient.GetOption) error { - resource := _const.ResourceFromObject(obj) - if d.client != nil && resource != _const.RuntimePipelineTaskDir { - return d.client.Get(ctx, key, obj, opts...) - } - if resource == "" { - return fmt.Errorf("unsupported object type: %s", obj.GetObjectKind().GroupVersionKind().String()) - } - - path := filepath.Join(_const.GetWorkDir(), _const.RuntimeDir, key.Namespace, resource, key.Name, key.Name+".yaml") - data, err := os.ReadFile(path) - if err != nil { - klog.ErrorS(err, "failed to read yaml file", "path", path) - return err - } - if err := yaml.Unmarshal(data, obj); err != nil { - klog.ErrorS(err, "failed to unmarshal yaml file", "path", path) - return err - } - return nil -} - -func (d delegatingClient) List(ctx context.Context, list ctrlclient.ObjectList, opts ...ctrlclient.ListOption) error { - resource := _const.ResourceFromObject(list) - if d.client != nil && resource != _const.RuntimePipelineTaskDir { - return d.client.List(ctx, list, opts...) - } - if resource == "" { - return fmt.Errorf("unsupported object type: %s", list.GetObjectKind().GroupVersionKind().String()) - } - // read all runtime.Object - var objects []runtime.Object - runtimeDirEntries, err := os.ReadDir(filepath.Join(_const.GetWorkDir(), _const.RuntimeDir)) - if err != nil && !os.IsNotExist(err) { - klog.ErrorS(err, "failed to read dir", "path", filepath.Join(_const.GetWorkDir(), _const.RuntimeDir)) - return err - } - for _, re := range runtimeDirEntries { - if re.IsDir() { - resourceDir := filepath.Join(_const.GetWorkDir(), _const.RuntimeDir, re.Name(), resource) - entries, err := os.ReadDir(resourceDir) - if err != nil { - if os.IsNotExist(err) { - continue - } - klog.ErrorS(err, "failed to read dir", "path", resourceDir) - return err - } - for _, e := range entries { - if !e.IsDir() { - continue - } - resourceFile := filepath.Join(resourceDir, e.Name(), e.Name()+".yaml") - data, err := os.ReadFile(resourceFile) - if err != nil { - if os.IsNotExist(err) { - continue - } - klog.ErrorS(err, "failed to read file", "path", resourceFile) - return err - } - var obj runtime.Object - switch resource { - case _const.RuntimePipelineDir: - obj = &kubekeyv1.Pipeline{} - case _const.RuntimeInventoryDir: - obj = &kubekeyv1.Inventory{} - case _const.RuntimeConfigDir: - obj = &kubekeyv1.Config{} - case _const.RuntimePipelineTaskDir: - obj = &kubekeyv1alpha1.Task{} - } - if err := yaml.Unmarshal(data, &obj); err != nil { - klog.ErrorS(err, "failed to unmarshal yaml file", "path", resourceFile) - return err - } - objects = append(objects, obj) - } - } - } - - o := ctrlclient.ListOptions{} - o.ApplyOptions(opts) - - switch { - case o.Namespace != "": - for i := len(objects) - 1; i >= 0; i-- { - if objects[i].(metav1.Object).GetNamespace() != o.Namespace { - objects = append(objects[:i], objects[i+1:]...) - } - } - } - - if err := apimeta.SetList(list, objects); err != nil { - klog.ErrorS(err, "failed to set list") - return err - } - return nil -} - -func (d delegatingClient) Create(ctx context.Context, obj ctrlclient.Object, opts ...ctrlclient.CreateOption) error { - resource := _const.ResourceFromObject(obj) - if d.client != nil && resource != _const.RuntimePipelineTaskDir { - return d.client.Create(ctx, obj, opts...) - } - if resource == "" { - return fmt.Errorf("unsupported object type: %s", obj.GetObjectKind().GroupVersionKind().String()) - } - - data, err := yaml.Marshal(obj) - if err != nil { - klog.ErrorS(err, "failed to marshal object") - return err - } - if err := os.MkdirAll(filepath.Join(_const.GetWorkDir(), _const.RuntimeDir, obj.GetNamespace(), resource, obj.GetName()), fs.ModePerm); err != nil { - klog.ErrorS(err, "failed to create dir", "path", filepath.Join(_const.GetWorkDir(), _const.RuntimeDir, obj.GetNamespace(), resource, obj.GetName())) - return err - } - return os.WriteFile(filepath.Join(_const.GetWorkDir(), _const.RuntimeDir, obj.GetNamespace(), resource, obj.GetName(), obj.GetName()+".yaml"), data, fs.ModePerm) -} - -func (d delegatingClient) Delete(ctx context.Context, obj ctrlclient.Object, opts ...ctrlclient.DeleteOption) error { - resource := _const.ResourceFromObject(obj) - if d.client != nil && resource != _const.RuntimePipelineTaskDir { - return d.client.Delete(ctx, obj, opts...) - } - if resource == "" { - return fmt.Errorf("unsupported object type: %s", obj.GetObjectKind().GroupVersionKind().String()) - } - - return os.RemoveAll(filepath.Join(_const.GetWorkDir(), _const.RuntimeDir, obj.GetNamespace(), resource, obj.GetName())) -} - -func (d delegatingClient) Update(ctx context.Context, obj ctrlclient.Object, opts ...ctrlclient.UpdateOption) error { - resource := _const.ResourceFromObject(obj) - if d.client != nil && resource != _const.RuntimePipelineTaskDir { - return d.client.Update(ctx, obj, opts...) - } - if resource == "" { - return fmt.Errorf("unsupported object type: %s", obj.GetObjectKind().GroupVersionKind().String()) - } - - data, err := yaml.Marshal(obj) - if err != nil { - klog.Errorf("failed to marshal object: %v", err) - return err - } - return os.WriteFile(filepath.Join(_const.GetWorkDir(), _const.RuntimeDir, obj.GetNamespace(), resource, obj.GetName(), obj.GetName()+".yaml"), data, fs.ModePerm) -} - -func (d delegatingClient) Patch(ctx context.Context, obj ctrlclient.Object, patch ctrlclient.Patch, opts ...ctrlclient.PatchOption) error { - resource := _const.ResourceFromObject(obj) - if d.client != nil && resource != _const.RuntimePipelineTaskDir { - return d.client.Patch(ctx, obj, patch, opts...) - } - if resource == "" { - return fmt.Errorf("unsupported object type: %s", obj.GetObjectKind().GroupVersionKind().String()) - } - - patchData, err := patch.Data(obj) - if err != nil { - klog.ErrorS(err, "failed to get patch data") - return err - } - if len(patchData) == 0 { - klog.V(4).Info("nothing to patch, skip") - return nil - } - data, err := yaml.Marshal(obj) - if err != nil { - klog.ErrorS(err, "failed to marshal object") - return err - } - return os.WriteFile(filepath.Join(_const.GetWorkDir(), _const.RuntimeDir, obj.GetNamespace(), resource, obj.GetName(), obj.GetName()+".yaml"), data, fs.ModePerm) -} - -func (d delegatingClient) DeleteAllOf(ctx context.Context, obj ctrlclient.Object, opts ...ctrlclient.DeleteAllOfOption) error { - resource := _const.ResourceFromObject(obj) - if d.client != nil && resource != _const.RuntimePipelineTaskDir { - return d.client.DeleteAllOf(ctx, obj, opts...) - } - if resource == "" { - return fmt.Errorf("unsupported object type: %s", obj.GetObjectKind().GroupVersionKind().String()) - } - return d.Delete(ctx, obj) -} - -func (d delegatingClient) Status() ctrlclient.SubResourceWriter { - if d.client != nil { - return d.client.Status() - } - return &delegatingSubResourceWriter{client: d.client} -} - -func (d delegatingClient) SubResource(subResource string) ctrlclient.SubResourceClient { - if d.client != nil { - return d.client.SubResource(subResource) - } - return nil -} - -func (d delegatingClient) Scheme() *runtime.Scheme { - if d.client != nil { - return d.client.Scheme() - } - return d.scheme -} - -func (d delegatingClient) RESTMapper() apimeta.RESTMapper { - if d.client != nil { - return d.client.RESTMapper() - } - return nil -} - -func (d delegatingClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) { - if d.client != nil { - return d.client.GroupVersionKindFor(obj) - } - return apiutil.GVKForObject(obj, d.scheme) -} - -func (d delegatingClient) IsObjectNamespaced(obj runtime.Object) (bool, error) { - if d.client != nil { - return d.client.IsObjectNamespaced(obj) - } - return true, nil -} - -type delegatingSubResourceWriter struct { - client ctrlclient.Client -} - -func (d delegatingSubResourceWriter) Create(ctx context.Context, obj ctrlclient.Object, subResource ctrlclient.Object, opts ...ctrlclient.SubResourceCreateOption) error { - resource := _const.ResourceFromObject(obj) - if d.client != nil && resource != _const.RuntimePipelineTaskDir { - return d.client.Status().Create(ctx, obj, subResource, opts...) - } - if resource == "" { - return fmt.Errorf("unsupported object type: %s", obj.GetObjectKind().GroupVersionKind().String()) - } - - data, err := yaml.Marshal(obj) - if err != nil { - klog.ErrorS(err, "failed to marshal object") - return err - } - return os.WriteFile(filepath.Join(_const.GetWorkDir(), _const.RuntimeDir, obj.GetNamespace(), resource, obj.GetName(), obj.GetName()+".yaml"), data, fs.ModePerm) - -} - -func (d delegatingSubResourceWriter) Update(ctx context.Context, obj ctrlclient.Object, opts ...ctrlclient.SubResourceUpdateOption) error { - resource := _const.ResourceFromObject(obj) - if d.client != nil && resource != _const.RuntimePipelineTaskDir { - return d.client.Status().Update(ctx, obj, opts...) - } - if resource == "" { - return fmt.Errorf("unsupported object type: %s", obj.GetObjectKind().GroupVersionKind().String()) - } - - data, err := yaml.Marshal(obj) - if err != nil { - klog.ErrorS(err, "failed to marshal object") - return err - } - return os.WriteFile(filepath.Join(_const.GetWorkDir(), _const.RuntimeDir, obj.GetNamespace(), resource, obj.GetName(), obj.GetName()+".yaml"), data, fs.ModePerm) -} - -func (d delegatingSubResourceWriter) Patch(ctx context.Context, obj ctrlclient.Object, patch ctrlclient.Patch, opts ...ctrlclient.SubResourcePatchOption) error { - resource := _const.ResourceFromObject(obj) - if d.client != nil && resource != _const.RuntimePipelineTaskDir { - return d.client.Status().Patch(ctx, obj, patch, opts...) - } - if resource == "" { - return fmt.Errorf("unsupported object type: %s", obj.GetObjectKind().GroupVersionKind().String()) - } - - patchData, err := patch.Data(obj) - if err != nil { - klog.ErrorS(err, "failed to get patch data") - return err - } - if len(patchData) == 0 { - klog.V(4).Info("nothing to patch, skip") - return nil - } - data, err := yaml.Marshal(obj) - if err != nil { - klog.ErrorS(err, "failed to marshal object") - return err - } - return os.WriteFile(filepath.Join(_const.GetWorkDir(), _const.RuntimeDir, obj.GetNamespace(), resource, obj.GetName(), obj.GetName()+".yaml"), data, fs.ModePerm) -} diff --git a/pkg/proxy/transport.go b/pkg/proxy/transport.go new file mode 100644 index 00000000..9c28c7e6 --- /dev/null +++ b/pkg/proxy/transport.go @@ -0,0 +1,446 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package proxy + +import ( + "bytes" + "fmt" + "io" + "net/http" + "sort" + "strings" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/managedfields" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/authorization/authorizerfactory" + apiendpoints "k8s.io/apiserver/pkg/endpoints" + genericapifilters "k8s.io/apiserver/pkg/endpoints/filters" + apihandlers "k8s.io/apiserver/pkg/endpoints/handlers" + apirequest "k8s.io/apiserver/pkg/endpoints/request" + apirest "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/client-go/rest" + "k8s.io/klog/v2" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + + kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" + kubekeyv1alpha1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1alpha1" + _const "github.com/kubesphere/kubekey/v4/pkg/const" + "github.com/kubesphere/kubekey/v4/pkg/proxy/internal" + "github.com/kubesphere/kubekey/v4/pkg/proxy/resources/config" + "github.com/kubesphere/kubekey/v4/pkg/proxy/resources/inventory" + "github.com/kubesphere/kubekey/v4/pkg/proxy/resources/pipeline" + "github.com/kubesphere/kubekey/v4/pkg/proxy/resources/task" +) + +func NewLocalClient() (ctrlclient.Client, error) { + lt, err := NewProxyTransport(true) + if err != nil { + klog.V(4).ErrorS(err, "failed to create local transport") + return nil, err + } + return ctrlclient.New(&rest.Config{ + Transport: lt, + }, ctrlclient.Options{ + Scheme: _const.Scheme, + }) +} + +func NewProxyTransport(isLocal bool) (http.RoundTripper, error) { + lt := &transport{ + isLocal: isLocal, + authz: authorizerfactory.NewAlwaysAllowAuthorizer(), + handlerChainFunc: defaultHandlerChain, + } + // register kubekeyv1alpha1 resources + kkv1alpha1 := newApiIResources(kubekeyv1alpha1.SchemeGroupVersion) + storage, err := task.NewStorage(internal.NewFileRESTOptionsGetter(kubekeyv1alpha1.SchemeGroupVersion)) + if err != nil { + klog.V(4).ErrorS(err, "failed to create storage") + return nil, err + } + if err := kkv1alpha1.AddResource(resourceOptions{ + path: "tasks", + storage: storage.Task, + }); err != nil { + klog.V(4).ErrorS(err, "failed to add resource") + return nil, err + } + if err := kkv1alpha1.AddResource(resourceOptions{ + path: "tasks/status", + storage: storage.TaskStatus, + }); err != nil { + klog.V(4).ErrorS(err, "failed to add resource") + return nil, err + } + if err := lt.registerResources(kkv1alpha1); err != nil { + klog.V(4).ErrorS(err, "failed to register resources") + } + if isLocal { + // register kubekeyv1 resources + kkv1 := newApiIResources(kubekeyv1.SchemeGroupVersion) + // add config + configStorage, err := config.NewStorage(internal.NewFileRESTOptionsGetter(kubekeyv1.SchemeGroupVersion)) + if err != nil { + klog.V(4).ErrorS(err, "failed to create storage") + return nil, err + } + if err := kkv1.AddResource(resourceOptions{ + path: "configs", + storage: configStorage.Config, + }); err != nil { + klog.V(4).ErrorS(err, "failed to add resource") + return nil, err + } + // add inventory + inventoryStorage, err := inventory.NewStorage(internal.NewFileRESTOptionsGetter(kubekeyv1.SchemeGroupVersion)) + if err != nil { + klog.V(4).ErrorS(err, "failed to create storage") + return nil, err + } + if err := kkv1.AddResource(resourceOptions{ + path: "inventories", + storage: inventoryStorage.Inventory, + }); err != nil { + klog.V(4).ErrorS(err, "failed to add resource") + return nil, err + } + // add pipeline + pipelineStorage, err := pipeline.NewStorage(internal.NewFileRESTOptionsGetter(kubekeyv1.SchemeGroupVersion)) + if err != nil { + klog.V(4).ErrorS(err, "failed to create storage") + return nil, err + } + if err := kkv1.AddResource(resourceOptions{ + path: "pipelines", + storage: pipelineStorage.Pipeline, + }); err != nil { + klog.V(4).ErrorS(err, "failed to add resource") + return nil, err + } + if err := kkv1.AddResource(resourceOptions{ + path: "pipelines/status", + storage: pipelineStorage.PipelineStatus, + }); err != nil { + klog.V(4).ErrorS(err, "failed to add resource") + return nil, err + } + + if err := lt.registerResources(kkv1); err != nil { + klog.V(4).ErrorS(err, "failed to register resources") + return nil, err + } + } + + return lt, nil +} + +type responseWriter struct { + *http.Response +} + +func (r *responseWriter) Header() http.Header { + return r.Response.Header +} + +func (r *responseWriter) Write(bs []byte) (int, error) { + r.Response.Body = io.NopCloser(bytes.NewBuffer(bs)) + return 0, nil +} + +func (r *responseWriter) WriteHeader(statusCode int) { + r.Response.StatusCode = statusCode +} + +type transport struct { + // isLocal represent the transport use local file client or http client + isLocal bool + + authz authorizer.Authorizer + // routers is a list of routers + routers []router + + // handlerChain will be called after each request. + handlerChainFunc func(handler http.Handler) http.Handler +} + +func (l *transport) RoundTrip(request *http.Request) (*http.Response, error) { + // kubekey.v1alpha1 always use local client + if !l.isLocal && !strings.HasPrefix(request.URL.Path, "/apis/"+kubekeyv1alpha1.SchemeGroupVersion.String()+"/") { + return http.DefaultTransport.RoundTrip(request) + } + response := &http.Response{ + Proto: "local", + Header: make(http.Header), + } + // dispatch request + handler, err := l.detectDispatcher(request) + if err != nil { + return response, fmt.Errorf("no router for request. url: %s, method: %s", request.URL.Path, request.Method) + } + // call handler + l.handlerChainFunc(handler).ServeHTTP(&responseWriter{response}, request) + return response, nil +} + +// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 1) +func (l transport) detectDispatcher(request *http.Request) (http.HandlerFunc, error) { + filtered := &sortableDispatcherCandidates{} + for _, each := range l.routers { + matches := each.pathExpr.Matcher.FindStringSubmatch(request.URL.Path) + if matches != nil { + filtered.candidates = append(filtered.candidates, + dispatcherCandidate{each, matches[len(matches)-1], len(matches), each.pathExpr.LiteralCount, each.pathExpr.VarCount}) + } + } + if len(filtered.candidates) == 0 { + return nil, fmt.Errorf("not found") + } + sort.Sort(sort.Reverse(filtered)) + + handler, ok := filtered.candidates[0].router.handlers[request.Method] + if !ok { + return nil, fmt.Errorf("not found") + } + return handler, nil +} + +func (l *transport) registerResources(resources *apiResources) error { + // register apiResources router + l.registerRouter(http.MethodGet, resources.prefix, resources.handlerApiResources(), true) + // register resources router + for _, o := range resources.resourceOptions { + // what verbs are supported by the storage, used to know what verbs we support per path + creater, isCreater := o.storage.(apirest.Creater) + namedCreater, isNamedCreater := o.storage.(apirest.NamedCreater) + lister, isLister := o.storage.(apirest.Lister) + getter, isGetter := o.storage.(apirest.Getter) + getterWithOptions, isGetterWithOptions := o.storage.(apirest.GetterWithOptions) + gracefulDeleter, isGracefulDeleter := o.storage.(apirest.GracefulDeleter) + collectionDeleter, isCollectionDeleter := o.storage.(apirest.CollectionDeleter) + updater, isUpdater := o.storage.(apirest.Updater) + patcher, isPatcher := o.storage.(apirest.Patcher) + watcher, isWatcher := o.storage.(apirest.Watcher) + connecter, isConnecter := o.storage.(apirest.Connecter) + tableProvider, isTableProvider := o.storage.(apirest.TableConvertor) + if isLister && !isTableProvider { + // All listers must implement TableProvider + return fmt.Errorf("%q must implement TableConvertor", o.path) + } + gvAcceptor, _ := o.storage.(apirest.GroupVersionAcceptor) + + if isNamedCreater { + isCreater = true + } + + allowWatchList := isWatcher && isLister + var ( + connectSubpath bool + getSubpath bool + ) + if isConnecter { + _, connectSubpath, _ = connecter.NewConnectOptions() + } + if isGetterWithOptions { + _, getSubpath, _ = getterWithOptions.NewGetOptions() + } + resource, subresource, err := splitSubresource(o.path) + if err != nil { + return err + } + isSubresource := len(subresource) > 0 + scoper, ok := o.storage.(apirest.Scoper) + if !ok { + return fmt.Errorf("%q must implement scoper", o.path) + } + + // Get the list of actions for the given scope. + switch { + case !scoper.NamespaceScoped(): + // do nothing. The current managed resources are all namespace scope. + default: + resourcePath := "/namespaces/{namespace}/" + resource + itemPath := resourcePath + "/{name}" + if isSubresource { + itemPath = itemPath + "/" + subresource + resourcePath = itemPath + } + // request scope + fqKindToRegister, err := apiendpoints.GetResourceKind(resources.gv, o.storage, _const.Scheme) + if err != nil { + return err + } + reqScope := apihandlers.RequestScope{ + Namer: apihandlers.ContextBasedNaming{ + Namer: meta.NewAccessor(), + ClusterScoped: false, + }, + Serializer: _const.Codecs, + ParameterCodec: _const.ParameterCodec, + Creater: _const.Scheme, + Convertor: _const.Scheme, + Defaulter: _const.Scheme, + Typer: _const.Scheme, + UnsafeConvertor: _const.Scheme, + Authorizer: l.authz, + + EquivalentResourceMapper: runtime.NewEquivalentResourceRegistry(), + + // TODO: Check for the interface on storage + TableConvertor: tableProvider, + + // TODO: This seems wrong for cross-group subresources. It makes an assumption that a subresource and its parent are in the same group version. Revisit this. + Resource: resources.gv.WithResource(resource), + Subresource: subresource, + Kind: fqKindToRegister, + + AcceptsGroupVersionDelegate: gvAcceptor, + + HubGroupVersion: schema.GroupVersion{Group: fqKindToRegister.Group, Version: runtime.APIVersionInternal}, + + MetaGroupVersion: metav1.SchemeGroupVersion, + + MaxRequestBodyBytes: 0, + } + var resetFields map[fieldpath.APIVersion]*fieldpath.Set + if resetFieldsStrategy, isResetFieldsStrategy := o.storage.(apirest.ResetFieldsStrategy); isResetFieldsStrategy { + resetFields = resetFieldsStrategy.GetResetFields() + } + reqScope.FieldManager, err = managedfields.NewDefaultFieldManager( + managedfields.NewDeducedTypeConverter(), + _const.Scheme, + _const.Scheme, + _const.Scheme, + fqKindToRegister, + reqScope.HubGroupVersion, + subresource, + resetFields, + ) + if err != nil { + return err + } + + // LIST + l.registerRouter(http.MethodGet, resources.prefix+resourcePath, apihandlers.ListResource(lister, watcher, &reqScope, false, resources.minRequestTimeout), isLister) + // POST + if isNamedCreater { + l.registerRouter(http.MethodPost, resources.prefix+resourcePath, apihandlers.CreateNamedResource(namedCreater, &reqScope, o.admit), isCreater) + } else { + l.registerRouter(http.MethodPost, resources.prefix+resourcePath, apihandlers.CreateResource(creater, &reqScope, o.admit), isCreater) + } + // DELETECOLLECTION + l.registerRouter(http.MethodDelete, resources.prefix+resourcePath, apihandlers.DeleteCollection(collectionDeleter, isCollectionDeleter, &reqScope, o.admit), isCollectionDeleter) + // DEPRECATED in 1.11 WATCHLIST + l.registerRouter(http.MethodGet, resources.prefix+"/watch"+resourcePath, apihandlers.ListResource(lister, watcher, &reqScope, true, resources.minRequestTimeout), allowWatchList) + // GET + if isGetterWithOptions { + l.registerRouter(http.MethodGet, resources.prefix+itemPath, apihandlers.GetResourceWithOptions(getterWithOptions, &reqScope, isSubresource), isGetter) + l.registerRouter(http.MethodGet, resources.prefix+itemPath+"/{path:*}", apihandlers.GetResourceWithOptions(getterWithOptions, &reqScope, isSubresource), isGetter && getSubpath) + } else { + l.registerRouter(http.MethodGet, resources.prefix+itemPath, apihandlers.GetResource(getter, &reqScope), isGetter) + l.registerRouter(http.MethodGet, resources.prefix+itemPath+"/{path:*}", apihandlers.GetResource(getter, &reqScope), isGetter && getSubpath) + } + // PUT + l.registerRouter(http.MethodPut, resources.prefix+itemPath, apihandlers.UpdateResource(updater, &reqScope, o.admit), isUpdater) + // PATCH + supportedTypes := []string{ + string(types.JSONPatchType), + string(types.MergePatchType), + string(types.StrategicMergePatchType), + string(types.ApplyPatchType), + } + l.registerRouter(http.MethodPatch, resources.prefix+itemPath, apihandlers.PatchResource(patcher, &reqScope, o.admit, supportedTypes), isPatcher) + // DELETE + l.registerRouter(http.MethodDelete, resources.prefix+itemPath, apihandlers.DeleteResource(gracefulDeleter, isGracefulDeleter, &reqScope, o.admit), isGracefulDeleter) + // DEPRECATED in 1.11 WATCH + l.registerRouter(http.MethodGet, resources.prefix+"/watch"+itemPath, apihandlers.ListResource(lister, watcher, &reqScope, true, resources.minRequestTimeout), isWatcher) + // CONNECT + l.registerRouter(http.MethodConnect, resources.prefix+itemPath, apihandlers.ConnectResource(connecter, &reqScope, o.admit, o.path, isSubresource), isConnecter) + l.registerRouter(http.MethodConnect, resources.prefix+itemPath+"/{path:*}", apihandlers.ConnectResource(connecter, &reqScope, o.admit, o.path, isSubresource), isConnecter && connectSubpath) + // list or post across namespace. + // For ex: LIST all pods in all namespaces by sending a LIST request at /api/apiVersion/pods. + // LIST + l.registerRouter(http.MethodGet, resources.prefix+"/"+resource, apihandlers.ListResource(lister, watcher, &reqScope, false, resources.minRequestTimeout), !isSubresource && isLister) + // WATCHLIST + l.registerRouter(http.MethodGet, resources.prefix+"/watch/"+resource, apihandlers.ListResource(lister, watcher, &reqScope, true, resources.minRequestTimeout), !isSubresource && allowWatchList) + } + } + return nil +} + +func (l *transport) registerRouter(verb string, path string, handler http.HandlerFunc, shouldAdd bool) { + if !shouldAdd { + // if the router should not be added. return + return + } + for i, r := range l.routers { + if r.path != path { + continue + } + // add handler to router + if _, ok := r.handlers[verb]; ok { + // if handler is exists. throw error + klog.V(4).ErrorS(fmt.Errorf("handler has already register"), "failed to register router", "path", path, "verb", verb) + return + } + l.routers[i].handlers[verb] = handler + return + } + + // add new router + expression, err := newPathExpression(path) + if err != nil { + klog.V(4).ErrorS(err, "failed to register router", "path", path, "verb", verb) + return + } + l.routers = append(l.routers, router{ + path: path, + pathExpr: expression, + handlers: map[string]http.HandlerFunc{ + verb: handler, + }, + }) +} + +// splitSubresource checks if the given storage path is the path of a subresource and returns +// the resource and subresource components. +func splitSubresource(path string) (string, string, error) { + var resource, subresource string + switch parts := strings.Split(path, "/"); len(parts) { + case 2: + resource, subresource = parts[0], parts[1] + case 1: + resource = parts[0] + default: + return "", "", fmt.Errorf("api_installer allows only one or two segment paths (resource or resource/subresource)") + } + return resource, subresource, nil +} + +var defaultRequestInfoResolver = &apirequest.RequestInfoFactory{ + APIPrefixes: sets.NewString("apis"), +} + +func defaultHandlerChain(handler http.Handler) http.Handler { + return genericapifilters.WithRequestInfo(handler, defaultRequestInfoResolver) +} diff --git a/pkg/proxy/transport_test.go b/pkg/proxy/transport_test.go new file mode 100644 index 00000000..7c9925e8 --- /dev/null +++ b/pkg/proxy/transport_test.go @@ -0,0 +1,150 @@ +/* +Copyright 2024 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package proxy + +import ( + "context" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + kubekeyv1alpha1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1alpha1" + _const "github.com/kubesphere/kubekey/v4/pkg/const" +) + +func TestTransport(t *testing.T) { + cwd, err := os.Getwd() + assert.NoError(t, err) + _const.SetWorkDir(cwd) + // create runtimeDir + if _, err := os.Stat(_const.RuntimeDir); err != nil && os.IsNotExist(err) { + err = os.Mkdir(_const.RuntimeDir, os.ModePerm) + assert.NoError(t, err) + } + defer os.RemoveAll(_const.RuntimeDir) + + cli, err := NewLocalClient() + assert.NoError(t, err) + + testcases := []struct { + name string + fn func() error + }{ + { + name: "create task", + fn: func() error { + return cli.Create(context.Background(), &kubekeyv1alpha1.Task{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: kubekeyv1alpha1.KubeKeyTaskSpec{ + Name: "test", + }, + }) + }, + }, + { + name: "get task", + fn: func() error { + task := &kubekeyv1alpha1.Task{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: kubekeyv1alpha1.KubeKeyTaskSpec{ + Name: "test", + }, + } + cli.Create(context.Background(), task) + return cli.Get(context.Background(), ctrlclient.ObjectKeyFromObject(task), task) + }, + }, + { + name: "list task", + fn: func() error { + cli.Create(context.Background(), &kubekeyv1alpha1.Task{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: kubekeyv1alpha1.KubeKeyTaskSpec{ + Name: "test", + }, + }) + tasklist := &kubekeyv1alpha1.TaskList{} + return cli.List(context.Background(), tasklist) + }, + }, + { + name: "update task", + fn: func() error { + cli.Create(context.Background(), &kubekeyv1alpha1.Task{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: kubekeyv1alpha1.KubeKeyTaskSpec{ + Name: "test", + }, + }) + if err := cli.Update(context.Background(), &kubekeyv1alpha1.Task{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: kubekeyv1alpha1.KubeKeyTaskSpec{ + Name: "test1", + }, + }); err != nil && !strings.Contains(err.Error(), "spec is immutable") { + return err + } + return nil + }, + }, + { + name: "delete task", + fn: func() error { + cli.Create(context.Background(), &kubekeyv1alpha1.Task{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: kubekeyv1alpha1.KubeKeyTaskSpec{ + Name: "test", + }, + }) + return cli.Delete(context.Background(), &kubekeyv1alpha1.Task{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + }) + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + assert.NoError(t, tc.fn()) + }) + } +} diff --git a/pkg/task/controller.go b/pkg/task/controller.go index e2a192c8..758d448b 100644 --- a/pkg/task/controller.go +++ b/pkg/task/controller.go @@ -20,6 +20,7 @@ import ( "context" "golang.org/x/time/rate" + "k8s.io/apimachinery/pkg/runtime" cgcache "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" @@ -45,6 +46,7 @@ type AddTaskOptions struct { } type ControllerOptions struct { + *runtime.Scheme MaxConcurrent int ctrlclient.Client TaskReconciler reconcile.Reconciler @@ -56,10 +58,15 @@ func NewController(o ControllerOptions) (Controller, error) { o.MaxConcurrent = 1 } if o.Client == nil { - o.Client = proxy.NewDelegatingClient(nil) + var err error + o.Client, err = proxy.NewLocalClient() + if err != nil { + return nil, err + } } return &taskController{ + schema: o.Scheme, MaxConcurrent: o.MaxConcurrent, wq: workqueue.NewRateLimitingQueue(&workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)}), client: o.Client, diff --git a/pkg/task/helper.go b/pkg/task/helper.go index b96dff43..91d02915 100644 --- a/pkg/task/helper.go +++ b/pkg/task/helper.go @@ -32,12 +32,12 @@ import ( func getGatherFact(ctx context.Context, hostname string, vars variable.Variable) (variable.VariableData, error) { v, err := vars.Get(variable.HostVars{HostName: hostname}) if err != nil { - klog.ErrorS(err, "Get host variable error", "hostname", hostname) + klog.V(4).ErrorS(err, "Get host variable error", "hostname", hostname) return nil, err } conn := connector.NewConnector(hostname, v.(variable.VariableData)) if err := conn.Init(ctx); err != nil { - klog.ErrorS(err, "Init connection error", "hostname", hostname) + klog.V(4).ErrorS(err, "Init connection error", "hostname", hostname) return nil, err } defer conn.Close(ctx) @@ -46,25 +46,25 @@ func getGatherFact(ctx context.Context, hostname string, vars variable.Variable) osVars := make(variable.VariableData) var osRelease bytes.Buffer if err := conn.FetchFile(ctx, "/etc/os-release", &osRelease); err != nil { - klog.ErrorS(err, "Fetch os-release error", "hostname", hostname) + klog.V(4).ErrorS(err, "Fetch os-release error", "hostname", hostname) return nil, err } osVars["release"] = convertBytesToMap(osRelease.Bytes(), "=") kernel, err := conn.ExecuteCommand(ctx, "uname -r") if err != nil { - klog.ErrorS(err, "Get kernel version error", "hostname", hostname) + klog.V(4).ErrorS(err, "Get kernel version error", "hostname", hostname) return nil, err } osVars["kernelVersion"] = string(bytes.TrimSuffix(kernel, []byte("\n"))) hn, err := conn.ExecuteCommand(ctx, "hostname") if err != nil { - klog.ErrorS(err, "Get hostname error", "hostname", hostname) + klog.V(4).ErrorS(err, "Get hostname error", "hostname", hostname) return nil, err } osVars["hostname"] = string(bytes.TrimSuffix(hn, []byte("\n"))) arch, err := conn.ExecuteCommand(ctx, "arch") if err != nil { - klog.ErrorS(err, "Get arch error", "hostname", hostname) + klog.V(4).ErrorS(err, "Get arch error", "hostname", hostname) return nil, err } osVars["architecture"] = string(bytes.TrimSuffix(arch, []byte("\n"))) @@ -73,13 +73,13 @@ func getGatherFact(ctx context.Context, hostname string, vars variable.Variable) procVars := make(variable.VariableData) var cpu bytes.Buffer if err := conn.FetchFile(ctx, "/proc/cpuinfo", &cpu); err != nil { - klog.ErrorS(err, "Fetch cpuinfo error", "hostname", hostname) + klog.V(4).ErrorS(err, "Fetch cpuinfo error", "hostname", hostname) return nil, err } procVars["cpuInfo"] = convertBytesToSlice(cpu.Bytes(), ":") var mem bytes.Buffer if err := conn.FetchFile(ctx, "/proc/meminfo", &mem); err != nil { - klog.ErrorS(err, "Fetch meminfo error", "hostname", hostname) + klog.V(4).ErrorS(err, "Fetch meminfo error", "hostname", hostname) return nil, err } procVars["memInfo"] = convertBytesToMap(mem.Bytes(), ":") diff --git a/pkg/task/internal.go b/pkg/task/internal.go index ac48c59b..90e9fd72 100644 --- a/pkg/task/internal.go +++ b/pkg/task/internal.go @@ -29,6 +29,7 @@ import ( "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" kkcorev1 "github.com/kubesphere/kubekey/v4/pkg/apis/core/v1" @@ -41,6 +42,7 @@ import ( ) type taskController struct { + schema *runtime.Scheme client ctrlclient.Client taskReconciler reconcile.Reconciler @@ -54,7 +56,7 @@ func (c *taskController) AddTasks(ctx context.Context, o AddTaskOptions) error { var nsTasks = &kubekeyv1alpha1.TaskList{} if err := c.client.List(ctx, nsTasks, ctrlclient.InNamespace(o.Pipeline.Namespace)); err != nil { - klog.ErrorS(err, "List tasks error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) + klog.V(4).ErrorS(err, "List tasks error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) return err } defer func() { @@ -82,7 +84,7 @@ func (c *taskController) AddTasks(ctx context.Context, o AddTaskOptions) error { if len(nsTasks.Items) == 0 { vars, ok, err := c.variableCache.GetByKey(string(o.Pipeline.UID)) if err != nil { - klog.ErrorS(err, "Get variable from store error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) + klog.V(4).ErrorS(err, "Get variable from store error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) return err } // if tasks has not generated. generate tasks from pipeline @@ -96,11 +98,11 @@ func (c *taskController) AddTasks(ctx context.Context, o AddTaskOptions) error { Pipeline: *o.Pipeline, }) if err != nil { - klog.ErrorS(err, "Create variable error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) + klog.V(4).ErrorS(err, "Create variable error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) return err } if err := c.variableCache.Add(nv); err != nil { - klog.ErrorS(err, "Add variable to store error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) + klog.V(4).ErrorS(err, "Add variable to store error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) return err } o.variable = nv @@ -109,7 +111,7 @@ func (c *taskController) AddTasks(ctx context.Context, o AddTaskOptions) error { klog.V(4).InfoS("deal project", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) projectFs, err := project.New(project.Options{Pipeline: o.Pipeline}).FS(ctx, true) if err != nil { - klog.ErrorS(err, "Deal project error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) + klog.V(4).ErrorS(err, "Deal project error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) return err } @@ -126,7 +128,7 @@ func (c *taskController) AddTasks(ctx context.Context, o AddTaskOptions) error { // convert Hosts (group or host) to all hosts ahn, err := o.variable.Get(variable.Hostnames{Name: play.PlayHost.Hosts}) if err != nil { - klog.ErrorS(err, "Get all host name error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) + klog.V(4).ErrorS(err, "Get all host name error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) return err } @@ -135,7 +137,7 @@ func (c *taskController) AddTasks(ctx context.Context, o AddTaskOptions) error { for _, h := range ahn.([]string) { gfv, err := getGatherFact(ctx, h, o.variable) if err != nil { - klog.ErrorS(err, "Get gather fact error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "host", h) + klog.V(4).ErrorS(err, "Get gather fact error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "host", h) return err } if err := o.variable.Merge(variable.HostMerge{ @@ -143,7 +145,7 @@ func (c *taskController) AddTasks(ctx context.Context, o AddTaskOptions) error { LocationUID: "", Data: gfv, }); err != nil { - klog.ErrorS(err, "Merge gather fact error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "host", h) + klog.V(4).ErrorS(err, "Merge gather fact error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "host", h) return err } } @@ -157,7 +159,7 @@ func (c *taskController) AddTasks(ctx context.Context, o AddTaskOptions) error { // group hosts by serial. run the playbook by serial hs, err = converter.GroupHostBySerial(ahn.([]string), play.Serial.Data) if err != nil { - klog.ErrorS(err, "Group host by serial error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) + klog.V(4).ErrorS(err, "Group host by serial error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) return err } } @@ -166,18 +168,23 @@ func (c *taskController) AddTasks(ctx context.Context, o AddTaskOptions) error { for _, h := range hs { puid := uuid.NewString() if err := o.variable.Merge(variable.LocationMerge{ - Uid: puid, + UID: puid, Name: play.Name, Type: variable.BlockLocation, Vars: play.Vars, }); err != nil { return err } + if len(h) == 0 { + err := fmt.Errorf("host is empty") + klog.V(4).ErrorS(err, "Host is empty", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline)) + return err + } hctx := context.WithValue(ctx, _const.CtxBlockHosts, h) // generate task from pre tasks - preTasks, err := c.block2Task(hctx, o, play.PreTasks, nil, puid, variable.BlockLocation) + preTasks, err := c.createTasks(hctx, o, play.PreTasks, nil, puid, variable.BlockLocation) if err != nil { - klog.ErrorS(err, "Get pre task from play error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "play", play.Name) + klog.V(4).ErrorS(err, "Get pre task from play error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "play", play.Name) return err } nsTasks.Items = append(nsTasks.Items, preTasks...) @@ -185,103 +192,91 @@ func (c *taskController) AddTasks(ctx context.Context, o AddTaskOptions) error { for _, role := range play.Roles { ruid := uuid.NewString() if err := o.variable.Merge(variable.LocationMerge{ - ParentID: puid, - Uid: ruid, - Name: play.Name, - Type: variable.BlockLocation, - Vars: role.Vars, + ParentUID: puid, + UID: ruid, + Name: play.Name, + Type: variable.BlockLocation, + Vars: role.Vars, }); err != nil { return err } - roleTasks, err := c.block2Task(context.WithValue(hctx, _const.CtxBlockRole, role.Role), o, role.Block, role.When.Data, ruid, variable.BlockLocation) + roleTasks, err := c.createTasks(context.WithValue(hctx, _const.CtxBlockRole, role.Role), o, role.Block, role.When.Data, ruid, variable.BlockLocation) if err != nil { - klog.ErrorS(err, "Get role task from play error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "play", play.Name, "role", role.Role) + klog.V(4).ErrorS(err, "Get role task from play error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "play", play.Name, "role", role.Role) return err } nsTasks.Items = append(nsTasks.Items, roleTasks...) } // generate task from tasks - tasks, err := c.block2Task(hctx, o, play.Tasks, nil, puid, variable.BlockLocation) + tasks, err := c.createTasks(hctx, o, play.Tasks, nil, puid, variable.BlockLocation) if err != nil { - klog.ErrorS(err, "Get task from play error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "play", play.Name) + klog.V(4).ErrorS(err, "Get task from play error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "play", play.Name) return err } nsTasks.Items = append(nsTasks.Items, tasks...) // generate task from post tasks - postTasks, err := c.block2Task(hctx, o, play.Tasks, nil, puid, variable.BlockLocation) + postTasks, err := c.createTasks(hctx, o, play.Tasks, nil, puid, variable.BlockLocation) if err != nil { - klog.ErrorS(err, "Get post task from play error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "play", play.Name) + klog.V(4).ErrorS(err, "Get post task from play error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "play", play.Name) return err } nsTasks.Items = append(nsTasks.Items, postTasks...) } } - - for _, task := range nsTasks.Items { - if err := c.client.Create(ctx, &task); err != nil { - klog.ErrorS(err, "Create task error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "task", task.Name) - return err - } - } } return nil } -// block2Task convert ansible block to task -func (k *taskController) block2Task(ctx context.Context, o AddTaskOptions, ats []kkcorev1.Block, when []string, parentLocation string, locationType variable.LocationType) ([]kubekeyv1alpha1.Task, error) { +// createTasks convert ansible block to task +func (k *taskController) createTasks(ctx context.Context, o AddTaskOptions, ats []kkcorev1.Block, when []string, puid string, locationType variable.LocationType) ([]kubekeyv1alpha1.Task, error) { var tasks []kubekeyv1alpha1.Task - for _, at := range ats { if !at.Taggable.IsEnabled(o.Pipeline.Spec.Tags, o.Pipeline.Spec.SkipTags) { continue } - buid := uuid.NewString() - if err := o.variable.Merge(variable.LocationMerge{ - Uid: buid, - ParentID: parentLocation, - Type: locationType, - Name: at.Name, - Vars: at.Vars, - }); err != nil { - klog.ErrorS(err, "Merge block to variable error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "block", at.Name) - return nil, err - } - atWhen := append(when, at.When.Data...) + uid := uuid.NewString() + atWhen := append(when, at.When.Data...) if len(at.Block) != 0 { // add block - block, err := k.block2Task(ctx, o, at.Block, atWhen, buid, variable.BlockLocation) + block, err := k.createTasks(ctx, o, at.Block, atWhen, uid, variable.BlockLocation) if err != nil { - klog.ErrorS(err, "Get block task from block error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "block", at.Name) + klog.V(4).ErrorS(err, "Get block task from block error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "block", at.Name) return nil, err } tasks = append(tasks, block...) if len(at.Always) != 0 { - always, err := k.block2Task(ctx, o, at.Always, atWhen, buid, variable.AlwaysLocation) + always, err := k.createTasks(ctx, o, at.Always, atWhen, uid, variable.AlwaysLocation) if err != nil { - klog.ErrorS(err, "Get always task from block error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "block", at.Name) + klog.V(4).ErrorS(err, "Get always task from block error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "block", at.Name) return nil, err } tasks = append(tasks, always...) } if len(at.Rescue) != 0 { - rescue, err := k.block2Task(ctx, o, at.Rescue, atWhen, buid, variable.RescueLocation) + rescue, err := k.createTasks(ctx, o, at.Rescue, atWhen, uid, variable.RescueLocation) if err != nil { - klog.ErrorS(err, "Get rescue task from block error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "block", at.Name) + klog.V(4).ErrorS(err, "Get rescue task from block error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "block", at.Name) return nil, err } tasks = append(tasks, rescue...) } } else { - task := converter.MarshalBlock(context.WithValue(context.WithValue(ctx, _const.CtxBlockWhen, atWhen), _const.CtxBlockTaskUID, buid), - at, o.Pipeline) - + task := converter.MarshalBlock(context.WithValue(ctx, _const.CtxBlockWhen, atWhen), at) + // complete by pipeline + task.GenerateName = o.Pipeline.Name + "-" + task.Namespace = o.Pipeline.Namespace + if err := controllerutil.SetControllerReference(o.Pipeline, task, k.schema); err != nil { + klog.V(4).ErrorS(err, "Set controller reference error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "block", at.Name) + return nil, err + } + // complete module by unknown field for n, a := range at.UnknownFiled { data, err := json.Marshal(a) if err != nil { - klog.ErrorS(err, "Marshal unknown field error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "block", at.Name, "field", n) + klog.V(4).ErrorS(err, "Marshal unknown field error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "block", at.Name, "field", n) return nil, err } if m := modules.FindModule(n); m != nil { @@ -291,10 +286,28 @@ func (k *taskController) block2Task(ctx context.Context, o AddTaskOptions, ats [ } } if task.Spec.Module.Name == "" { // action is necessary for a task + klog.V(4).ErrorS(nil, "No module/action detected in task", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "block", at.Name) return nil, fmt.Errorf("no module/action detected in task: %s", task.Name) } + // create task + if err := k.client.Create(ctx, task); err != nil { + klog.V(4).ErrorS(err, "Create task error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "block", at.Name) + return nil, err + } + uid = string(task.UID) tasks = append(tasks, *task) } + // add location to variable + if err := o.variable.Merge(variable.LocationMerge{ + UID: uid, + ParentUID: puid, + Type: locationType, + Name: at.Name, + Vars: at.Vars, + }); err != nil { + klog.V(4).ErrorS(err, "Merge block to variable error", "pipeline", ctrlclient.ObjectKeyFromObject(o.Pipeline), "block", at.Name) + return nil, err + } } return tasks, nil } @@ -334,7 +347,7 @@ func (k *taskController) processNextWorkItem(ctx context.Context) bool { // Forget here else we'd go into a loop of attempting to // process a work item that is invalid. k.wq.Forget(obj) - klog.Errorf("Queue item %v was not a Request", obj) + klog.V(4).ErrorS(nil, "Queue item was not a Request", "request", req) // Return true, don't take a break return true } @@ -343,7 +356,7 @@ func (k *taskController) processNextWorkItem(ctx context.Context) bool { switch { case err != nil: k.wq.AddRateLimited(req) - klog.ErrorS(err, "Reconciler error", "request", req) + klog.V(4).ErrorS(err, "Reconciler error", "request", req) case result.RequeueAfter > 0: // The result.RequeueAfter request will be lost, if it is returned // along with a non-nil error. But this is intended as diff --git a/pkg/variable/helper.go b/pkg/variable/helper.go index 7bad4fa3..56f673f4 100644 --- a/pkg/variable/helper.go +++ b/pkg/variable/helper.go @@ -17,6 +17,7 @@ limitations under the License. package variable import ( + "path/filepath" "strconv" "k8s.io/apimachinery/pkg/runtime" @@ -24,6 +25,7 @@ import ( "sigs.k8s.io/yaml" kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1" + _const "github.com/kubesphere/kubekey/v4/pkg/const" ) // mergeVariables merge multiple variables into one variable @@ -139,7 +141,7 @@ func Extension2Variables(ext runtime.RawExtension) VariableData { var data VariableData if err := yaml.Unmarshal(ext.Raw, &data); err != nil { - klog.ErrorS(err, "failed to unmarshal extension to variables") + klog.V(4).ErrorS(err, "failed to unmarshal extension to variables") } return data } @@ -151,7 +153,7 @@ func Extension2Slice(ext runtime.RawExtension) []any { var data []any if err := yaml.Unmarshal(ext.Raw, &data); err != nil { - klog.ErrorS(err, "failed to unmarshal extension to slice") + klog.V(4).ErrorS(err, "failed to unmarshal extension to slice") } return data } @@ -166,3 +168,8 @@ func Extension2String(ext runtime.RawExtension) string { } return string(ext.Raw) } + +func RuntimeDirFromPipeline(obj kubekeyv1.Pipeline) string { + return filepath.Join(_const.GetRuntimeDir(), kubekeyv1.SchemeGroupVersion.String(), + _const.RuntimePipelineDir, obj.Namespace, obj.Name, _const.RuntimePipelineVariableDir) +} diff --git a/pkg/variable/internal.go b/pkg/variable/internal.go index c414ec76..5915f67e 100644 --- a/pkg/variable/internal.go +++ b/pkg/variable/internal.go @@ -114,7 +114,7 @@ type VariableData map[string]any func (v VariableData) String() string { data, err := json.Marshal(v) if err != nil { - klog.ErrorS(err, "marshal in error", "data", v) + klog.V(4).ErrorS(err, "marshal in error", "data", v) return "" } return string(data) @@ -168,7 +168,7 @@ func (v *variable) syncLocation() error { return err } if err := v.source.Write(data, _const.RuntimePipelineVariableLocationFile); err != nil { - klog.ErrorS(err, "write location data to local file error", "filename", _const.RuntimePipelineVariableLocationFile) + klog.V(4).ErrorS(err, "write location data to local file error", "filename", _const.RuntimePipelineVariableLocationFile) return err } return nil diff --git a/pkg/variable/source/file.go b/pkg/variable/source/file.go index af387056..d4b3ee0c 100644 --- a/pkg/variable/source/file.go +++ b/pkg/variable/source/file.go @@ -31,7 +31,7 @@ type fileSource struct { func (f *fileSource) Read() (map[string][]byte, error) { de, err := os.ReadDir(f.path) if err != nil { - klog.ErrorS(err, "read dir error", "path", f.path) + klog.V(4).ErrorS(err, "read dir error", "path", f.path) return nil, err } var result map[string][]byte @@ -46,7 +46,7 @@ func (f *fileSource) Read() (map[string][]byte, error) { if strings.HasSuffix(entry.Name(), ".json") { data, err := os.ReadFile(filepath.Join(f.path, entry.Name())) if err != nil { - klog.ErrorS(err, "read file error", "filename", entry.Name()) + klog.V(4).ErrorS(err, "read file error", "filename", entry.Name()) return nil, err } result[entry.Name()] = data @@ -59,12 +59,12 @@ func (f *fileSource) Read() (map[string][]byte, error) { func (f *fileSource) Write(data []byte, filename string) error { file, err := os.Create(filepath.Join(f.path, filename)) if err != nil { - klog.ErrorS(err, "create file error", "filename", filename) + klog.V(4).ErrorS(err, "create file error", "filename", filename) return err } defer file.Close() if _, err := file.Write(data); err != nil { - klog.ErrorS(err, "write file error", "filename", filename) + klog.V(4).ErrorS(err, "write file error", "filename", filename) return err } return nil diff --git a/pkg/variable/source/source.go b/pkg/variable/source/source.go index e7328878..7fa71905 100644 --- a/pkg/variable/source/source.go +++ b/pkg/variable/source/source.go @@ -40,7 +40,7 @@ type Watcher interface { func New(path string) (Source, error) { if _, err := os.Stat(path); err != nil { if err := os.MkdirAll(path, fs.ModePerm); err != nil { - klog.ErrorS(err, "create source path error", "path", path) + klog.V(4).ErrorS(err, "create source path error", "path", path) return nil, err } } diff --git a/pkg/variable/variable.go b/pkg/variable/variable.go index 848f316e..744a7597 100644 --- a/pkg/variable/variable.go +++ b/pkg/variable/variable.go @@ -25,9 +25,8 @@ import ( "strconv" "strings" - cgcache "k8s.io/client-go/tools/cache" - "k8s.io/apimachinery/pkg/types" + cgcache "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" @@ -52,21 +51,21 @@ type Options struct { // New variable. generate value from config args. and render to source. func New(o Options) (Variable, error) { // new source - s, err := source.New(filepath.Join(_const.RuntimeDirFromObject(&o.Pipeline), _const.RuntimePipelineVariableDir)) + s, err := source.New(RuntimeDirFromPipeline(o.Pipeline)) if err != nil { - klog.ErrorS(err, "create file source failed", "path", filepath.Join(_const.RuntimeDirFromObject(&o.Pipeline), _const.RuntimePipelineVariableDir), "pipeline", ctrlclient.ObjectKeyFromObject(&o.Pipeline)) + klog.V(4).ErrorS(err, "create file source failed", "path", filepath.Join(RuntimeDirFromPipeline(o.Pipeline)), "pipeline", ctrlclient.ObjectKeyFromObject(&o.Pipeline)) return nil, err } // get config var config = &kubekeyv1.Config{} if err := o.Client.Get(o.Ctx, types.NamespacedName{o.Pipeline.Spec.ConfigRef.Namespace, o.Pipeline.Spec.ConfigRef.Name}, config); err != nil { - klog.ErrorS(err, "get config from pipeline error", "config", o.Pipeline.Spec.ConfigRef, "pipeline", ctrlclient.ObjectKeyFromObject(&o.Pipeline)) + klog.V(4).ErrorS(err, "get config from pipeline error", "config", o.Pipeline.Spec.ConfigRef, "pipeline", ctrlclient.ObjectKeyFromObject(&o.Pipeline)) return nil, err } // get inventory var inventory = &kubekeyv1.Inventory{} if err := o.Client.Get(o.Ctx, types.NamespacedName{o.Pipeline.Spec.InventoryRef.Namespace, o.Pipeline.Spec.InventoryRef.Name}, inventory); err != nil { - klog.ErrorS(err, "get inventory from pipeline error", "inventory", o.Pipeline.Spec.InventoryRef, "pipeline", ctrlclient.ObjectKeyFromObject(&o.Pipeline)) + klog.V(4).ErrorS(err, "get inventory from pipeline error", "inventory", o.Pipeline.Spec.InventoryRef, "pipeline", ctrlclient.ObjectKeyFromObject(&o.Pipeline)) return nil, err } v := &variable{ @@ -81,21 +80,21 @@ func New(o Options) (Variable, error) { // read data from source data, err := v.source.Read() if err != nil { - klog.ErrorS(err, "read data from source error", "pipeline", ctrlclient.ObjectKeyFromObject(&o.Pipeline)) + klog.V(4).ErrorS(err, "read data from source error", "pipeline", ctrlclient.ObjectKeyFromObject(&o.Pipeline)) return nil, err } for k, d := range data { if k == _const.RuntimePipelineVariableLocationFile { // set location if err := json.Unmarshal(d, &v.value.Location); err != nil { - klog.ErrorS(err, "unmarshal location error", "pipeline", ctrlclient.ObjectKeyFromObject(&o.Pipeline)) + klog.V(4).ErrorS(err, "unmarshal location error", "pipeline", ctrlclient.ObjectKeyFromObject(&o.Pipeline)) return nil, err } } else { // set hosts h := host{} if err := json.Unmarshal(d, &h); err != nil { - klog.ErrorS(err, "unmarshal host error", "pipeline", ctrlclient.ObjectKeyFromObject(&o.Pipeline)) + klog.V(4).ErrorS(err, "unmarshal host error", "pipeline", ctrlclient.ObjectKeyFromObject(&o.Pipeline)) return nil, err } v.value.Hosts[strings.TrimSuffix(k, ".json")] = h @@ -256,7 +255,7 @@ func (g Hostnames) filter(data value) (any, error) { if match := regex.FindStringSubmatch(n); match != nil { index, err := strconv.Atoi(match[2]) if err != nil { - klog.ErrorS(err, "convert index to int error", "index", match[2]) + klog.V(4).ErrorS(err, "convert index to int error", "index", match[2]) return nil, err } for gn, gv := range data.Inventory.Spec.Groups { @@ -484,67 +483,67 @@ const ( // LocationMerge merge variable to location type LocationMerge struct { - Uid string - ParentID string - Type LocationType - Name string - Vars VariableData + UID string + ParentUID string + Type LocationType + Name string + Vars VariableData } func (t LocationMerge) mergeTo(v *value) error { - if t.ParentID == "" { + if t.ParentUID == "" { v.Location = append(v.Location, location{ Name: t.Name, - PUID: t.ParentID, - UID: t.Uid, + PUID: t.ParentUID, + UID: t.UID, Vars: t.Vars, }) return nil } // find parent graph - parentLocation := findLocation(v.Location, t.ParentID) + parentLocation := findLocation(v.Location, t.ParentUID) if parentLocation == nil { - return fmt.Errorf("cannot find parent location %s", t.ParentID) + return fmt.Errorf("cannot find parent location %s", t.ParentUID) } switch t.Type { case BlockLocation: for _, loc := range parentLocation.Block { - if loc.UID == t.Uid { - klog.Warningf("task graph %s already exist", t.Uid) + if loc.UID == t.UID { + klog.Warningf("task graph %s already exist", t.UID) return nil } } parentLocation.Block = append(parentLocation.Block, location{ Name: t.Name, - PUID: t.ParentID, - UID: t.Uid, + PUID: t.ParentUID, + UID: t.UID, Vars: t.Vars, }) case AlwaysLocation: for _, loc := range parentLocation.Always { - if loc.UID == t.Uid { - klog.Warningf("task graph %s already exist", t.Uid) + if loc.UID == t.UID { + klog.Warningf("task graph %s already exist", t.UID) return nil } } parentLocation.Always = append(parentLocation.Always, location{ Name: t.Name, - PUID: t.ParentID, - UID: t.Uid, + PUID: t.ParentUID, + UID: t.UID, Vars: t.Vars, }) case RescueLocation: for _, loc := range parentLocation.Rescue { - if loc.UID == t.Uid { - klog.Warningf("task graph %s already exist", t.Uid) + if loc.UID == t.UID { + klog.Warningf("task graph %s already exist", t.UID) return nil } } parentLocation.Rescue = append(parentLocation.Rescue, location{ Name: t.Name, - PUID: t.ParentID, - UID: t.Uid, + PUID: t.ParentUID, + UID: t.UID, Vars: t.Vars, }) default: