From 80d305b326556773504c788c5cf530ed5868cdc6 Mon Sep 17 00:00:00 2001 From: xuezhaojun Date: Tue, 3 Dec 2024 22:25:53 +0800 Subject: [PATCH] Support flightctl. Signed-off-by: xuezhaojun --- cmd/manager/main.go | 29 ++- deploy/base/clusterrole.yaml | 9 +- go.mod | 15 +- go.sum | 31 ++- pkg/controller/controller.go | 25 +- pkg/controller/flightctl/flightctl.go | 226 ++++++++++++++++++ pkg/controller/flightctl/flightctl_test.go | 50 ++++ .../flightctl/managedclustercontroller.go | 81 +++++++ .../flightctl/manifests/clusterrole.yml | 19 ++ .../clusterrolebinding_agentregistration.yml | 12 + .../clusterrolebinding_flightctl.yml | 12 + .../flightctl/manifests/networkpolicy.yml | 18 ++ .../flightctl/manifests/serviceaccount.yml | 5 + pkg/helpers/helpers.go | 38 +++ 14 files changed, 557 insertions(+), 13 deletions(-) create mode 100644 pkg/controller/flightctl/flightctl.go create mode 100644 pkg/controller/flightctl/flightctl_test.go create mode 100644 pkg/controller/flightctl/managedclustercontroller.go create mode 100644 pkg/controller/flightctl/manifests/clusterrole.yml create mode 100644 pkg/controller/flightctl/manifests/clusterrolebinding_agentregistration.yml create mode 100644 pkg/controller/flightctl/manifests/clusterrolebinding_flightctl.yml create mode 100644 pkg/controller/flightctl/manifests/networkpolicy.yml create mode 100644 pkg/controller/flightctl/manifests/serviceaccount.yml diff --git a/cmd/manager/main.go b/cmd/manager/main.go index addd2f2e..1c9651b2 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -23,6 +23,7 @@ import ( "github.com/stolostron/managedcluster-import-controller/pkg/constants" "github.com/stolostron/managedcluster-import-controller/pkg/controller" "github.com/stolostron/managedcluster-import-controller/pkg/controller/agentregistration" + "github.com/stolostron/managedcluster-import-controller/pkg/controller/flightctl" "github.com/stolostron/managedcluster-import-controller/pkg/controller/importconfig" "github.com/stolostron/managedcluster-import-controller/pkg/features" "github.com/stolostron/managedcluster-import-controller/pkg/helpers" @@ -32,6 +33,10 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/tools/cache" + ocinfrav1 "github.com/openshift/api/config/v1" + asv1beta1 "github.com/openshift/assisted-service/api/v1beta1" + hivev1 "github.com/openshift/hive/apis/hive/v1" + hyperv1beta1 "github.com/openshift/hypershift/api/hypershift/v1beta1" klusterletconfigclient "github.com/stolostron/cluster-lifecycle-api/client/klusterletconfig/clientset/versioned" klusterletconfiginformer "github.com/stolostron/cluster-lifecycle-api/client/klusterletconfig/informers/externalversions" klusterletconfigv1alpha1 "github.com/stolostron/cluster-lifecycle-api/klusterletconfig/v1alpha1" @@ -43,11 +48,6 @@ import ( informerswork "open-cluster-management.io/api/client/work/informers/externalversions" clusterv1 "open-cluster-management.io/api/cluster/v1" - ocinfrav1 "github.com/openshift/api/config/v1" - asv1beta1 "github.com/openshift/assisted-service/api/v1beta1" - hivev1 "github.com/openshift/hive/apis/hive/v1" - hyperv1beta1 "github.com/openshift/hypershift/api/hypershift/v1beta1" - apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" @@ -104,6 +104,12 @@ func main() { } } + var clusterIngressDomain string + var enableFlightCtl bool = false + + pflag.StringVar(&clusterIngressDomain, "cluster-ingress-domain", "", "the ingress domain of the cluster") + pflag.BoolVar(&enableFlightCtl, "enable-flightctl", false, "enable flightctl") + pflag.StringVar(&leaderElectionNamespace, "leader-election-namespace", "", "required when the process is not running in cluster") pflag.BoolVar(&helpers.DeployOnOCP, "deploy-on-ocp", true, "used to deploy the controller on OCP or not") pflag.Float32Var(&QPS, "kube-api-qps", 50, "QPS indicates the maximum QPS to the master from this client") @@ -283,6 +289,9 @@ func main() { // with involvedObject set to the ManagedCluster. mcRecorder := helpers.NewManagedClusterEventRecorder(ctx, clientHolder.KubeClient) + // Init flightctler + flightctl := flightctl.NewFlightCtl(clientHolder, clusterIngressDomain) + setupLog.Info("Registering Controllers") if err := controller.AddToManager( ctx, @@ -301,6 +310,8 @@ func main() { KlusterletConfigLister: klusterletconfigLister, ManagedClusterInformer: managedclusterInformer, }, + enableFlightCtl, + flightctl, mcRecorder, ); err != nil { setupLog.Error(err, "failed to register controller") @@ -331,6 +342,14 @@ func main() { }() } + if enableFlightCtl { + err = flightctl.InstallResources(ctx, helpers.NewEventRecorder(clientHolder.KubeClient, "FlightCtl")) + if err != nil { + setupLog.Error(err, "failed to install FlightCtl resources") + os.Exit(1) + } + } + if enablePprof { go func() { server := &http.Server{ diff --git a/deploy/base/clusterrole.yaml b/deploy/base/clusterrole.yaml index 77bc7f4d..beef4bfb 100644 --- a/deploy/base/clusterrole.yaml +++ b/deploy/base/clusterrole.yaml @@ -268,4 +268,11 @@ rules: verbs: - get - list - +- apiGroups: + - networking.k8s.io + resources: + - networkpolicies + verbs: + - create + - get + - update diff --git a/go.mod b/go.mod index 869085d1..18534839 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,11 @@ module github.com/stolostron/managedcluster-import-controller -go 1.22.0 +go 1.22.5 + +replace github.com/flightctl/flightctl/lib => github.com/xuezhaojun/flightctl/lib v0.0.0-20241125124411-7eec33f53a61 require ( + github.com/flightctl/flightctl/lib v0.0.0 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 github.com/go-logr/logr v1.4.2 github.com/google/go-cmp v0.6.0 @@ -40,6 +43,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/RangelReale/osincli v0.0.0-20160924135400-fababb0555f2 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -52,6 +56,8 @@ require ( github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/getkin/kin-openapi v0.128.0 // indirect + github.com/go-chi/chi v1.5.5 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect @@ -76,6 +82,7 @@ require ( github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/yaml v0.3.1 // indirect github.com/itchyny/gojq v0.12.7 // indirect github.com/itchyny/timefmt-go v0.1.3 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect @@ -91,21 +98,25 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/microcosm-cc/bluemonday v1.0.18 // indirect + github.com/microcosm-cc/bluemonday v1.0.25 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oapi-codegen/runtime v1.1.1 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/openshift/assisted-service/models v0.0.0 // indirect github.com/openshift/custom-resource-status v1.1.3-0.20220503160415-f2fdb4999d87 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/samber/lo v1.47.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cobra v1.8.1 // indirect diff --git a/go.sum b/go.sum index 9749cbe9..9ca3bb8b 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,9 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RangelReale/osincli v0.0.0-20160924135400-fababb0555f2 h1:x8Brv0YNEe6jY3V/hQglIG2nd8g5E2Zj5ubGKkPQctQ= github.com/RangelReale/osincli v0.0.0-20160924135400-fababb0555f2/go.mod h1:XyjUkMA8GN+tOOPXvnbi3XuRxWFvTJntqvTFnjmhzbk= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= @@ -21,6 +24,7 @@ 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/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -62,9 +66,13 @@ github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyT github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4= +github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= +github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= +github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= @@ -105,6 +113,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -168,6 +178,8 @@ 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/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= +github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/itchyny/gojq v0.12.7 h1:hYPTpeWfrJ1OT+2j6cvBScbhl0TkdwGM4bc66onUSOQ= github.com/itchyny/gojq v0.12.7/go.mod h1:ZdvNHVlzPgUf8pgjnuDTmGfHA/21KoutQUJ3An/xNuw= github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU= @@ -228,6 +240,7 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -259,8 +272,8 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo= -github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= +github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= +github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -275,6 +288,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 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= @@ -282,6 +297,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -315,6 +332,8 @@ github.com/openshift/hypershift/api v0.0.0-20241022184855-1fa7be0211e4 h1:hUOaw1 github.com/openshift/hypershift/api v0.0.0-20241022184855-1fa7be0211e4/go.mod h1:aTvlq6HevyNZ01GIaPBnnIe+HoLym3B2TIUBEol2reY= github.com/openshift/library-go v0.0.0-20240207105404-126b47137408 h1:Evg6GEvEuyj9toFX14YenXI6hGRnhLWqYx/rHO7VnQ4= github.com/openshift/library-go v0.0.0-20240207105404-126b47137408/go.mod h1:ePlaOqUiPplRc++6aYdMe+2FmXb2xTNS9Nz5laG2YmI= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -337,6 +356,8 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= @@ -353,6 +374,7 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA= github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stolostron/cluster-lifecycle-api v0.0.0-20240918064238-a5e71b599118 h1:etBoN2TXjxiTNZ/pmvbHuKSvJWm7ml+QGWrHPTeHQ+g= github.com/stolostron/cluster-lifecycle-api v0.0.0-20240918064238-a5e71b599118/go.mod h1:Sflr4YW8MRsymgNLJDcOAv4oyfeiTRyEDR7PRBkg788= @@ -367,8 +389,12 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xuezhaojun/flightctl/lib v0.0.0-20241125124411-7eec33f53a61 h1:Fkx2DwvpT0iKRBS0c2xvdeH7mGjxsoyIAG2H6DdmTSs= +github.com/xuezhaojun/flightctl/lib v0.0.0-20241125124411-7eec33f53a61/go.mod h1:D9FxWj70QGZfKKgoH6QwgC6/1HVx3aLgccRmSftdAig= 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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -438,7 +464,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 04a337f1..fb559045 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -16,6 +16,7 @@ import ( "github.com/stolostron/managedcluster-import-controller/pkg/controller/clusterdeployment" "github.com/stolostron/managedcluster-import-controller/pkg/controller/clusternamespacedeletion" "github.com/stolostron/managedcluster-import-controller/pkg/controller/csr" + "github.com/stolostron/managedcluster-import-controller/pkg/controller/flightctl" "github.com/stolostron/managedcluster-import-controller/pkg/controller/hosted" "github.com/stolostron/managedcluster-import-controller/pkg/controller/importconfig" "github.com/stolostron/managedcluster-import-controller/pkg/controller/importstatus" @@ -36,8 +37,18 @@ func AddToManager(ctx context.Context, manager manager.Manager, clientHolder *helpers.ClientHolder, informerHolder *source.InformerHolder, + enableFlightCtl bool, + flightctler flightctl.FlightCtler, mcRecorder kevents.EventRecorder) error { + extraCSRApprovalConditions := []func(ctx context.Context, csr *certificatesv1.CertificateSigningRequest) (bool, error){} + if enableFlightCtl { + // Case 1: If flightctl is enabled, and a csr is from a flightctl device, approve it. + extraCSRApprovalConditions = append(extraCSRApprovalConditions, func(ctx context.Context, csr *certificatesv1.CertificateSigningRequest) (bool, error) { + return flightctler.IsManagedClusterAFlightctlDevice(ctx, csr.Spec.Username) + }) + } + AddToManagerFuncs := []struct { ControllerName string Add func() error @@ -45,8 +56,7 @@ func AddToManager(ctx context.Context, { csr.ControllerName, func() error { - return csr.Add(ctx, manager, clientHolder, - []func(ctx context.Context, csr *certificatesv1.CertificateSigningRequest) (bool, error){}) + return csr.Add(ctx, manager, clientHolder, extraCSRApprovalConditions) }, }, { @@ -92,6 +102,17 @@ func AddToManager(ctx context.Context, }, } + if enableFlightCtl { + // If flightctl is enabled, add a managedcluster controller to set hubAcceptsClient to true if the managed cluster is a flightctl device. + AddToManagerFuncs = append(AddToManagerFuncs, struct { + ControllerName string + Add func() error + }{ + flightctl.ManagedClusterControllerName, + func() error { return flightctl.AddManagedClusterController(ctx, manager, flightctler, clientHolder) }, + }) + } + for _, f := range AddToManagerFuncs { if err := f.Add(); err != nil { return fmt.Errorf("failed to add %s controller: %w", f.ControllerName, err) diff --git a/pkg/controller/flightctl/flightctl.go b/pkg/controller/flightctl/flightctl.go new file mode 100644 index 00000000..d1d32d91 --- /dev/null +++ b/pkg/controller/flightctl/flightctl.go @@ -0,0 +1,226 @@ +package flightctl + +import ( + "context" + "embed" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "os" + "strings" + "time" + + flightctlapiv1 "github.com/flightctl/flightctl/lib/apipublic/v1alpha1" + flightctlcli "github.com/flightctl/flightctl/lib/cli" + "github.com/openshift/library-go/pkg/operator/events" + "github.com/stolostron/managedcluster-import-controller/pkg/helpers" + authenticationv1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" +) + +const ( + FlightCtlNamespace = "flightctl" + FlightCtlInternalServer = "https://flightctl-api.flightctl.svc.cluster.local:3443" +) + +//go:embed manifests +var FlightCtlManifestFiles embed.FS + +// The flightctl-client's service account token has 2 usages: +// 1. delivered to the devices, used to access the agent-registration to get klusterlet manifests used for registration. +// 2. on the hub side, used for the import-controller to apply the flightctl's Repository resources and get devices. +var files = []string{ + "manifests/clusterrole.yml", + "manifests/clusterrolebinding_agentregistration.yml", + "manifests/clusterrolebinding_flightctl.yml", + "manifests/serviceaccount.yml", + "manifests/networkpolicy.yml", +} + +type FlightCtler interface { + InstallResources(ctx context.Context, recorder events.Recorder) error + IsManagedClusterAFlightctlDevice(ctx context.Context, managedClusterName string) (bool, error) +} + +func NewFlightCtl(clientHolder *helpers.ClientHolder, clusterIngressDomain string) FlightCtler { + return &FlightCtl{ + flightctlServer: FlightCtlInternalServer, + agentRegistrationServer: "https://agent-registration-multicluster-engine." + clusterIngressDomain, + clientHolder: clientHolder, + } +} + +type FlightCtl struct { + clientHolder *helpers.ClientHolder + recorder events.Recorder + flightctlServer string + agentRegistrationServer string + cachedToken string + cachedCA string +} + +var _ FlightCtler = &FlightCtl{} + +func (f *FlightCtl) InstallResources(ctx context.Context, recorder events.Recorder) error { + var err error + + // Get the FlightCtl namespace + _, err = f.clientHolder.KubeClient.CoreV1().Namespaces().Get(ctx, FlightCtlNamespace, metav1.GetOptions{}) + if err != nil { + return err + } + + // Create rbac resources and set owner reference to the ns. + objects, err := helpers.FilesToObjects(files, struct { + Namespace string + PodNamespace string + }{ + Namespace: FlightCtlNamespace, + PodNamespace: os.Getenv("POD_NAMESPACE"), + }, &FlightCtlManifestFiles) + if err != nil { + return err + } + if _, err := helpers.ApplyResources( + f.clientHolder, recorder, nil, nil, objects...); err != nil { + return err + } + + // Create Repository resources + err = f.applyRepository(ctx) + if err != nil { + return err + } + + return nil +} + +func (f *FlightCtl) applyRepository(ctx context.Context) error { + token, err := f.getFlightCtlClientServiceAccountToken(ctx) + if err != nil { + return err + } + + ca, err := f.getAgentRegistrationCA() + if err != nil { + return err + } + + expectedRepository := &flightctlapiv1.Repository{ + ApiVersion: "v1alpha1", + Kind: "Repository", + Metadata: flightctlapiv1.ObjectMeta{ + // Note: In the fleets' `httpRef.repository` field, the name is `acm-registration`. + // See details in: https://github.com/flightctl/flightctl/blob/main/docs/user/registering-microshift-devices-acm.md + Name: ptr.To("acm-registration"), + }, + Spec: flightctlapiv1.RepositorySpec{}, + } + err = expectedRepository.Spec.MergeHttpRepoSpec(flightctlapiv1.HttpRepoSpec{ + Type: flightctlapiv1.Http, + Url: f.agentRegistrationServer, + HttpConfig: flightctlapiv1.HttpConfig{ + Token: &token, + CaCrt: &ca, + }, + ValidationSuffix: ptr.To("/agent-registration"), + }) + if err != nil { + return err + } + + return flightctlcli.ApplyRepository(ctx, token, f.flightctlServer, expectedRepository) +} + +func (f *FlightCtl) IsManagedClusterAFlightctlDevice(ctx context.Context, managedClusterName string) (bool, error) { + token, err := f.getFlightCtlClientServiceAccountToken(ctx) + if err != nil { + return false, err + } + + response, err := flightctlcli.GetDevice(ctx, token, f.flightctlServer, managedClusterName) + if err != nil { + return false, err + } + + if response.HTTPResponse.StatusCode == http.StatusNotFound { + return false, nil + } + + if response.HTTPResponse.StatusCode != http.StatusOK { + return false, fmt.Errorf("failed to get device %s, status code: %d", managedClusterName, response.HTTPResponse.StatusCode) + } + + return true, nil +} + +func (f *FlightCtl) getFlightCtlClientServiceAccountToken(ctx context.Context) (string, error) { + if f.cachedToken != "" { + // check if the cached token is close to expire, use 7 days as the threshold + if closeToExpire, err := tokenCloseToExpire(f.cachedToken, 7*24*time.Hour); err != nil { + return "", err + } else if !closeToExpire { // if not close to expire, return the cached token + return f.cachedToken, nil + } + } + + // Create token request for the service account + tokenRequest := &authenticationv1.TokenRequest{ + Spec: authenticationv1.TokenRequestSpec{ + ExpirationSeconds: ptr.To[int64](10 * 24 * 60 * 60), // Give 10 days for the token to expire + }, + } + + // Get the token using TokenRequest API + tokenResponse, err := f.clientHolder.KubeClient.CoreV1().ServiceAccounts(FlightCtlNamespace). + CreateToken(ctx, "flightctl-client", tokenRequest, metav1.CreateOptions{}) + if err != nil { + return "", fmt.Errorf("failed to create token: %v", err) + } + + f.cachedToken = tokenResponse.Status.Token + return f.cachedToken, nil +} + +func (f *FlightCtl) getAgentRegistrationCA() (string, error) { + if f.cachedCA != "" { + return f.cachedCA, nil + } + + caData, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt") + if err != nil { + return "", fmt.Errorf("failed to read service account CA: %v", err) + } + + f.cachedCA = base64.StdEncoding.EncodeToString(caData) + return f.cachedCA, nil +} + +func tokenCloseToExpire(token string, timeDuration time.Duration) (bool, error) { + // Split the token into parts + parts := strings.Split(token, ".") + if len(parts) != 3 { + return false, fmt.Errorf("invalid token format") + } + + // Decode the claims (second part of the token) + claimBytes, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return false, fmt.Errorf("failed to decode token claims: %v", err) + } + + // Parse the claims + var claims struct { + Exp int64 `json:"exp"` + } + if err := json.Unmarshal(claimBytes, &claims); err != nil { + return false, fmt.Errorf("failed to parse token claims: %v", err) + } + + // Check if token will expire within the given duration + expirationTime := time.Unix(claims.Exp, 0) + timeUntilExpiration := time.Until(expirationTime) + return timeUntilExpiration <= timeDuration, nil +} diff --git a/pkg/controller/flightctl/flightctl_test.go b/pkg/controller/flightctl/flightctl_test.go new file mode 100644 index 00000000..6fc055e7 --- /dev/null +++ b/pkg/controller/flightctl/flightctl_test.go @@ -0,0 +1,50 @@ +package flightctl + +import ( + "testing" + "time" +) + +// TODO: add test cases for `IsFlightCtlEnabled` and `IsManagedClusterAFlightctlDevice` +func TestFlightCtl_IsFlightCtlEnabled(t *testing.T) { + +} + +// TODO: add test cases for `ApplyRepository` +func TestFlightCtl_ApplyRepository(t *testing.T) { + +} + +// TODO: add test cases for `IsManagedClusterAFlightctlDevice` +func TestFlightCtl_IsManagedClusterAFlightctlDevice(t *testing.T) { + +} + +func TestFlightCtl_tokenCloseToExpire(t *testing.T) { + testcases := []struct { + token string + timeDuration time.Duration + expected bool + }{ + { + token: "eyJhbGciOiJSUzI1NiIsImtpZCI6Im5DZE1nUC0wQnA5QjJKQS1PQ3lOdDdqZG14SUhrN1lXZU1LMVhtOERUTHcifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjIl0sImV4cCI6MTczNDAxNjcwMSwiaWF0IjoxNzMzMTUyNzAxLCJpc3MiOiJodHRwczovL2t1YmVybmV0ZXMuZGVmYXVsdC5zdmMiLCJrdWJlcm5ldGVzLmlvIjp7Im5hbWVzcGFjZSI6ImRlZmF1bHQiLCJzZXJ2aWNlYWNjb3VudCI6eyJuYW1lIjoiZGVmYXVsdCIsInVpZCI6IjZjZGVhN2NkLTAxZTYtNDVhMy1iMTZjLTk5ZTEyMzExNTMyYyJ9fSwibmJmIjoxNzMzMTUyNzAxLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkZWZhdWx0In0.VA620a1pJ7NH21ttPqngB0BHmCWlfpoapI4wexnFqWnTzlljyKwuEauSLkTACWelt0Rs5x5G7f2fy37pVCaQZpzzJZlGy1RNxGKMJ7tDCiNmoGCDjZcqBwfaJqRYAGpHvVLc2gwhdnm_-TjeZNc2kpxeo9o3XABFHWJCS5IPmkEny1nh71Pb77CDizNb_K0tu-UfqDVtOLXCTMDhcJ81B9OoqMVew7_omU7c5A-mjLMBlxD63KrolsRlYgE8lT9NkEM5Kolg0wJKaiTofxVTfzF35qkFQv0nG6T2vqHH0hFjDVTzCDG2zhreXUlzQB8x1a2dWYwNaYDNJbDsKGP2NSBZaq_FNjDDpKCsiWZTEEP1sDOrqnXa-s_V1wNXghqz9Fdio-VO-H0MqPGjHmtg1xcWahSzscdhb7r97mNpyW7cLQm2guVRhuEyimaiqW_oBIRfTX4wirFK12XEHbyMoIAYZOL-H1QguXA4HftH9vxQsAkv5gr7ggLAGCRZSuyK_AsksJiIAgtDF2mPHEtnK_JStq0X0ky3--1HghFjLhUmZIZni6-tnXgazH8KvgSSc4mtfaWOmIglcYOgS63H4mb0_ZTSIYnlaVh8sOWNEF3ZxSxzzeCmquxWwCMdGPG6WalUS05AG2UHlpQiADjLb4mfFrFyWxOrX93ud9c6P34", + timeDuration: 7 * 24 * time.Hour, + expected: false, + }, + { + token: "eyJhbGciOiJSUzI1NiIsImtpZCI6Im5DZE1nUC0wQnA5QjJKQS1PQ3lOdDdqZG14SUhrN1lXZU1LMVhtOERUTHcifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjIl0sImV4cCI6MTczMzE2NzE4MCwiaWF0IjoxNzMzMTUyNzgwLCJpc3MiOiJodHRwczovL2t1YmVybmV0ZXMuZGVmYXVsdC5zdmMiLCJrdWJlcm5ldGVzLmlvIjp7Im5hbWVzcGFjZSI6ImRlZmF1bHQiLCJzZXJ2aWNlYWNjb3VudCI6eyJuYW1lIjoiZGVmYXVsdCIsInVpZCI6IjZjZGVhN2NkLTAxZTYtNDVhMy1iMTZjLTk5ZTEyMzExNTMyYyJ9fSwibmJmIjoxNzMzMTUyNzgwLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkZWZhdWx0In0.kB4CUUTdCY2hCgDFdEX4pWT3ZPmYxzYxohzeir7yJJXuKvvA6wPTRxNdbLFQBMC4knh_Dww7uTQhSJiYra6Q8CJrK8lIgp_H5fksYb3atNaSJJT_3qYTsR8l2QQqDhZcjztIQqnipcYYy_UQYPVujeg69P2sy5LtfwsqUtZed3vS8kX2A0rQcG2lzOhvHpwAbLXJwsiaB6y3h_zgncT2DpiHzN9XxaT4au8W1wwdcEECgkbHp0C8QzXPDcp0_hjI2987ENfp-9cW2czbSWxQprIPM4hqYbOdDdk1Cqb0FNjHAEL6giBkZOHd7NE4rcRD4V6MpWYvPrhP7q8wfpNZkbQ2WANMDDWyLow-eMWao9BZLcSu5ymdl_5A1xdUfgj4h0EIjXTrJV8o58kKq60JYRIbpBAOwyXoRsydyFFjbjgz1IuQxaP3v4zntSRqxmTgW1biWw-zwh_csbVgYlV-lna79htcfBZ5_HYITLXAlyNIC9eB7NAXzlFM3GA-MP4fpVhxpfl_aDwHV5-tx_F_9LgNBUPvhQPCZjy4k9DcOS6fHlSwOnrzOq2ECG7saKinmLnMRkfOBoKquXCDT4UZXBPNl5mzapi1iN5TEAavnVKbKdHjV-LYkLmydg5G6NSuOf408_y4NSnqodfziKRv6Gy7fzfyKHxQAwuZT6nw3x4", + timeDuration: 7 * 24 * time.Hour, + expected: true, + }, + } + + for _, tc := range testcases { + actual, err := tokenCloseToExpire(tc.token, tc.timeDuration) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if actual != tc.expected { + t.Errorf("expected: %v, actual: %v", tc.expected, actual) + } + } +} diff --git a/pkg/controller/flightctl/managedclustercontroller.go b/pkg/controller/flightctl/managedclustercontroller.go new file mode 100644 index 00000000..ea78a727 --- /dev/null +++ b/pkg/controller/flightctl/managedclustercontroller.go @@ -0,0 +1,81 @@ +package flightctl + +import ( + "context" + + "github.com/openshift/library-go/pkg/operator/events" + "github.com/stolostron/managedcluster-import-controller/pkg/helpers" + clusterv1 "open-cluster-management.io/api/cluster/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ManagedClusterControllerName = "flightctl-managedcluster-controller" + +// ManagedClusterReconciler is responsible to set hubAcceptsClient to true if the managed cluster is a flightctl device. +type ManagedClusterReconciler struct { + clientHolder *helpers.ClientHolder + recorder events.Recorder + flightctl FlightCtler +} + +var _ reconcile.Reconciler = &ManagedClusterReconciler{} + +func (r *ManagedClusterReconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + cluster := &clusterv1.ManagedCluster{} + if err := r.clientHolder.RuntimeClient.Get(ctx, request.NamespacedName, cluster); err != nil { + return reconcile.Result{}, err + } + + if cluster.DeletionTimestamp != nil { + return reconcile.Result{}, nil + } + + if cluster.Spec.HubAcceptsClient { + return reconcile.Result{}, nil + } + + isDevice, err := r.flightctl.IsManagedClusterAFlightctlDevice(ctx, cluster.Name) + if err != nil { + return reconcile.Result{}, err + } + if !isDevice { + return reconcile.Result{}, nil + } + + cluster.Spec.HubAcceptsClient = true + if err := r.clientHolder.RuntimeClient.Update(ctx, cluster); err != nil { + return reconcile.Result{}, err + } + + return reconcile.Result{}, nil +} + +func AddManagedClusterController(ctx context.Context, mgr manager.Manager, flightctler FlightCtler, clientHolder *helpers.ClientHolder) error { + err := ctrl.NewControllerManagedBy(mgr).Named(ManagedClusterControllerName). + Watches( + &clusterv1.ManagedCluster{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.Funcs{ + GenericFunc: func(e event.GenericEvent) bool { return false }, + DeleteFunc: func(e event.DeleteEvent) bool { return false }, + UpdateFunc: func(e event.UpdateEvent) bool { + return !e.ObjectNew.(*clusterv1.ManagedCluster).Spec.HubAcceptsClient + }, + CreateFunc: func(e event.CreateEvent) bool { + return !e.Object.(*clusterv1.ManagedCluster).Spec.HubAcceptsClient + }, + })). + Complete(&ManagedClusterReconciler{ + clientHolder: clientHolder, + flightctl: flightctler, + recorder: helpers.NewEventRecorder(clientHolder.KubeClient, ManagedClusterControllerName), + }) + + return err +} diff --git a/pkg/controller/flightctl/manifests/clusterrole.yml b/pkg/controller/flightctl/manifests/clusterrole.yml new file mode 100644 index 00000000..7f8cb401 --- /dev/null +++ b/pkg/controller/flightctl/manifests/clusterrole.yml @@ -0,0 +1,19 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: flightctl-client +rules: + - verbs: + - get + apiGroups: + - flightctl.io + resources: + - devices + - verbs: + - create + - update + - get + apiGroups: + - flightctl.io + resources: + - repositories diff --git a/pkg/controller/flightctl/manifests/clusterrolebinding_agentregistration.yml b/pkg/controller/flightctl/manifests/clusterrolebinding_agentregistration.yml new file mode 100644 index 00000000..ae5b297f --- /dev/null +++ b/pkg/controller/flightctl/manifests/clusterrolebinding_agentregistration.yml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: flightctl-agent-registration +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: managedcluster-import-controller-agent-registration-client +subjects: +- kind: ServiceAccount + name: flightctl-client + namespace: "{{ .Namespace }}" diff --git a/pkg/controller/flightctl/manifests/clusterrolebinding_flightctl.yml b/pkg/controller/flightctl/manifests/clusterrolebinding_flightctl.yml new file mode 100644 index 00000000..60a9183b --- /dev/null +++ b/pkg/controller/flightctl/manifests/clusterrolebinding_flightctl.yml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: flightctl-client +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: flightctl-client +subjects: +- kind: ServiceAccount + name: flightctl-client + namespace: "{{ .Namespace }}" diff --git a/pkg/controller/flightctl/manifests/networkpolicy.yml b/pkg/controller/flightctl/manifests/networkpolicy.yml new file mode 100644 index 00000000..d805a7f5 --- /dev/null +++ b/pkg/controller/flightctl/manifests/networkpolicy.yml @@ -0,0 +1,18 @@ +kind: NetworkPolicy +apiVersion: networking.k8s.io/v1 +metadata: + name: flightctl-acm + namespace: flightctl +spec: + podSelector: {} + ingress: + - from: + - podSelector: {} + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: In + values: + - "{{ .PodNamespace }}" + policyTypes: + - Ingress diff --git a/pkg/controller/flightctl/manifests/serviceaccount.yml b/pkg/controller/flightctl/manifests/serviceaccount.yml new file mode 100644 index 00000000..5b9afd9d --- /dev/null +++ b/pkg/controller/flightctl/manifests/serviceaccount.yml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: flightctl-client + namespace: "{{ .Namespace }}" diff --git a/pkg/helpers/helpers.go b/pkg/helpers/helpers.go index 4b4d1882..21b35824 100644 --- a/pkg/helpers/helpers.go +++ b/pkg/helpers/helpers.go @@ -22,6 +22,7 @@ import ( "golang.org/x/text/language" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" schedulingv1 "k8s.io/api/scheduling/v1" crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -95,6 +96,7 @@ func init() { utilruntime.Must(operatorv1.Install(genericScheme)) utilruntime.Must(addonv1alpha1.Install(genericScheme)) utilruntime.Must(schedulingv1.AddToScheme(genericScheme)) + utilruntime.Must(networkingv1.AddToScheme(genericScheme)) } type ClientHolder struct { @@ -581,6 +583,10 @@ func ApplyResources(clientHolder *ClientHolder, recorder events.Recorder, modified, err := applyPriorityClass(clientHolder.KubeClient, recorder, required) errs = append(errs, err) changed = changed || modified + case *networkingv1.NetworkPolicy: + modified, err := applyNetworkPolicy(clientHolder.KubeClient, recorder, required) + errs = append(errs, err) + changed = changed || modified default: errs = append(errs, fmt.Errorf("unknown type %T", required)) } @@ -711,6 +717,38 @@ func applyPriorityClass(client kubernetes.Interface, recorder events.Recorder, return true, nil } +func applyNetworkPolicy(client kubernetes.Interface, recorder events.Recorder, + required *networkingv1.NetworkPolicy) (bool, error) { + existing, err := client.NetworkingV1().NetworkPolicies(required.Namespace).Get( + context.TODO(), required.Name, metav1.GetOptions{}) + if errors.IsNotFound(err) { + if _, err := client.NetworkingV1().NetworkPolicies(required.Namespace).Create( + context.TODO(), required, metav1.CreateOptions{}); err != nil { + return false, err + } + + reportEvent(recorder, required, "NetworkPolicy", "created") + return true, nil + } + if err != nil { + return false, err + } + + if equality.Semantic.DeepEqual(existing.Spec, required.Spec) { + return false, nil + } + + existing = existing.DeepCopy() + existing.Spec = required.Spec + + if _, err := client.NetworkingV1().NetworkPolicies(required.Namespace).Update( + context.TODO(), existing, metav1.UpdateOptions{}); err != nil { + return false, err + } + reportEvent(recorder, required, "NetworkPolicy", "updated") + return true, nil +} + // MustCreateObject translate object from raw bytes to runtime object func MustCreateObject(raw []byte) runtime.Object { obj, _, err := genericCodec.Decode(raw, nil, nil)