Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/charmbracelet/lipgloss v0.10.0
github.com/getkin/kin-openapi v0.124.0
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
github.com/iancoleman/strcase v0.3.0
github.com/launchdarkly/api-client-go/v14 v14.0.0
Expand Down Expand Up @@ -37,6 +38,7 @@ require (
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-openapi/jsonpointer v0.20.2 // indirect
github.com/go-openapi/swag v0.22.8 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
Expand Down Expand Up @@ -143,6 +145,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f h1:kOkUP6rcVVqC+KlKKENKtgfFfJyDySYhqL9srXooghY=
Expand Down
2 changes: 1 addition & 1 deletion internal/dev_server/db/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (s Sqlite) GetDevProject(ctx context.Context, key string) (*model.Project,
}

func (s Sqlite) InsertProject(ctx context.Context, project model.Project) error {
flagsStateJson, err := project.FlagState.MarshalJSON()
flagsStateJson, err := json.Marshal(project.FlagState)
if err != nil {
return errors.Wrap(err, "unable to marshal flags state when writing project")
}
Expand Down
7 changes: 6 additions & 1 deletion internal/dev_server/dev_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import (
"fmt"
"log"
"net/http"
"os"

"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/launchdarkly/ldcli/internal/dev_server/sdk"

"github.com/launchdarkly/ldcli/internal/client"
"github.com/launchdarkly/ldcli/internal/dev_server/adapters"
Expand Down Expand Up @@ -40,8 +43,10 @@ func (c LDClient) RunServer(ctx context.Context, accessToken, baseURI string) {
r := mux.NewRouter()
r.Use(adapters.Middleware(*ldClient, "https://events.ld.catamorphic.com", "https://relay-stg.ld.catamorphic.com", "https://relay-stg.ld.catamorphic.com")) // TODO add to config
r.Use(model.StoreMiddleware(sqlStore))
// TODO need a subrouter for relay endpoints
sdk.BindRoutes(r)
handler := api.HandlerFromMux(apiServer, r)
handler = handlers.CombinedLoggingHandler(os.Stdout, handler)
handler = handlers.RecoveryHandler()(handler)
fmt.Println("Server running on 0.0.0.0:8765")
server := http.Server{
Addr: "0.0.0.0:8765",
Expand Down
30 changes: 30 additions & 0 deletions internal/dev_server/model/flags_state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package model

import (
"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
"github.com/launchdarkly/go-server-sdk/v7/interfaces/flagstate"
)

type FlagState struct {
Value ldvalue.Value `json:"value"`
Version int `json:"version"`
}

type FlagsState map[string]FlagState

func FromAllFlags(sdkFlags flagstate.AllFlags) FlagsState {
flags := sdkFlags.ToValuesMap()
flagsState := make(FlagsState, len(flags))
for key, value := range flags {
sdkFlag, ok := sdkFlags.GetFlag(key)
if !ok {
// panic because we're iterating over the same set of keys
panic("flag '" + key + "' not found")
}
flagsState[key] = FlagState{
Value: value,
Version: sdkFlag.Version,
}
}
return flagsState
}
16 changes: 4 additions & 12 deletions internal/dev_server/model/override.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package model

import (
"github.com/launchdarkly/go-sdk-common/v3/ldreason"
"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
"github.com/launchdarkly/go-server-sdk/v7/interfaces/flagstate"
)

type Override struct {
Expand All @@ -14,21 +12,15 @@ type Override struct {
Version int
}

func (o Override) Apply(state flagstate.FlagState) flagstate.FlagState {
func (o Override) Apply(state FlagState) FlagState {
flagVersion := state.Version + o.Version
flagValue := state.Value
if o.Active {
flagValue = o.Value
}
return flagstate.FlagState{
Value: flagValue,
Variation: ldvalue.NewOptionalIntFromPointer(nil),
Version: flagVersion,
Reason: ldreason.NewEvalReasonFallthrough(),
TrackEvents: false,
TrackReason: false,
DebugEventsUntilDate: 0,
OmitDetails: true,
return FlagState{
Value: flagValue,
Version: flagVersion,
}
}

Expand Down
28 changes: 9 additions & 19 deletions internal/dev_server/model/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ import (
"time"

"github.com/launchdarkly/go-sdk-common/v3/ldcontext"
"github.com/launchdarkly/go-server-sdk/v7/interfaces/flagstate"
"github.com/launchdarkly/ldcli/internal/dev_server/adapters"
"github.com/pkg/errors"
)

type Project struct {
Key string
SourceEnvironmentKey string
Context ldcontext.Context
LastSyncTime time.Time
FlagState flagstate.AllFlags
FlagState FlagsState
}

// CreateProject creates a project and adds it to the database.
Expand All @@ -36,10 +34,11 @@ func CreateProject(ctx context.Context, projectKey, sourceEnvironmentKey string,
if err != nil {
return Project{}, err
}
project.FlagState, err = sdkAdapter.GetAllFlagsState(ctx, project.Context, sdkKey)
sdkFlags, err := sdkAdapter.GetAllFlagsState(ctx, project.Context, sdkKey)
if err != nil {
return Project{}, err
}
project.FlagState = FromAllFlags(sdkFlags)
project.LastSyncTime = time.Now()

err = store.InsertProject(ctx, project)
Expand All @@ -49,22 +48,13 @@ func CreateProject(ctx context.Context, projectKey, sourceEnvironmentKey string,
return project, nil
}

func (p Project) GetDefaultStateForFlag(key string) (flagstate.FlagState, bool) {
return p.FlagState.GetFlag(key)
}

func (p Project) GetFlagStateWithOverridesForProject(ctx context.Context, overrides Overrides) (flagstate.AllFlags, error) {
flags := p.FlagState.ToValuesMap()
stateBuilder := flagstate.NewAllFlagsBuilder()
for flagKey := range flags {
stateOfFlag, ok := p.FlagState.GetFlag(flagKey)
if !ok {
return flagstate.AllFlags{}, errors.Errorf("could not find flag, %s, in flag state", flagKey)
}
func (p Project) GetFlagStateWithOverridesForProject(ctx context.Context, overrides Overrides) FlagsState {
withOverrides := make(FlagsState, len(p.FlagState))
for flagKey, flagState := range p.FlagState {
if override, ok := overrides.GetFlag(flagKey); ok {
stateOfFlag = override.Apply(stateOfFlag)
flagState = override.Apply(flagState)
}
stateBuilder.AddFlag(flagKey, stateOfFlag)
withOverrides[flagKey] = flagState
}
return stateBuilder.Build(), nil
return withOverrides
}
1 change: 1 addition & 0 deletions internal/dev_server/sdk/constant_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "net/http"

func ConstantResponseHandler(statusCode int, response string) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Content-Type", "application/json")
writer.WriteHeader(statusCode)
if len(response) > 0 {
_, _ = writer.Write([]byte(response))
Expand Down
12 changes: 12 additions & 0 deletions internal/dev_server/sdk/cors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package sdk

import "net/http"

func CorsHeaders(handler http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Access-Control-Allow-Origin", "*")
writer.Header().Set("Access-Control-Allow-Methods", "GET,OPTIONS")
writer.Header().Set("Access-Control-Allow-Headers", "Cache-Control,Content-Type,Content-Length,Accept-Encoding,X-LaunchDarkly-User-Agent,X-LaunchDarkly-Payload-ID,X-LaunchDarkly-Wrapper,X-LaunchDarkly-Event-Schema,X-LaunchDarkly-Tags")
handler.ServeHTTP(writer, request)
})
}
29 changes: 29 additions & 0 deletions internal/dev_server/sdk/get_client_flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package sdk

import (
"encoding/json"
"net/http"

"github.com/launchdarkly/ldcli/internal/dev_server/model"
"github.com/pkg/errors"
)

func GetClientFlags(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
store := model.StoreFromContext(ctx)
projectKey := GetProjectKeyFromContext(ctx)
project, err := store.GetDevProject(ctx, projectKey)
if err != nil {
panic(errors.Wrap(err, "unable to get dev project"))
}
allFlags := project.GetFlagStateWithOverridesForProject(ctx, nil) // TODO fetch overrides
jsonBody, err := json.Marshal(allFlags)
if err != nil {
panic(errors.Wrap(err, "failed to marshal flag state"))
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonBody)
if err != nil {
panic(errors.Wrap(err, "unable to write response"))
}
}
31 changes: 31 additions & 0 deletions internal/dev_server/sdk/project_key_middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package sdk

import (
"context"
"net/http"

"github.com/gorilla/mux"
)

type ctxKey string

const projectKeyContextKey = ctxKey("projectKey")

func SetProjectKeyOnContext(ctx context.Context, projectKey string) context.Context {
return context.WithValue(ctx, projectKeyContextKey, projectKey)
}
func GetProjectKeyFromContext(ctx context.Context) string {
return ctx.Value(projectKeyContextKey).(string)
}

func GetProjectKeyFromEnvIdParameter(pathParameter string) func(handler http.Handler) http.Handler {
return func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
projectKey := mux.Vars(request)[pathParameter]
ctx := request.Context()
ctx = SetProjectKeyOnContext(ctx, projectKey)
request = request.WithContext(ctx)
handler.ServeHTTP(writer, request)
})
}
}
8 changes: 6 additions & 2 deletions internal/dev_server/sdk/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ func BindRoutes(router *mux.Router) {
router.HandleFunc("/mobile/events/bulk", DevNull)
router.HandleFunc("/mobile/events/diagnostic", DevNull)

router.HandleFunc("/sdk/goals/{envId}", ConstantResponseHandler(http.StatusOK, "[]"))

clientsideSdkRouter := router.PathPrefix("/sdk").Subrouter()
clientsideSdkRouter.Use(CorsHeaders)
clientsideSdkRouter.Methods("OPTIONS").HandlerFunc(ConstantResponseHandler(http.StatusOK, ""))
clientsideSdkRouter.Use(GetProjectKeyFromEnvIdParameter("envId"))
clientsideSdkRouter.HandleFunc("/goals/{envId}", ConstantResponseHandler(http.StatusOK, "[]"))
clientsideSdkRouter.HandleFunc("/evalx/{envId}/contexts/{contextBase64}", GetClientFlags)
/*
/all GET stream. SSE stream for all data
✅ /bulk POST events. Receives analytics events from SDKs
Expand Down
Empty file.
6 changes: 6 additions & 0 deletions vendor/github.com/felixge/httpsnoop/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions vendor/github.com/felixge/httpsnoop/LICENSE.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions vendor/github.com/felixge/httpsnoop/Makefile

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading