Coder Social home page Coder Social logo

flagsmith-go-client's Introduction

Feature Flag, Remote Config and A/B Testing platform, Flagsmith

Stars Docker Pulls Docker Image Size Join the Discord chat Coverage Built with Depot

Flagsmith is an open source, fully featured, Feature Flag and Remote Config service. Use our hosted API, deploy to your own private cloud, or run on-premise.

Flagsmith

Flagsmith makes it easy to create and manage features flags across web, mobile, and server side applications. Just wrap a section of code with a flag, and then use Flagsmith to toggle that feature on or off for different environments, users or user segments.

Get up and running in less than a minute:

curl -o docker-compose.yml https://raw.githubusercontent.com/Flagsmith/flagsmith/main/docker-compose.yml
docker-compose -f docker-compose.yml up

The application will bootstrap an admin user, organisation, and project for you. You'll find a link to set your password in your Compose logs:

Superuser "[email protected]" created successfully.
Please go to the following page and choose a password: https://localhost:8000/password-reset/confirm/.../...

Flagsmith Screenshot

Features

  • Feature flags. Release features with confidence through phased roll-outs.
  • Remote config. Easily toggle individual features on and off, and make changes without deploying new code.
  • A/B and Multivariate Testing. Use segments to run A/B and multivariate tests on new features. With segments, you can also introduce beta programs to get early user feedback.
  • Organization Management. Organizations, projects, and roles for team members help keep your deployment organized.
  • Integrations. Easily enhance Flagsmith with your favourite tools.

Trying Flagsmith

Flagsmith hosted SaaS

You can try our hosted version for free at https://flagsmith.com/

Flagsmith Open Source

The Flagsmith API is built using Python 3, Django 2, and DjangoRestFramework 3. You can try the application out using:

We also have options for deploying to AWS, GCP, Azure and On-Premise. If you need help getting up and running, please get in touch!

Overview

This repository is formed of 2 core components, the REST API (found in /api) and the web-based administrator dashboard (found in /frontend) that you can use to manage Flagsmith. Technical documentation for each component can be found at the API and Frontend pages within our Documentation

These two components run as separate applications. The web-based dashboard is a single page app that communicates via REST calls to the API.

Resources

Acknowledgements

Thank you to Uffizzi for providing ephemeral environments to preview pull requests.

flagsmith-go-client's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flagsmith-go-client's Issues

Identity overrides in local evaluation mode

  1. Extend the Environment model with the identity_overrides: List[IdentityModel] field.
  2. On environment update in local evaluation mode, store overrides so they're efficiently accessed by identifier.
  3. For buildIndentityModel interface (now more appropriate to call it getIdentityModel), use the storage above to retrieve the identity overrides. Fall back to a new IdentityModel instance if not found. If found, update traits with user-provided traits.

Refer to the following existing implementations:

Flagsmith/flagsmith-python-client#72
Flagsmith/flagsmith-java-client#142
Flagsmith/flagsmith-nodejs-client#143

Bug WithRequestTimeout is not working properly

I noticed that the method WithRequestTimeout is not working properly because it's receiving a time.Duration and in the NewFlagsmith method it's multiplied for time.Second. Please see the details below.

1 - The timeout property is a time.Duration Timeout

2 - In the NewClient the timeout is multiplied for time.Second c.client.SetTimeout(time.Second * c.config.timeout)

3 - As you can see in the images below after the multiplication the timeout prop is wrong
time duration
wrong value

Solution suggestion

1 - Change this line NewClient to c.client.SetTimeout(c.config.timeout) instead of c.client.SetTimeout(time.Second * c.config.timeout)

2 - Or change this property to int instead of time.Duration Timeout

Errors are not wrapped with %w

Errors like:

return Flags{}, &FlagsmithAPIError{msg: fmt.Sprintf("flagsmith: error performing request to Flagsmith API: %s", err)}

Are not wrapped with %w e.g.

return Flags{}, &FlagsmithAPIError{msg: fmt.Sprintf("flagsmith: error performing request to Flagsmith API: %w", err)}

This makes handling errors like -

  Failed to fetch flags with error:
    flagsmith:
      error performing request to Flagsmith API:
        Post "https://api.flagsmith.com/api/v1/identities/":
          context deadline exceeded (Client.Timeout exceeded while awaiting headers)

a challenge. I'm happy to make a PR there's 5 places.

Bug when getting value from Multi-Variate Flags (GetUserFeaturesWithContext)

Hello folks,

