From 689ea002adb929f7e49cda5d7ff2f64c52f43180 Mon Sep 17 00:00:00 2001 From: khewonc <39867936+khewonc@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:58:22 -0400 Subject: [PATCH] Add metadata forwarder --- cmd/main.go | 35 ++++- go.work.sum | 14 +- internal/controller/setup.go | 4 +- internal/controller/suite_v2_test.go | 5 +- pkg/controller/utils/metadata/metadata.go | 148 ++++++++++++++++++++++ pkg/controller/utils/metadata/payload.go | 56 ++++++++ pkg/version/version.go | 5 + 7 files changed, 256 insertions(+), 11 deletions(-) create mode 100644 pkg/controller/utils/metadata/metadata.go create mode 100644 pkg/controller/utils/metadata/payload.go diff --git a/cmd/main.go b/cmd/main.go index d551e3ece..c9b294fe8 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -34,13 +34,14 @@ import ( "github.com/go-logr/logr" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "github.com/DataDog/datadog-operator/api/datadoghq/common" datadoghqv1alpha1 "github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1" datadoghqv2alpha1 "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" "github.com/DataDog/datadog-operator/internal/controller" "github.com/DataDog/datadog-operator/internal/controller/metrics" - "github.com/DataDog/datadog-operator/pkg/config" "github.com/DataDog/datadog-operator/pkg/controller/debug" + "github.com/DataDog/datadog-operator/pkg/controller/utils/metadata" "github.com/DataDog/datadog-operator/pkg/remoteconfig" "github.com/DataDog/datadog-operator/pkg/secrets" "github.com/DataDog/datadog-operator/pkg/version" @@ -52,8 +53,9 @@ const ( ) var ( - scheme = runtime.NewScheme() - setupLog = ctrl.Log.WithName("setup") + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") + metadataLog = ctrl.Log.WithName("metadata") ) func init() { @@ -261,6 +263,7 @@ func run(opts *options) error { // Custom setup customSetupHealthChecks(setupLog, mgr, &opts.maximumGoroutines) + mdf := setupMetadataForwarder(metadataLog, opts) creds, err := config.NewCredentialManager().GetCredentials() if err != nil && opts.datadogMonitorEnabled { @@ -308,10 +311,13 @@ func run(opts *options) error { DatadogDashboardEnabled: opts.datadogDashboardEnabled, } - if err = controller.SetupControllers(setupLog, mgr, options); err != nil { + if err = controller.SetupControllers(setupLog, mgr, options, mdf); err != nil { return setupErrorf(setupLog, err, "Unable to start controllers") } + // send metadata in a new goroutine + mdf.Start() + // +kubebuilder:scaffold:builder setupLog.Info("starting manager") @@ -319,6 +325,8 @@ func run(opts *options) error { return setupErrorf(setupLog, err, "Problem running manager") } + mdf.Stop() + return nil } @@ -363,3 +371,22 @@ func setupErrorf(logger logr.Logger, err error, msg string, keysAndValues ...any setupLog.Error(err, msg, keysAndValues...) return fmt.Errorf("%s, err:%w", msg, err) } + +func setupMetadataForwarder(logger logr.Logger, options *options) *metadata.MetadataForwarder { + mf := metadata.NewMetadataForwarder(logger) + mf.OperatorMetadata = metadata.OperatorMetadata{ + DatadogAgentEnabled: options.datadogAgentEnabled, + DatadogMonitorEnabled: options.datadogMonitorEnabled, + DatadogDashboardEnabled: options.datadogDashboardEnabled, + DatadogSLOEnabled: options.datadogSLOEnabled, + DatadogAgentProfileEnabled: options.datadogAgentProfileEnabled, + ExtendedDaemonSetEnabled: options.supportExtendedDaemonset, + RemoteConfigEnabled: options.remoteConfigEnabled, + IntrospectionEnabled: options.introspectionEnabled, + OperatorVersion: version.GetVersion(), + ConfigDDURL: os.Getenv(common.DDURL), + ConfigDDSite: os.Getenv(common.DDSite), + } + + return mf +} diff --git a/go.work.sum b/go.work.sum index 776bd47c0..3ab04789a 100644 --- a/go.work.sum +++ b/go.work.sum @@ -736,6 +736,7 @@ cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5og cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= +cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= cloud.google.com/go/storage v1.37.0/go.mod h1:i34TiT2IhiNDmcj65PqwCjcoUX7Z5pLzS8DEmoiFq1k= cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= @@ -1065,8 +1066,6 @@ github.com/aws/aws-sdk-go v1.43.11/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4 github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.44.45/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.44.68/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.44.327 h1:ZS8oO4+7MOBLhkdwIhgtVeDzCeWOlTfKJS7EgggbIEY= -github.com/aws/aws-sdk-go v1.44.327/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go v1.53.11/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.0.0/go.mod h1:smfAbmpW+tcRVuNUjo3MOArSZmW72t62rkCzc2i0TWM= @@ -1181,7 +1180,6 @@ github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw= github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradfitz/gomemcache v0.0.0-20230611145640-acc696258285/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= @@ -1422,7 +1420,6 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -2150,6 +2147,7 @@ github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aW github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= @@ -2199,6 +2197,7 @@ github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinmbeaulieu/eq-go v1.0.0 h1:AQgYHURDOmnVJ62jnEk0W/7yFKEn+Lv8RHN6t7mB0Zo= github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM= github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= @@ -2310,6 +2309,7 @@ github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4 github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -2443,7 +2443,6 @@ github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= @@ -3594,6 +3593,7 @@ google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45 google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= +google.golang.org/api v0.152.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g= google.golang.org/api v0.160.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9ZHyw= @@ -4072,12 +4072,14 @@ k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/ k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= modernc.org/cc/v4 v4.2.1/go.mod h1:0O8vuqhQfwBy+piyfEjzWIUGV4I3TPsXSf0W05+lgN8= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= @@ -4085,6 +4087,7 @@ modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWs modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= modernc.org/ccgo/v4 v4.0.0-20230612200659-63de3e82e68d/go.mod h1:austqj6cmEDRfewsUvmGmyIgsI/Nq87oTXlfTgY85Fc= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= @@ -4107,6 +4110,7 @@ modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/strutil v1.0.0 h1:XVFtQwFVwc02Wk+0L/Z/zDDXO81r5Lhe6iMKmGX3KhE= modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= diff --git a/internal/controller/setup.go b/internal/controller/setup.go index c701409b5..277844b7c 100644 --- a/internal/controller/setup.go +++ b/internal/controller/setup.go @@ -17,6 +17,7 @@ import ( componentagent "github.com/DataDog/datadog-operator/internal/controller/datadogagent/component/agent" "github.com/DataDog/datadog-operator/pkg/config" + "github.com/DataDog/datadog-operator/pkg/controller/utils/metadata" "github.com/DataDog/datadog-operator/pkg/datadogclient" "github.com/DataDog/datadog-operator/pkg/kubernetes" "github.com/DataDog/datadog-operator/pkg/utils" @@ -77,7 +78,7 @@ var controllerStarters = map[string]starterFunc{ } // SetupControllers starts all controllers (also used by e2e tests) -func SetupControllers(logger logr.Logger, mgr manager.Manager, options SetupOptions) error { +func SetupControllers(logger logr.Logger, mgr manager.Manager, options SetupOptions, mf *metadata.MetadataForwarder) error { // Get some information about Kubernetes version // Never use original mgr.GetConfig(), always copy as clients might modify the configuration discoveryConfig := rest.CopyConfig(mgr.GetConfig()) @@ -92,6 +93,7 @@ func SetupControllers(logger logr.Logger, mgr manager.Manager, options SetupOpti } if versionInfo != nil { + mf.OperatorMetadata.KubernetesVersion = versionInfo.String() gitVersion := versionInfo.GitVersion if !utils.IsAboveMinVersion(gitVersion, "1.16-0") { logger.Error(nil, "Detected Kubernetes version <1.16 which requires CRD version apiextensions.k8s.io/v1beta1. "+ diff --git a/internal/controller/suite_v2_test.go b/internal/controller/suite_v2_test.go index da5ddfffa..c647a5184 100644 --- a/internal/controller/suite_v2_test.go +++ b/internal/controller/suite_v2_test.go @@ -38,6 +38,7 @@ import ( "github.com/DataDog/datadog-operator/internal/controller/testutils" "github.com/DataDog/datadog-operator/pkg/config" + "github.com/DataDog/datadog-operator/pkg/controller/utils/metadata" // +kubebuilder:scaffold:imports ) @@ -112,7 +113,9 @@ var _ = BeforeSuite(func() { V2APIEnabled: true, } - err = SetupControllers(logger, mgr, options) + mdf := metadata.NewMetadataForwarder(logger) + + err = SetupControllers(logger, mgr, options, mdf) Expect(err).ToNot(HaveOccurred()) var mgrCtx context.Context diff --git a/pkg/controller/utils/metadata/metadata.go b/pkg/controller/utils/metadata/metadata.go new file mode 100644 index 000000000..4851052da --- /dev/null +++ b/pkg/controller/utils/metadata/metadata.go @@ -0,0 +1,148 @@ +package metadata + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + "net/url" + "os" + "strconv" + "time" + + "github.com/go-logr/logr" +) + +const ( + apiHTTPHeaderKey = "Dd-Api-Key" + useragentHTTPHeaderKey = "User-Agent" + contentTypeHeaderKey = "Content-Type" + acceptHeaderKey = "Accept" + + defaultURLScheme = "https" + defaultURLHost = "app.datadog.com" + defaultURLHostPrefix = "app." + defaultURLPath = "api/v1/metadata" + + defaultInterval = 1 * time.Minute +) + +type MetadataForwarder struct { + OperatorMetadata OperatorMetadata + logger logr.Logger + stopChan chan struct{} +} + +// NewMetadataForwarder creates a new instance of the metadata forwarder +func NewMetadataForwarder(logger logr.Logger) *MetadataForwarder { + return &MetadataForwarder{ + OperatorMetadata: OperatorMetadata{}, + logger: logger, + stopChan: make(chan struct{}), + } +} + +// Start starts the metadata forwarder +func (mdf *MetadataForwarder) Start() { + ticker := time.NewTicker(getTickerInterval(mdf.logger)) + go func() { + for { + select { + case <-mdf.stopChan: + ticker.Stop() + mdf.logger.Info("Stopping ticker for metadata forwarder case") + return + case <-ticker.C: + if err := mdf.sendMetadata(); err != nil { + mdf.logger.Error(err, "Error while sending metadata") + } + } + } + }() +} + +// Stop stops the metadata forwarder +func (mdf *MetadataForwarder) Stop() { + close(mdf.stopChan) + mdf.logger.Info("Stopping metadata forwarder") +} + +func (mdf *MetadataForwarder) sendMetadata() error { + url := createURL(mdf.logger) + mdf.logger.V(1).Info("Sending metadata to URL", "url", url) + + reader := bytes.NewReader(mdf.createPayload()) + req, err := http.NewRequestWithContext(context.TODO(), "POST", url, reader) + if err != nil { + mdf.logger.Error(err, "Error creating request", "url", url, "reader", reader) + } + req.Header = getHeaders() + + client := &http.Client{ + Timeout: 10 * time.Second, + } + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("Error sending request: %w", err) + } + + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("Failed to read response body: %w", err) + } + mdf.logger.V(1).Info("Read response", "status code", resp.StatusCode, "body", string(body)) + return nil +} + +func getHeaders() http.Header { + header := http.Header{} + header.Set(apiHTTPHeaderKey, os.Getenv("DD_API_KEY")) + header.Set(useragentHTTPHeaderKey, fmt.Sprintf("datadog-operator/%s", "test")) // TODO: use operator version + header.Set(contentTypeHeaderKey, "application/json") + header.Set(acceptHeaderKey, "application/json") + return header +} + +func createURL(logger logr.Logger) string { + url := url.URL{ + Scheme: defaultURLScheme, + Host: defaultURLHost, + Path: defaultURLPath, + } + // prioritize URL set in env var + if mdURLFromEnvVar := os.Getenv("METADATA_URL"); mdURLFromEnvVar != "" { + parsedURL, err := url.Parse(mdURLFromEnvVar) + if err != nil { + logger.Error(err, "Error parsing METADATA_URL") + } + // TODO: check for correct URL format + return parsedURL.String() + } + // check site env var + // example: datadoghq.com + if siteFromEnvVar := os.Getenv("DD_SITE"); siteFromEnvVar != "" { + url.Host = defaultURLHostPrefix + siteFromEnvVar + } + // check url env var + // example: https://app.datadoghq.com + if urlFromEnvVar := os.Getenv("DD_URL"); urlFromEnvVar != "" { + url.Host = urlFromEnvVar + } + + return url.String() +} + +func getTickerInterval(logger logr.Logger) time.Duration { + interval := defaultInterval + if s := os.Getenv("METADATA_INTERVAL"); s != "" { + i, err := strconv.Atoi(s) + if err != nil { + logger.Error(err, "Error coverting METADATA_INTERVAL") + } + interval = time.Duration(i) * time.Minute + } + logger.Info("Operator metadata will be sent periodically", "frequency (seconds)", interval) + return interval +} diff --git a/pkg/controller/utils/metadata/payload.go b/pkg/controller/utils/metadata/payload.go new file mode 100644 index 000000000..988fc8e9f --- /dev/null +++ b/pkg/controller/utils/metadata/payload.go @@ -0,0 +1,56 @@ +package metadata + +import ( + "encoding/json" + "os" + "time" +) + +const ( + nodeNameEnvVar = "NODE_NAME" +) + +type OperatorMetadataPayload struct { + Hostname string `json:"hostname"` + Timestamp int64 `json:"timestamp"` + Metadata OperatorMetadata `json:"datadog_operator_metadata"` +} + +type OperatorMetadata struct { + DatadogAgentEnabled bool `json:"datadogagent_enabled"` + DatadogMonitorEnabled bool `json:"datadogmonitor_enabled"` + DatadogDashboardEnabled bool `json:"datadogdashboard_enabled"` + DatadogSLOEnabled bool `json:"datadogslo_enabled"` + DatadogAgentProfileEnabled bool `json:"datadogagentprofile_enabled"` + ExtendedDaemonSetEnabled bool `json:"extendeddaemonset_enabled"` + RemoteConfigEnabled bool `json:"remote_config_enabled"` + IntrospectionEnabled bool `json:"introspection_enabled"` + KubernetesVersion string `json:"kubernetes_version"` + OperatorVersion string `json:"operator_version"` + ConfigDDURL string `json:"config_dd_url"` + ConfigDDSite string `json:"config_site"` +} + +func (mdf *MetadataForwarder) createPayload() []byte { + now := time.Now().Unix() + + // prioritize custom payload from env var + if payloadFromEnvVar := os.Getenv("PAYLOAD"); payloadFromEnvVar != "" { + mdf.logger.V(1).Info("Using custom payload from PAYLOAD env var", "payload", payloadFromEnvVar) + return []byte(payloadFromEnvVar) + } + + payload := OperatorMetadataPayload{ + Hostname: os.Getenv(nodeNameEnvVar), + Timestamp: now, + Metadata: mdf.OperatorMetadata, + } + mdf.logger.V(1).Info("Using payload", "payload", payload) + + jsonPayload, err := json.Marshal(payload) + if err != nil { + mdf.logger.Error(err, "Error marshaling payload to json") + } + + return jsonPayload +} diff --git a/pkg/version/version.go b/pkg/version/version.go index 2a735e463..b2b09349c 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -81,3 +81,8 @@ func printVersionSlice() []string { fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH), } } + +// GetVersion returns the operator version +func GetVersion() string { + return Version +}