From a5532b8d6533728380f55b9de470e3e1327ef5d9 Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 14 Nov 2022 09:19:48 +0200 Subject: [PATCH 1/6] meta: implement silentErrors --- command/meta.go | 6 ++++-- command/silent_error.go | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 command/silent_error.go diff --git a/command/meta.go b/command/meta.go index 89931f8..b7f851c 100644 --- a/command/meta.go +++ b/command/meta.go @@ -152,8 +152,10 @@ func (m *Meta) Run(args []string) int { tracing.StoreFlags(ctx, f) if err := m.cmd.RunContext(ctx, f.Args()); err != nil { - tracing.Error(span, err) - m.Ui.Error(err.Error()) + if !IsSilentError(err) { + tracing.Error(span, err) + m.Ui.Error(err.Error()) + } return 1 } diff --git a/command/silent_error.go b/command/silent_error.go new file mode 100644 index 0000000..439ae43 --- /dev/null +++ b/command/silent_error.go @@ -0,0 +1,12 @@ +package command + +type SilentError struct{} + +func (e *SilentError) Error() string { + return "" +} + +func IsSilentError(err error) bool { + _, ok := err.(*SilentError) + return ok +} From 398cc276e19908e34dd634ccef1325403eb81843 Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 14 Nov 2022 10:37:51 +0200 Subject: [PATCH 2/6] backends: refactor state to return a flag struct --- backends/backend.go | 3 ++- backends/launchdarkly/ld.go | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/backends/backend.go b/backends/backend.go index cc6f1ee..f3a751a 100644 --- a/backends/backend.go +++ b/backends/backend.go @@ -10,9 +10,10 @@ type User struct { type Flag struct { Key string DefaultValue bool + Value bool } type Backend interface { - State(ctx context.Context, flag Flag, user User) (bool, error) + State(ctx context.Context, flag Flag, user User) (Flag, error) Close(ctx context.Context) error } diff --git a/backends/launchdarkly/ld.go b/backends/launchdarkly/ld.go index 88abb05..c444417 100644 --- a/backends/launchdarkly/ld.go +++ b/backends/launchdarkly/ld.go @@ -54,7 +54,7 @@ func (ldb *LaunchDarklyBackend) Close(ctx context.Context) error { return ldb.client.Close() } -func (ldb *LaunchDarklyBackend) State(ctx context.Context, flag backends.Flag, user backends.User) (bool, error) { +func (ldb *LaunchDarklyBackend) State(ctx context.Context, flag backends.Flag, user backends.User) (backends.Flag, error) { ctx, span := tr.Start(ctx, "state") defer span.End() @@ -62,15 +62,19 @@ func (ldb *LaunchDarklyBackend) State(ctx context.Context, flag backends.Flag, u span.SetAttributes(attribute.String("flag.key", flag.Key)) + flag.Value = flag.DefaultValue + variation, detail, err := ldb.client.BoolVariationDetail(flag.Key, u, flag.DefaultValue) if err != nil { - return flag.DefaultValue, tracing.Error(span, err) + return flag, tracing.Error(span, err) } span.SetAttributes(attribute.String("reason", detail.Reason.String())) span.SetAttributes(attribute.Bool("variation", variation)) - return variation, nil + flag.Value = variation + + return flag, nil } func createUser(ctx context.Context, user backends.User) lduser.User { From 76126f48b0af56a692cb1e2467bdb524f504da4a Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 14 Nov 2022 10:43:03 +0200 Subject: [PATCH 3/6] commands: refactor printing, add silent flag --- command/helpers.go | 18 ------------------ command/meta.go | 22 ++++++++++++++++++++++ command/state.go | 13 ++++++------- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/command/helpers.go b/command/helpers.go index 57b006c..8876a2a 100644 --- a/command/helpers.go +++ b/command/helpers.go @@ -1,11 +1,8 @@ package command import ( - "encoding/json" "fmt" "strings" - - "github.com/mitchellh/cli" ) func parseKeyValuePairs(tags []string) (map[string]string, error) { @@ -33,18 +30,3 @@ func parseKeyValuePairs(tags []string) (map[string]string, error) { return m, nil } - -func print(ui cli.Ui, format string, vals map[string]interface{}) error { - - switch format { - case "json": - b, err := json.Marshal(vals) - if err != nil { - return err - } - ui.Output(string(b)) - - } - - return nil -} diff --git a/command/meta.go b/command/meta.go index b7f851c..29eebc0 100644 --- a/command/meta.go +++ b/command/meta.go @@ -2,6 +2,7 @@ package command import ( "context" + "encoding/json" "flagon/backends" "flagon/backends/launchdarkly" "flagon/tracing" @@ -24,6 +25,7 @@ type Meta struct { backend string output string + silent bool ldFlags launchdarkly.LaunchDarklyConfiguration } @@ -105,6 +107,7 @@ func (m *Meta) allFlags() []FlagGroup { common.StringVar(&m.backend, "backend", "launchdarkly", "which flag service to use") common.StringVar(&m.output, "output", "json", "specifies the output format") + common.BoolVar(&m.silent, "silent", false, "don't print anything to stdout/stderr") return []FlagGroup{ {Name: "Command", FlagSet: m.cmd.Flags()}, @@ -134,6 +137,25 @@ func (m *Meta) createBackend(ctx context.Context) (backends.Backend, error) { } } +func (m *Meta) print(vals interface{}) error { + + if m.silent { + return nil + } + + switch m.output { + case "json": + b, err := json.Marshal(vals) + if err != nil { + return err + } + m.Ui.Output(string(b)) + + } + + return nil +} + func (m *Meta) Run(args []string) int { ctx := context.Background() diff --git a/command/state.go b/command/state.go index 8c90b98..22ec5b8 100644 --- a/command/state.go +++ b/command/state.go @@ -68,14 +68,13 @@ func (c *StateCommand) RunContext(ctx context.Context, args []string) error { Attributes: attrs, } - value, err := backend.State(ctx, flag, user) - if err != nil { + if flag, err = backend.State(ctx, flag, user); err != nil { + return tracing.Error(span, err) + } + + if err := c.print(flag); err != nil { return tracing.Error(span, err) } - return print(c.Ui, c.output, map[string]interface{}{ - "flag": flag.Key, - "default": flag.DefaultValue, - "state": value, - }) + return nil } From de18d9a62fccaca8e399f361ca7c91680c15d304 Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 14 Nov 2022 11:01:21 +0200 Subject: [PATCH 4/6] state: trace flag and user attributes --- command/state.go | 9 +++++++++ tracing/span.go | 17 +++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/command/state.go b/command/state.go index 22ec5b8..3c1703a 100644 --- a/command/state.go +++ b/command/state.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/spf13/pflag" + "go.opentelemetry.io/otel/attribute" ) type StateCommand struct { @@ -57,6 +58,10 @@ func (c *StateCommand) RunContext(ctx context.Context, args []string) error { Key: args[0], DefaultValue: defaultValue, } + span.SetAttributes( + attribute.String("flag.key", flag.Key), + attribute.Bool("flag.default", flag.DefaultValue), + ) attrs, err := parseKeyValuePairs(c.userAttributes) if err != nil { @@ -67,6 +72,8 @@ func (c *StateCommand) RunContext(ctx context.Context, args []string) error { Key: c.userKey, Attributes: attrs, } + span.SetAttributes(attribute.String("user.key", user.Key)) + span.SetAttributes(tracing.FromMap("user.", user.Attributes)...) if flag, err = backend.State(ctx, flag, user); err != nil { return tracing.Error(span, err) @@ -76,5 +83,7 @@ func (c *StateCommand) RunContext(ctx context.Context, args []string) error { return tracing.Error(span, err) } + span.SetAttributes(attribute.Bool("flag.value", flag.Value)) + return nil } diff --git a/tracing/span.go b/tracing/span.go index 7701925..5a0c4bd 100644 --- a/tracing/span.go +++ b/tracing/span.go @@ -12,17 +12,18 @@ import ( "go.opentelemetry.io/otel/trace" ) -func SetAttributes(ctx context.Context, attrs ...attribute.KeyValue) { - s := trace.SpanFromContext(ctx) - s.SetAttributes(attrs...) -} +func FromMap[V any](prefix string, m map[string]V) []attribute.KeyValue { -func SetAttribute(ctx context.Context, key string, val interface{}) { - s := trace.SpanFromContext(ctx) - s.SetAttributes(asAttribute(key, val)) + attrs := make([]attribute.KeyValue, 0, len(m)) + + for k, v := range m { + attrs = append(attrs, asAttribute(k, v)) + } + + return attrs } -func asAttribute(key string, v interface{}) attribute.KeyValue { +func asAttribute(key string, v any) attribute.KeyValue { switch val := v.(type) { From 09d2b94d7ae3ca7087be27fc27cb5ace59d921d7 Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 14 Nov 2022 11:02:14 +0200 Subject: [PATCH 5/6] state: exit with status 1 if flag is false --- command/state.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/command/state.go b/command/state.go index 3c1703a..c8b1d63 100644 --- a/command/state.go +++ b/command/state.go @@ -85,5 +85,9 @@ func (c *StateCommand) RunContext(ctx context.Context, args []string) error { span.SetAttributes(attribute.Bool("flag.value", flag.Value)) - return nil + if flag.Value { + return nil + } + + return &SilentError{} } From 81d258cc87f44be2ffe843bb6dadfa04110a1f8d Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 14 Nov 2022 13:31:45 +0200 Subject: [PATCH 6/6] changelog: update for exit-codes --- changelog.md | 13 ++++++++++++- readme.md | 47 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index f8668ec..8305e49 100644 --- a/changelog.md +++ b/changelog.md @@ -1,8 +1,19 @@ # Changelog +## [0.0.1] - 2022-11-14 + +### Added + +- Exit with code `0` if a flag is `true`, and `1` otherwise +- Add `--silent` flag, to suppress console information + +### Changed + +- Expand what information is written to traces + ## [0.0.0] - 2022-11-11 ### Added - Initial Version -- Read a flag from LaunchDarkly \ No newline at end of file +- Read a flag from LaunchDarkly diff --git a/readme.md b/readme.md index 3e9eec3..19790f9 100644 --- a/readme.md +++ b/readme.md @@ -5,6 +5,47 @@ ## Usage ``` -> flagon state "some-flag-name" --user "$user_id" --prop "branch=$branch" --output json -# { "name": "some-flag-name", default: "off", state: "on" } -``` \ No newline at end of file +> flagon state "some-flag-name" --user "$user_id" --attr "branch=$branch" +# { "name": "some-flag-name", "defaultValue": false, "value": true } +``` + +For example, in a CI system where you wish to select which deployment process is used: + +```shell +email=$(git show --quiet --pretty="format:%ce") +branch=$(git rev-parse --abbrev-ref HEAD) + +if flagon state "ci-replacement-deploy" --user "${email}" --attr "branch=${branch}" --silent; then + ./build/replacement-deploy.sh "${branch}" "${commit}" +else + ./build/deploy.sh "${branch}" "${commit}" +fi +``` + +## Configuration + +### Common + +| Flag | Default | Description | +|-------------|-----------------|-----------------------------------------------------------------------------| +| `--backend` | `launchdarkly` | The backend to query flags from | +| `--output` | `json` | The output format to write to the console. Currently only supports `json` | +| `--silent` | `false` | Silence any console output | + +### Telemetry + +| EnvVar | Default | Description | +|---------------------------------------|-------------------|-----------------------------------------------------------------| +| `OTEL_TRACE_EXPORTER` | ` ` | Which exporter to use: `otlp`, `stdout`, `stderr` | +| `OTEL_EXPORTER_OTLP_ENDPOINT` | `localhost:4317` | Set the Exporter endpoint | +| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | ` ` | Set the Exporter endpoint, takes priority over `OTEL_EXPORTER_OTLP_ENDPOINT` | +| `OTEL_EXPORTER_OTLP_HEADERS` | ` ` | A Csv of Headers and Values to pass to the tracing service, for example `Authentication: Bearer 13213213,X-Environment: Production` | +| `OTEL_DEBUG` | `false` | Print debug information from tracing to the console | + +### Backend: LaunchDarkly + +| EnvVar | Flag | Default | Description | +|---------------------|----------------|----------|------------------------------------------------------------------------------| +| `FLAGON_LD_SDKKEY` | `--ld-sdk-key` | | The [project](https://app.launchdarkly.com/settings/projects) SDK Key to use | +| `FLAGON_LD_TIMEOUT` | `--ld-timeout` | `10s` | How long to wait for successful connection | +| `FLAGON_LD_DEBUG` | `--ld-debug` | `0` | Set to `true` (or `1`) to see debug information from the LaunchDarkly client |