I have a weird behavior here getting value from a Multi-Variate Flags identifying a user, on the web page the value is for example ON, and when I use the GetUserFeaturesWithContext to get this value it's off, so the flagsmith-go-client is using this endpoint: /api/v1/flags/{identify}/ and the webpage is using this endpoint: /api/v1/identities/?identifier={identify}, the second one is giving the correct value and the first one is not.

Flag creation:
image

Flag value using the webpage:
image

Flag value using the go-client:
Using the same endpoint used here:

Get(c.config.BaseURI + "flags/" + user.Identifier + "/")

image

Suggestion
Maybe we could change the endpoint here using the same url that is used by the page:

image

Add better logging support

Currently, there are a couple of places which include a a log.Printf(โ€œโ€) statement which just prints the message to stdout without respecting any other logger settings.

We should add an option to the client to pass in a logger (perhaps using log.Interface or similar so we can be logging provider agnostic).

FeatureStateValue returns with double-quotations escaped when using Local Evaluation

I would expect that the FeatureStateValue would return in exactly the same way if I were to use Remote Evaluation or Local Evaluation. But that is not what is happening. It seems that when using Local Evaluation, the engine caches the environment data (more precisely the feature state values) in a different way.

  1. To reproduce that, instantiate the client as usual, with Remote Evaluation:

flagsmithClient := flagsmith.NewClient(cfg.FlagsmithApiKey, flagsmith.WithRemoteEvaluation(), flagsmith.WithRequestTimeout(cfg.FlagsmithTimeout))

  1. Then you call the method GetIdentityFlags().
  2. Extract the desired flag value using flag.GetFeatureValue()
  3. Then take a look at the Value field.

In my case, this is basically what is returned:

image

You can see that it has the line-breaks (which do not matter at all), and the double-quotes inside the json are not escaped (which is the expected outcome).

Now, if you change the client to use flagsmith.WithLocalEvaluation(), the same flag returns the value in a different way:

image

You can see that now the value is escaping all of the double-quotes inside the json. This breaks any unmarshalling. Not even strconv.Unquote() is enough. I chose to keep using Remote Evaluation so I don't add too many complexity trying to unmarshal the payload.

gz#334

Calls to get GetFeatures and FeatureEnabled fail due to marshall failures

When making calls to the api I get failures in unmarshalling

import (
	"fmt"
	bullettrain "github.com/BulletTrainHQ/bullet-train-go-client"
)

func main() {

	bt := bullettrain.DefaultClient("<my_api_key>")
	f, err := bt.GetFeatures()
	if err != nil {
		fmt.Printf("error: %+v\n", err)
		return
	}
	fmt.Printf("%+v\n", f)
}

Results in

error: json: cannot unmarshal number into Go struct field Flag.feature_state_value of type string

My project has 3 features,

