From c3a33ef64fdb8b2c1cb269055febb5b522b749d6 Mon Sep 17 00:00:00 2001 From: Fergal Mc Carthy Date: Fri, 19 Jul 2024 13:31:11 -0400 Subject: [PATCH] Implement telemetry client interfaces Add a telemetry package to the repo top-level directory that defines the client interfaces that should be used by applications integrating with the SUSE Telemetry Client to generate telemetry. The telemetry package defines the following interfaces: * Generate() - can be called by telemetry providers to "generate" telemetry. * Status() - can be used by telemetry providers to check the status of the telemetry client. Enhance the telemetry client config to support specifing enablement state for the opt-out and opt-in telemetry classes, and allow and deny lists for specific telemetry types. Add new types and associated helper methods, to support the telemetry client interfaces: * TelemetryClass - specifies mandatory, opt-out or opt-in classes of telemetry. * GenerateFlags - flags that control how the Generate() interface handles provided telemetry. Updated the examples directory contents, including adding an example Go app leveraging the telemetry client interfaces to generate telemetry and check the status of the telemetry client. Updated various config files and embedded config settings to reflect recent logging support changes and add class_options settings; they should all now be pretty consistent in their content. Updated the cmd/generator and cmd/clientds tools to setup logging. Relates: #15 #30 #31 #32 #33 --- README.md | 3 + cmd/clientds/main.go | 17 +- cmd/generator/main.go | 17 +- examples/README.md | 14 ++ examples/app/go.mod | 14 ++ examples/app/go.sum | 16 ++ examples/app/main.go | 88 +++++++ examples/config/telemetry.yaml | 13 +- pkg/client/client.go | 42 ++++ pkg/config/config.go | 68 +++++- pkg/config/config_test.go | 57 +++-- .../config/processor/defaultEnvProcessor.yaml | 10 +- pkg/types/types.go | 20 ++ telemetry.go | 223 ++++++++++++++++++ testdata/config/localClient.yaml | 5 + 15 files changed, 574 insertions(+), 33 deletions(-) create mode 100644 examples/README.md create mode 100644 examples/app/go.mod create mode 100644 examples/app/go.sum create mode 100644 examples/app/main.go create mode 100644 telemetry.go diff --git a/README.md b/README.md index dbe788c..f311df1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # telemetry SUSE Telemetry Client Library and associated client CLI tools +See [examples](examples/) directory for examples of how to use the +Telemetry Client library. + # What's available ## cmd/generator diff --git a/cmd/clientds/main.go b/cmd/clientds/main.go index 3c41190..5989e22 100644 --- a/cmd/clientds/main.go +++ b/cmd/clientds/main.go @@ -7,6 +7,7 @@ import ( "github.com/SUSE/telemetry/pkg/client" "github.com/SUSE/telemetry/pkg/config" + "github.com/SUSE/telemetry/pkg/logging" ) // options is a struct of the options @@ -15,10 +16,11 @@ type options struct { items bool bundles bool reports bool + debug bool } func (o options) String() string { - return fmt.Sprintf("config=%v, items=%v, bundles=%v, reports=%v", o.config, o.items, o.bundles, o.reports) + return fmt.Sprintf("config=%v, items=%v, bundles=%v, reports=%v, debug=%v", o.config, o.items, o.bundles, o.reports, o.debug) } var opts options @@ -26,12 +28,24 @@ var opts options func main() { fmt.Printf("clientds: %s\n", opts) + if err := logging.SetupBasicLogging(opts.debug); err != nil { + panic(err) + } + cfg, err := config.NewConfig(opts.config) if err != nil { log.Fatal(err) } fmt.Printf("Config: %+v\n", cfg) + lm := logging.NewLogManager() + if opts.debug { + lm.SetLevel("debug") + } + if err := lm.ConfigAndSetup(&cfg.Logging); err != nil { + panic(err) + } + tc, err := client.NewTelemetryClient(cfg) if err != nil { log.Fatal(err) @@ -86,6 +100,7 @@ func main() { } func init() { + flag.BoolVar(&opts.debug, "debug", false, "Enable debug level logging") flag.StringVar(&opts.config, "config", client.CONFIG_PATH, "Path to config file to read") flag.BoolVar(&opts.items, "items", false, "Report details on telemetry data items datastore") flag.BoolVar(&opts.bundles, "bundles", false, "Report details on telemetry bundles datastore") diff --git a/cmd/generator/main.go b/cmd/generator/main.go index 487d88b..c90bf09 100644 --- a/cmd/generator/main.go +++ b/cmd/generator/main.go @@ -8,6 +8,7 @@ import ( "github.com/SUSE/telemetry/pkg/client" "github.com/SUSE/telemetry/pkg/config" + "github.com/SUSE/telemetry/pkg/logging" "github.com/SUSE/telemetry/pkg/types" ) @@ -21,10 +22,11 @@ type options struct { tags types.Tags telemetry types.TelemetryType jsonFiles []string + debug bool } func (o options) String() string { - return fmt.Sprintf("config=%v, dryrun=%v, tags=%v, telemetry=%v, jsonFiles=%v", o.config, o.dryrun, o.tags, o.telemetry, o.jsonFiles) + return fmt.Sprintf("config=%v, dryrun=%v, tags=%v, telemetry=%v, jsonFiles=%v, debug=%v", o.config, o.dryrun, o.tags, o.telemetry, o.jsonFiles, o.debug) } var opts options @@ -32,12 +34,24 @@ var opts options func main() { fmt.Printf("Generator: %s\n", opts) + if err := logging.SetupBasicLogging(opts.debug); err != nil { + panic(err) + } + cfg, err := config.NewConfig(opts.config) if err != nil { log.Fatal(err) } fmt.Printf("Config: %+v\n", cfg) + lm := logging.NewLogManager() + if opts.debug { + lm.SetLevel("debug") + } + if err := lm.ConfigAndSetup(&cfg.Logging); err != nil { + panic(err) + } + tc, err := client.NewTelemetryClient(cfg) if err != nil { log.Fatal(err) @@ -85,6 +99,7 @@ func main() { func init() { flag.StringVar(&opts.config, "config", client.CONFIG_PATH, "Path to config file to read") + flag.BoolVar(&opts.debug, "debug", false, "Whether to enable debug level logging.") flag.BoolVar(&opts.dryrun, "dryrun", false, "Process provided JSON files but do add them to the telemetry staging area.") flag.BoolVar(&opts.noreports, "noreports", false, "Do not create Telemetry reports") flag.BoolVar(&opts.nobundles, "nobundles", false, "Do not create Telemetry bundles") diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..c35e3ac --- /dev/null +++ b/examples/README.md @@ -0,0 +1,14 @@ +# Telemetry Examples +This directory contains examples of: +* telemetry client configuration files +* telemetry data item payloads +* application integration with telemetry client + +The [app](app/) directory contains a minimal example of using the +telemetry client from a Go application. + +The [config](config/) directory contains example configuration files +for use with these examples. + +The [telemetry](telemetry/) directory contains example telemetry data +item JSON blob payloads named for their associated telemetry type. \ No newline at end of file diff --git a/examples/app/go.mod b/examples/app/go.mod new file mode 100644 index 0000000..96a8c76 --- /dev/null +++ b/examples/app/go.mod @@ -0,0 +1,14 @@ +module github.com/SUSE/telemetry/examples/app + +go 1.21.9 + +replace github.com/SUSE/telemetry => ../../ + +require github.com/SUSE/telemetry v0.0.0-00010101000000-000000000000 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/xyproto/randomstring v1.0.5 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/examples/app/go.sum b/examples/app/go.sum new file mode 100644 index 0000000..34efe85 --- /dev/null +++ b/examples/app/go.sum @@ -0,0 +1,16 @@ +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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/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/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/app/main.go b/examples/app/main.go new file mode 100644 index 0000000..6888e11 --- /dev/null +++ b/examples/app/main.go @@ -0,0 +1,88 @@ +package main + +import ( + "encoding/json" + "fmt" + "log/slog" + + "github.com/SUSE/telemetry" +) + +func status_check_example() { + status := telemetry.Status() + slog.Info("Telemetry Client", slog.String("status", status.String())) +} + +type SomeTelemetryType struct { + Version int `json:"version"` + Application string `json:"application"` + Fields []string `josn:"fields"` +} + +func (stt *SomeTelemetryType) String() string { + return fmt.Sprintf("%+v", *stt) +} + +func telemetry_content() []byte { + appTelemetry := SomeTelemetryType{ + Version: 1, + Application: "specific", + Fields: []string{ + "Of", + "Data", + }, + } + + content, err := json.Marshal(appTelemetry) + if err != nil { + slog.Error( + "Failed to json.Marshal() content", + slog.Any("telemetry", appTelemetry), + slog.String("error", err.Error()), + ) + panic(fmt.Errorf("json.Marshal() failed: %s", err.Error())) + } + + return content +} + +func generate_telemetry_example() { + telemetryType := telemetry.TelemetryType("SOME-TELEMETRY-TYPE") + class := telemetry.MANDATORY_TELEMETRY + content := telemetry_content() + tags := telemetry.Tags{} + flags := telemetry.GENERATE | telemetry.SUBMIT + + // verify the telemetry type + if valid, err := telemetryType.Valid(); !valid { + slog.Error( + "Invalid telemetry type", + slog.Any("type", telemetryType), + slog.String("error", err.Error()), + ) + return + } + + err := telemetry.Generate( + telemetryType, + class, + content, + tags, + flags) + if err != nil { + slog.Error( + "Generate() failed", + slog.String("type", telemetryType.String()), + slog.String("class", class.String()), + slog.Any("content", content), + slog.String("tags", tags.String()), + slog.String("flags", flags.String()), + slog.String("error", err.Error()), + ) + } +} + +func main() { + status_check_example() + generate_telemetry_example() +} diff --git a/examples/config/telemetry.yaml b/examples/config/telemetry.yaml index 89e6943..0680add 100644 --- a/examples/config/telemetry.yaml +++ b/examples/config/telemetry.yaml @@ -5,15 +5,12 @@ tags: [] datastores: driver: sqlite3 params: /tmp/telemetry/client/telemetry.db -extras: - some: thing - else: - - one - - two - - three - or: - maybe: 2024 logging: level: info location: stderr style: text +class_options: + opt_out: true + opt_in: false + allow: [] + deny: [] diff --git a/pkg/client/client.go b/pkg/client/client.go index f2641d5..6174be9 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -4,8 +4,10 @@ import ( "bytes" "encoding/base64" "encoding/json" + "errors" "fmt" "io" + "io/fs" "log/slog" "net/http" "os" @@ -42,6 +44,30 @@ func NewTelemetryClient(cfg *config.Config) (tc *TelemetryClient, err error) { return } +func checkFileExists(filePath string) bool { + slog.Debug("checking for existence", slog.String("filePath", filePath)) + + if _, err := os.Stat(filePath); err != nil { + if !errors.Is(err, fs.ErrExist) { + slog.Debug( + "failed to stat path", + slog.String("filePath", filePath), + slog.String("error", err.Error()), + ) + return false + } + } + + return true +} + +func checkFileReadAccessible(filePath string) bool { + if _, err := os.Open(filePath); err != nil { + return false + } + return true +} + func ensureInstanceIdExists(instIdPath string) error { slog.Info("ensuring existence of instIdPath", slog.String("instIdPath", instIdPath)) @@ -68,6 +94,22 @@ func ensureInstanceIdExists(instIdPath string) error { return nil } +func (tc *TelemetryClient) AuthAccessible() bool { + return checkFileReadAccessible(tc.AuthPath()) +} + +func (tc *TelemetryClient) InstanceIdAccessible() bool { + return checkFileReadAccessible(tc.InstIdPath()) +} + +func (tc *TelemetryClient) HasAuth() bool { + return checkFileExists(tc.AuthPath()) +} + +func (tc *TelemetryClient) HasInstanceId() bool { + return checkFileExists(tc.InstIdPath()) +} + func (tc *TelemetryClient) Processor() telemetrylib.TelemetryProcessor { // may want to just make the processor a public field return tc.processor diff --git a/pkg/config/config.go b/pkg/config/config.go index 9529398..244ac05 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -6,17 +6,22 @@ import ( "log" "log/slog" "os" + "slices" "gopkg.in/yaml.v3" + + "github.com/SUSE/telemetry/pkg/types" ) type Config struct { - TelemetryBaseURL string `yaml:"telemetry_base_url"` - Enabled bool `yaml:"enabled"` - CustomerID string `yaml:"customer_id"` - Tags []string `yaml:"tags"` - DataStores DBConfig `yaml:"datastores"` - Extras any `yaml:"extras,omitempty"` + TelemetryBaseURL string `yaml:"telemetry_base_url"` + Enabled bool `yaml:"enabled"` + CustomerID string `yaml:"customer_id"` + Tags []string `yaml:"tags"` + DataStores DBConfig `yaml:"datastores"` + ClassOptions ClassOptionsConfig `yaml:"classOptions"` + Logging LogConfig `yaml:"logging"` + Extras any `yaml:"extras,omitempty"` } // Defaults @@ -25,6 +30,19 @@ var DefaultDBCfg = DBConfig{ Params: "/tmp/telemetry/client/telemetry.db", } +var DefaultLogging = LogConfig{ + Level: "info", + Location: "stderr", + Style: "text", +} + +var DefaultClassOptions = ClassOptionsConfig{ + OptOut: true, + OptIn: false, + Allow: []types.TelemetryType{}, + Deny: []types.TelemetryType{}, +} + var DefaultCfg = Config{ //TelemetryBaseURL: "https://scc.suse.com/telemetry/", TelemetryBaseURL: "http://localhost:9999/telemetry", @@ -32,6 +50,8 @@ var DefaultCfg = Config{ CustomerID: "0", Tags: []string{}, DataStores: DefaultDBCfg, + Logging: DefaultLogging, + ClassOptions: DefaultClassOptions, } // Datastore config for staging the data @@ -51,6 +71,13 @@ func (lc *LogConfig) String() string { return string(str) } +type ClassOptionsConfig struct { + OptOut bool `yaml:"opt_out" json:"opt_out"` + OptIn bool `yaml:"opt_in" json:"opt_in"` + Allow []types.TelemetryType `yaml:"allow" json:"allow"` + Deny []types.TelemetryType `yaml:"deny" json:"deny"` +} + func NewConfig(cfgFile string) (*Config, error) { //Default configuration @@ -76,3 +103,32 @@ func NewConfig(cfgFile string) (*Config, error) { return cfg, nil } + +func (c *Config) TelemetryClassEnabled(class types.TelemetryClass) bool { + + switch class { + case types.MANDATORY_TELEMETRY: + return true + case types.OPT_OUT_TELEMETRY: + return c.ClassOptions.OptOut + case types.OPT_IN_TELEMETRY: + return c.ClassOptions.OptIn + } + + return false +} + +func (c *Config) TelemetryTypeEnabled(telemetry types.TelemetryType) bool { + // if telemetry type is in the allow list then allow it to be sent + if slices.Contains(c.ClassOptions.Allow, telemetry) { + return true + } + + // otherwise if telemetry type is in the deny list then deny it + if slices.Contains(c.ClassOptions.Deny, telemetry) { + return false + } + + // otherwise allow the telemetry type + return true +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index c5c72b3..bff896a 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -26,6 +26,13 @@ func (t *TestConfigTestSuite) TestConfigFileNotFound() { assert.Equal(t.T(), DefaultCfg.Tags, config.Tags, "Tags value is not the expected") assert.Equal(t.T(), DefaultCfg.DataStores.Driver, config.DataStores.Driver, "DataStores.Driver is not the expected") assert.Equal(t.T(), DefaultCfg.DataStores.Params, config.DataStores.Params, "DataStores.Params is not the expected") + assert.Equal(t.T(), DefaultCfg.Logging.Level, config.Logging.Level, "Logging.Level is not the expected") + assert.Equal(t.T(), DefaultCfg.Logging.Location, config.Logging.Location, "Logging.Location is not the expected") + assert.Equal(t.T(), DefaultCfg.Logging.Style, config.Logging.Style, "Logging.Style is not the expected") + assert.Equal(t.T(), DefaultCfg.ClassOptions.OptOut, config.ClassOptions.OptOut, "ClassOptions.OptOut is not the expected") + assert.Equal(t.T(), DefaultCfg.ClassOptions.OptIn, config.ClassOptions.OptIn, "ClassOptions.OptIn is not the expected") + assert.Equal(t.T(), DefaultCfg.ClassOptions.Allow, config.ClassOptions.Allow, "ClassOptions.Allow is not the expected") + assert.Equal(t.T(), DefaultCfg.ClassOptions.Deny, config.ClassOptions.Deny, "ClassOptions.Deny is not the expected") } func (t *TestConfigTestSuite) TestConfigFileFound() { @@ -38,14 +45,23 @@ func (t *TestConfigTestSuite) TestConfigFileFound() { params := "/tmp/telemetry/testcfg/telemetry.db" content := ` - telemetry_base_url: %s - enabled: true - customer_id: 01234 - tags: [] - datastores: - driver: %s - params: %s - ` +telemetry_base_url: %s +enabled: true +customer_id: 01234 +tags: [] +datastores: + driver: %s + params: %s +logging: + level: info + location: stderr + style: text +class_options: + opt_out: true + opt_in: false + allow: [] + deny: [] +` formattedContents := fmt.Sprintf(content, url, driver, params) _, err = tmpfile.Write([]byte(formattedContents)) @@ -70,14 +86,23 @@ func (t *TestConfigTestSuite) TestConfigFileFoundButUnparsable() { params := "/tmp/telemetry/testcfg/telemetry.db" content := ` - telemetry_base_url: %s - enabled true - customer_id: 01234 - tags: [] - datastores: - driver: %s - params: %s - ` +telemetry_base_url: %s +enabled true +customer_id: 01234 +tags: [] +datastores: + driver: %s + params: %s +logging: + level: info + location: stderr + style: text +class_options: + opt_out: true + opt_in: false + allow: [] + deny: [] +` formattedContents := fmt.Sprintf(content, url, driver, params) _, err = tmpfile.Write([]byte(formattedContents)) diff --git a/pkg/lib/testdata/config/processor/defaultEnvProcessor.yaml b/pkg/lib/testdata/config/processor/defaultEnvProcessor.yaml index 595e8ef..765d37a 100644 --- a/pkg/lib/testdata/config/processor/defaultEnvProcessor.yaml +++ b/pkg/lib/testdata/config/processor/defaultEnvProcessor.yaml @@ -1,7 +1,15 @@ - enabled: true customer_id: 1234567890 tags: [] datastores: driver: sqlite3 params: /tmp/telemetry/processor/telemetry.db +logging: + level: info + location: stderr + style: text +class_options: + opt_out: true + opt_in: false + allow: [] + deny: [] diff --git a/pkg/types/types.go b/pkg/types/types.go index 52a505c..149cd23 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -103,3 +103,23 @@ func Now() TelemetryTimeStamp { t := TelemetryTimeStamp{time.Now()} return t } + +type TelemetryClass int64 + +const ( + MANDATORY_TELEMETRY TelemetryClass = iota + OPT_OUT_TELEMETRY + OPT_IN_TELEMETRY +) + +func (tc *TelemetryClass) String() string { + switch *tc { + case MANDATORY_TELEMETRY: + return "MANDATORY" + case OPT_OUT_TELEMETRY: + return "OPT-OUT" + case OPT_IN_TELEMETRY: + return "OPT-IN" + } + return "UNKNOWN_TELEMETRY_CLASS" +} diff --git a/telemetry.go b/telemetry.go new file mode 100644 index 0000000..78b234e --- /dev/null +++ b/telemetry.go @@ -0,0 +1,223 @@ +package telemetry + +import ( + "log/slog" + + "github.com/SUSE/telemetry/pkg/client" + "github.com/SUSE/telemetry/pkg/config" + "github.com/SUSE/telemetry/pkg/types" +) + +type TelemetryType = types.TelemetryType + +type Tags = types.Tags + +type TelemetryClass = types.TelemetryClass + +const ( + MANDATORY_TELEMETRY = types.MANDATORY_TELEMETRY + OPT_OUT_TELEMETRY = types.OPT_OUT_TELEMETRY + OPT_IN_TELEMETRY = types.OPT_IN_TELEMETRY +) + +type GenerateFlags uint64 + +const ( + GENERATE GenerateFlags = iota + SUBMIT GenerateFlags = 1 << (iota - 1) +) + +func (gf *GenerateFlags) String() string { + flags := "GENERATE" + switch { + case gf.FlagSet(SUBMIT): + flags += "|SUBMIT" + fallthrough + default: + // nothing to do + } + return flags +} + +func (gf GenerateFlags) FlagSet(flag GenerateFlags) bool { + return (gf & flag) == flag +} + +func (gf GenerateFlags) SubmitRequested() bool { + return gf.FlagSet(SUBMIT) +} + +func Generate(telemetry types.TelemetryType, class TelemetryClass, content []byte, tags types.Tags, flags GenerateFlags) (err error) { + // check that the telemetry type is valid + if valid, err := telemetry.Valid(); !valid { + slog.Error( + "Invalid telemetry type", + slog.String("type", string(telemetry)), + ) + return err + } + + // attempt to load the default config file + cfg, err := config.NewConfig(client.CONFIG_PATH) + if err != nil { + slog.Error( + "Failed to load telemetry client config", + slog.String("path", client.CONFIG_PATH), + slog.String("error", err.Error()), + ) + return + } + + // check if the telemetry client is enabled in config + if !cfg.Enabled { + slog.Warn("The telemetry client is disabled in the configuration; no telemetry generated") + return + } + + // check that the telemetry class is enabled for generation + if !cfg.TelemetryClassEnabled(class) { + slog.Warn( + "Telemetry class generation is disabled", + slog.String("class", class.String()), + ) + return + } + + // check that the telemetry type is enabled for generation + if !cfg.TelemetryTypeEnabled(telemetry) { + slog.Warn( + "Telemetry class generation is disabled", + slog.String("class", class.String()), + ) + return + } + + // instantiate a telemetry client + tc, err := client.NewTelemetryClient(cfg) + if err != nil { + slog.Warn( + "Failed to instantiate a TelemetryClient", + slog.String("error", err.Error()), + ) + return + } + + // ensure the client is registered + err = tc.Register() + if err != nil { + slog.Warn( + "Failed to register TelemetryClient with upstream server", + slog.String("error", err.Error()), + ) + return + } + + // generate the telemetry, storing it in the local data store + err = tc.Generate(telemetry, content, tags) + if err != nil { + slog.Warn( + "Failed to generate telemetry", + slog.String("error", err.Error()), + ) + return + } + + // check if immediate submission requested + if flags.SubmitRequested() { + // TODO: implement immediate submission + slog.Info("Immediate Telemetry Submission requested") + } + + return +} + +type ClientStatus int64 + +const ( + CLIENT_UNINITIALIZED ClientStatus = iota + CLIENT_CONFIG_ACCESSIBLE + CLIENT_DISABLED + CLIENT_MISCONFIGURED + CLIENT_DATASTORE_ACCESSIBLE + CLIENT_INSTANCE_ID_ACCESSIBLE + CLIENT_REGISTERED +) + +func (cs *ClientStatus) String() string { + switch *cs { + case CLIENT_UNINITIALIZED: + return "UNITITIALIZED" + case CLIENT_CONFIG_ACCESSIBLE: + return "CONFIG_ACCESSIBLE" + case CLIENT_DISABLED: + return "DISABLED" + case CLIENT_MISCONFIGURED: + return "MISCONFIGURED" + case CLIENT_DATASTORE_ACCESSIBLE: + return "DATASTORE_ACCESSIBLE" + case CLIENT_INSTANCE_ID_ACCESSIBLE: + return "INSTANCE_ID_ACCESSIBLE" + case CLIENT_REGISTERED: + return "REGISTERED" + } + return "UNKNOWN_TELEMETRY_CLIENT_STATUS" +} + +func Status() (status ClientStatus) { + // default to being uninitialised + status = CLIENT_UNINITIALIZED + + // attempt to load the default config + cfg, err := config.NewConfig(client.CONFIG_PATH) + if err != nil { + slog.Warn( + "Failed to load telemetry client config", + slog.String("path", client.CONFIG_PATH), + slog.String("error", err.Error()), + ) + return + } + + // update status to indicate that telemetry client configuration is accessible + status = CLIENT_CONFIG_ACCESSIBLE + + // check if the telemetry client is enabled in config + if !cfg.Enabled { + slog.Info("The telemetry client is disabled in the configuration") + return CLIENT_DISABLED + } + + // instantiate a telemetry client using provided config + tc, err := client.NewTelemetryClient(cfg) + if err != nil { + slog.Warn( + "Failed to setup telemetry client using provided config", + slog.String("path", client.CONFIG_PATH), + slog.String("error", err.Error()), + ) + return CLIENT_MISCONFIGURED + } + + // update status to indicate that telemetry client datastore is accessible + status = CLIENT_DATASTORE_ACCESSIBLE + + // check that an instance id is available + if !tc.InstanceIdAccessible() { + slog.Warn("Telemetry client instance id has not been setup", slog.String("path", tc.InstIdPath())) + return + } + + // update status to indicate client has instance id + status = CLIENT_INSTANCE_ID_ACCESSIBLE + + // check that we have obtained a telemetry auth token + if !tc.AuthAccessible() { + slog.Warn("Telemetry client has not been registered", slog.String("path", tc.AuthPath())) + return + } + + // update status to indicate telemetry client is registered + status = CLIENT_REGISTERED + + return +} diff --git a/testdata/config/localClient.yaml b/testdata/config/localClient.yaml index 1a3618e..2c2dcb2 100644 --- a/testdata/config/localClient.yaml +++ b/testdata/config/localClient.yaml @@ -9,3 +9,8 @@ logging: level: info location: stderr style: text +class_options: + opt_out: true + opt_in: false + allow: [] + deny: []