'stats_max_parallel' remote config, value 200
'stats_max_parallel_2' feature flag, value false
`stats_test' feature flag, value false

All default settings other than that

Feature Request: Separate control over Identity and Environment flags Local/Remote

I'm using a large number of Environment flags to dictate api access on a per endpoint basis. For these flags, they change so infrequently that using Local Evaluation makes the most sense to avoid the latency hit.

When doing most of my Identity based checks however, Im checking for specific Identity Overrides, which do not function in Local, so I must use Remote Evaluation.

Currently, it is possible to accomplish this with two clients, but it is obviously undesirable to require duplicating client setup.

This request is to add extra options values for the client of WithRemoteEnvironmentEvaluation(), and WithRemoteIdentityEvaluation(). This allows first setting the default to local with WithLocalEvaluation(ctx) (and setting the context) and then setting which you wish to have Remotely evaluated second.

Support Context in the client

Passing Context to the Resty client would allow the user better control over the requests. A common use case would be to pass the context from http.Requests in a server setting.

Support setting proxy URL and port

We would like to have support for setting the proxy URL for the resty client which FS then uses.

This needs to be dynamic, as the proxy URL might change. I believe that the best way to go is to export SetProxy and RemoveProxy methods from the FS client which in turn calls the resty client.

Bulk trait update should support `[]Trait` as input

Pros:

  • Keeps mapping between Go types and FS JSON types consistent
  • Users can easily integrate UpdateTraits with their old code which generates single Trait objects
  • Static typing
  • No dependency on reflection

Cons:

  • More burdensome to hand write inline bulk updates. How often would a person write mulitple trait updates inline?

Implementation would look like this:

func (c *Client) UpdateTraits(user User, traits []Trait) ([]*Trait, error) {
	bulkResp := make([]*Trait, len(traits))

	_, err := c.client.NewRequest().
		SetBody(traits).
		SetResult(&bulkResp).
		Put(c.config.BaseURI + "traits/bulk/")

	return bulkResp, err
}

A slice to an array of pointers would also make sense:

func (c *Client) UpdateTraits(user User, traits []*Trait) ([]*Trait, error) {

Handle invalid Environment Key

Currently the SDK fails silently if passed an invalid environment key. We should handle this better by providing feedback to the developer that the API key was not found.

Tests still import flagsmith as bullettrain

After the latest update go test breaks for your repository. CI/CD didn't catch it as at the time it could still fetch the old Bullettrain dependency. This causes issues for users of your package when running go mod tidy which notifies them of this issue.

go test in your repo on origin/main:

$ go test
# github.com/Flagsmith/flagsmith-go-client
client_test.go:7:2: no required module provides package github.com/BulletTrainHQ/bullet-train-go-client; to add it:
        go get github.com/BulletTrainHQ/bullet-train-go-client
FAIL    github.com/Flagsmith/flagsmith-go-client [setup failed]

go mod tidy in a separate repo (truncated):

github.com/Flagsmith/flagsmith-go-client tested by
github.com/Flagsmith/flagsmith-go-client.test imports
github.com/BulletTrainHQ/bullet-train-go-client: github.com/BulletTrainHQ/[email protected]: parsing go.mod:
module declares its path as: github.com/Flagsmith/flagsmith-go-client
but was required as: github.com/BulletTrainHQ/bullet-train-go-client

I have a PR on the way which resolves these issues.

Update Resty dependency to 2.7.0+ version for bazel users

For people who are building code with this flagsmith library and bazel a workaround is needed to support the old resty 2.3.0 library dependency which contains legacy bazel rule naming in the resty BUILD.bazel file

2.3.0 BUILD.bazel file
2.7.0 BUILD.bazel file

Users will get an error similar to this when trying to build anything using the current flagsmith release with bazel:

no such target '@com_github_go_resty_resty_v2//:resty': target 'resty' not declared in package '' defined by /private/var/tmp/_bazel_user/123abc/external/com_github_go_resty_resty_v2/BUILD.bazel and referenced by '@com_github_flagsmith_flagsmith_go_client//:flagsmith-go-client'

This error indicates bazel is looking for a go_library rule named resty instead of go_default_library

The workaround/fix is to update the go.bzl file created by bazel gazelle to have two additional settings:

    go_repository(
        name = "com_github_go_resty_resty_v2",
        build_file_generation = "off",                     # Needs to be added manually
        build_file_proto_mode = "disable_global",
        build_naming_convention = "go_default_library",    # Needs to be added manually
        importpath = "github.com/go-resty/resty/v2",
        sum = "h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So=",
        version = "v2.3.0",
    )

flagsmith panics with wrong Base URL set

There is a panic inside flagsmith when used with wrong URL set and via open-feature.

panic({0x8dcc00?, 0xb922d0?})
        /usr/local/go/src/runtime/panic.go:920 +0x290
github.com/Flagsmith/flagsmith-go-client/v3/flagengine.GetIdentitySegments(0xc000066360, 0xc0002058c8, {0x0, 0x0, 0x0})
        /home/m/go/pkg/mod/github.com/!flagsmith/flagsmith-go-client/[email protected]/flagengine/engine.go:77 +0x71
github.com/Flagsmith/flagsmith-go-client/v3/flagengine.getIdentityFeatureStatesMap(0xc000066360, 0xc0002058c8, {0x0, 0x0, 0x0})
        /home/m/go/pkg/mod/github.com/!flagsmith/flagsmith-go-client/[email protected]/flagengine/engine.go:96 +0x172
github.com/Flagsmith/flagsmith-go-client/v3/flagengine.GetIdentityFeatureStates(0xc000066360, 0xc0002058c8, {0x0, 0x0, 0x0})
        /home/m/go/pkg/mod/github.com/!flagsmith/flagsmith-go-client/[email protected]/flagengine/engine.go:41 +0x7e
github.com/Flagsmith/flagsmith-go-client/v3.(*Client).getIdentityFlagsFromEnvironment(0xc000100be0, {0x93a4cb, 0x10}, {0x0, 0x0, 0x0})
        /home/m/go/pkg/mod/github.com/!flagsmith/flagsmith-go-client/[email protected]/client.go:189 +0x1b3
github.com/Flagsmith/flagsmith-go-client/v3.(*Client).GetIdentityFlags(0xc000100be0, {0x9a5b00, 0xbcf3c0}, {0x93a4cb, 0x10}, {0x0, 0x0, 0x0})
        /home/m/go/pkg/mod/github.com/!flagsmith/flagsmith-go-client/[email protected]/client.go:98 +0xec
github.com/open-feature/go-sdk-contrib/providers/flagsmith/pkg.(*Provider).resolveFlag(0xc000016ef0, {0x9a5b00, 0xbcf3c0}, {0x93b2b4, 0x12}, {0x8c77a0, 0xb70b40}, 0xc0001a03c0)
        /home/m/go/pkg/mod/github.com/open-feature/go-sdk-contrib/providers/[email protected]/pkg/provider.go:76 +0xd2d
github.com/open-feature/go-sdk-contrib/providers/flagsmith/pkg.(*Provider).BooleanEvaluation(0xc000016ef0, {0x9a5b00, 0xbcf3c0}, {0x93b2b4, 0x12}, 0x0, 0xc0001a03c0)
        /home/m/go/pkg/mod/github.com/open-feature/go-sdk-contrib/providers/[email protected]/pkg/provider.go:134 +0xd3
github.com/open-feature/go-sdk/pkg/openfeature.(*Client).evaluate(0xc0000662a0, {0x9a5b00, 0xbcf3c0}, {0x93b2b4, 0x12}, 0x0, {0x8c77a0, 0xb70b40}, {{0x93a4cb, 0x10}, ...}, ...)
        /home/m/go/pkg/mod/github.com/open-feature/[email protected]/pkg/openfeature/client.go:714 +0x17c4
github.com/open-feature/go-sdk/pkg/openfeature.(*Client).BooleanValue(0xc0000662a0, {0x9a5b00, 0xbcf3c0}, {0x93b2b4, 0x12}, 0x0, {{0x93a4cb, 0x10}, 0xc0001a0300}, {0x0, ...})
        /home/m/go/pkg/mod/github.com/open-feature/[email protected]/pkg/openfeature/client.go:303 +0x332
main.helloServer({0x9a53c0, 0xc0001fc000}, 0xc0001f6000)
        /home/m/ff/flagsmith/main.go:54 +0x39a
net/http.HandlerFunc.ServeHTTP(0x97e160, {0x9a53c0, 0xc0001fc000}, 0xc0001f6000)

This is the main.go

package main

import (
	"context"
	"github.com/Flagsmith/flagsmith-go-client/v3"
	openFlagsmith "github.com/open-feature/go-sdk-contrib/providers/flagsmith/pkg"
	"github.com/open-feature/go-sdk/pkg/openfeature"
	"io"
	"log"
	"net/http"
	"time"
)

var feature *openfeature.Client
var fc *flagsmith.Client

func init() {
	ctx := context.Background()
	// Initialize the flagsmith client
	client := flagsmith.NewClient(
		"ser.redactedredacted",
		flagsmith.WithLocalEvaluation(context.Background()),
		flagsmith.WithBaseURL("http://localhost:8000/"),
		//flagsmith.WithBaseURL("http://localhost:8000/api/v1/"),
		flagsmith.WithEnvironmentRefreshInterval(30*time.Second),
		flagsmith.WithAnalytics(ctx),
		flagsmith.WithRetries(3, 5*time.Second),
		flagsmith.WithDefaultHandler(func(s string) (flagsmith.Flag, error) {
			return flagsmith.Flag{Enabled: false}, nil
		}),
	)

	// Initialize the flagsmith provider
	provider := openFlagsmith.NewProvider(client, openFlagsmith.WithUsingBooleanConfigValue())

	openfeature.SetProvider(provider)

	// Create open feature client
	feature = openfeature.NewClient("prod")
	fc = client
}

func helloServer(w http.ResponseWriter, req *http.Request) {
	f, err := fc.GetEnvironmentFlags(context.Background())
	_, err = f.IsFeatureEnabled("bool_feature")
	_, err = f.IsFeatureEnabled("engine_status")
	_ = err

	evalCtx := openfeature.NewEvaluationContext(
		"openfeature_user",
		map[string]interface{}{},
	)

	if enabled, err := feature.BooleanValue(context.Background(), "engine_status", false, evalCtx); enabled && err == nil {
		io.WriteString(w, "Feature enabled\n")
	} else {
		io.WriteString(w, "hello, world!\n")
	}
}

func main() {
	http.HandleFunc("/", helloServer)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.