diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index eda5cb7..cee415f 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -4,6 +4,7 @@ on: branches: - main pull_request: + merge_group: permissions: # Required: allow read access to the content for analysis. @@ -28,4 +29,4 @@ jobs: uses: golangci/golangci-lint-action@v6 with: version: v1.64 - only-new-issues: true \ No newline at end of file + only-new-issues: true diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index a8a51f3..e9dfec9 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -57,7 +57,7 @@ jobs: go-version-file: 'go.mod' - name: Run all integration tests with Dagger - uses: dagger/dagger-for-github@v5 + uses: dagger/dagger-for-github@b81317a976cb7f7125469707321849737cd1b3bc # v7 with: workdir: . verb: run diff --git a/Dockerfile b/Dockerfile index d7e3766..508dcf8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.21 +FROM alpine:3.22 COPY ./openfeature usr/local/bin/openfeature diff --git a/Makefile b/Makefile index 78fb0ed..33a1f50 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,11 @@ test-integration-csharp: @echo "Running C# integration test with Dagger..." @go run ./test/integration/cmd/csharp/run.go +.PHONY: test-integration-go +test-integration-go: + @echo "Running Go integration test with Dagger..." + @go run ./test/integration/cmd/go/run.go + .PHONY: test-integration test-integration: @echo "Running all integration tests with Dagger..." diff --git a/go.mod b/go.mod index 3587cc6..d4ad6b2 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,18 @@ module github.com/open-feature/cli go 1.23.0 require ( - dagger.io/dagger v0.18.9 + dagger.io/dagger v0.18.10 github.com/google/go-cmp v0.7.0 github.com/iancoleman/strcase v0.3.0 github.com/invopop/jsonschema v0.13.0 - github.com/pterm/pterm v0.12.80 + github.com/pterm/pterm v0.12.81 github.com/spf13/afero v1.14.0 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/text v0.25.0 + golang.org/x/text v0.26.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -22,7 +22,7 @@ require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/schedule v0.1.0 // indirect - github.com/99designs/gqlgen v0.17.73 // indirect + github.com/99designs/gqlgen v0.17.74 // indirect github.com/Khan/genqlient v0.8.1 // indirect github.com/adrg/xdg v0.5.3 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect @@ -75,12 +75,12 @@ require ( go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20250530174510-65e920069ea6 // indirect - golang.org/x/net v0.40.0 // indirect - golang.org/x/sync v0.14.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sync v0.15.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect - google.golang.org/grpc v1.72.2 // indirect + google.golang.org/grpc v1.73.0 // indirect google.golang.org/protobuf v1.36.6 // indirect ) diff --git a/go.sum b/go.sum index b583be8..88c7307 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,12 @@ atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= dagger.io/dagger v0.18.9 h1:IXZhlGm893LuqYFpo6VHtaCAEP6Qz0VjMhLvyKQVl1Y= dagger.io/dagger v0.18.9/go.mod h1:e6Y+sAPWh04pHvBf4s3sSiOe1QMoCEcccmMv898RnZA= +dagger.io/dagger v0.18.10 h1:Ibyz5LqxjjEHfLMlaU9PJ3xt3ju7p29RWy0lVfvSNU0= +dagger.io/dagger v0.18.10/go.mod h1:VSj+2HMd/EnaCVt7gTY70p8LBW+oQDYjA1XTadr8vBE= github.com/99designs/gqlgen v0.17.73 h1:A3Ki+rHWqKbAOlg5fxiZBnz6OjW3nwupDHEG15gEsrg= github.com/99designs/gqlgen v0.17.73/go.mod h1:2RyGWjy2k7W9jxrs8MOQthXGkD3L3oGr0jXW3Pu8lGg= +github.com/99designs/gqlgen v0.17.74 h1:1FuVtkXxOc87xpKio3f6sohREmec+Jvy86PcYOuwgWo= +github.com/99designs/gqlgen v0.17.74/go.mod h1:a+iR6mfRLNRp++kDpooFHiPWYiWX3Yu1BIilQRHgh10= github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= @@ -106,6 +110,8 @@ github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5b github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= github.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg= github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaKGQlHo= +github.com/pterm/pterm v0.12.81 h1:ju+j5I2++FO1jBKMmscgh5h5DPFDFMB7epEjSoKehKA= +github.com/pterm/pterm v0.12.81/go.mod h1:TyuyrPjnxfwP+ccJdBTeWHtd/e0ybQHkOS/TakajZCw= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -206,11 +212,15 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -238,6 +248,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -249,6 +261,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/test/csharp-integration/CompileTest.csproj b/test/csharp-integration/CompileTest.csproj index 43c7d33..8d6c2f8 100644 --- a/test/csharp-integration/CompileTest.csproj +++ b/test/csharp-integration/CompileTest.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/test/go-integration/go.mod b/test/go-integration/go.mod new file mode 100644 index 0000000..318d908 --- /dev/null +++ b/test/go-integration/go.mod @@ -0,0 +1,13 @@ +module github.com/open-feature/cli/test/go-integration + +go 1.22 + +require github.com/open-feature/go-sdk v1.15.0 + +require ( + github.com/go-logr/logr v1.4.1 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.6.0 // indirect + golang.org/x/net v0.21.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect +) \ No newline at end of file diff --git a/test/go-integration/test.go b/test/go-integration/test.go new file mode 100644 index 0000000..10fba10 --- /dev/null +++ b/test/go-integration/test.go @@ -0,0 +1,90 @@ +package main + +import ( + "context" + "fmt" + "os" + + generated "github.com/open-feature/cli/test/go-integration/openfeature" + "github.com/open-feature/go-sdk/openfeature" + "github.com/open-feature/go-sdk/openfeature/memprovider" +) + +func main() { + if err := run(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func run() error { + // Set up the in-memory provider with test flags + provider := memprovider.NewInMemoryProvider(map[string]memprovider.InMemoryFlag{ + "discountPercentage": { + State: memprovider.Enabled, + DefaultVariant: "default", + Variants: map[string]any{ + "default": 0.15, + }, + }, + "enableFeatureA": { + State: memprovider.Enabled, + DefaultVariant: "default", + Variants: map[string]any{ + "default": false, + }, + }, + "greetingMessage": { + State: memprovider.Enabled, + DefaultVariant: "default", + Variants: map[string]any{ + "default": "Hello there!", + }, + }, + "usernameMaxLength": { + State: memprovider.Enabled, + DefaultVariant: "default", + Variants: map[string]any{ + "default": 50, + }, + }, + }) + + // Set the provider and wait for it to be ready + err := openfeature.SetProviderAndWait(provider) + if err != nil { + return fmt.Errorf("Failed to set provider: %w", err) + } + + ctx := context.Background() + evalCtx := openfeature.NewEvaluationContext("someid", map[string]any{}) + + // Use the generated code for all flag evaluations + enableFeatureA, err := generated.EnableFeatureA.Value(ctx, evalCtx) + if err != nil { + return fmt.Errorf("Error evaluating boolean flag: %w", err) + } + fmt.Printf("enableFeatureA: %v\n", enableFeatureA) + + discount, err := generated.DiscountPercentage.Value(ctx, evalCtx) + if err != nil { + return fmt.Errorf("Failed to get discount: %w", err) + } + fmt.Printf("Discount Percentage: %.2f\n", discount) + + greetingMessage, err := generated.GreetingMessage.Value(ctx, evalCtx) + if err != nil { + return fmt.Errorf("Error evaluating string flag: %w", err) + } + fmt.Printf("greetingMessage: %v\n", greetingMessage) + + usernameMaxLength, err := generated.UsernameMaxLength.Value(ctx, evalCtx) + if err != nil { + return fmt.Errorf("Error evaluating int flag: %v\n", err) + } + fmt.Printf("usernameMaxLength: %v\n", usernameMaxLength) + + fmt.Println("Generated Go code compiles successfully!") + + return nil +} diff --git a/test/integration/cmd/go/run.go b/test/integration/cmd/go/run.go new file mode 100644 index 0000000..93a5cda --- /dev/null +++ b/test/integration/cmd/go/run.go @@ -0,0 +1,101 @@ +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "dagger.io/dagger" + "github.com/open-feature/cli/test/integration" +) + +// Test implements the integration test for the Go generator +type Test struct { + // ProjectDir is the absolute path to the root of the project + ProjectDir string + // TestDir is the absolute path to the test directory + TestDir string +} + +// New creates a new Test +func New(projectDir, testDir string) *Test { + return &Test{ + ProjectDir: projectDir, + TestDir: testDir, + } +} + +// Run executes the Go integration test using Dagger +func (t *Test) Run(ctx context.Context, client *dagger.Client) (*dagger.Container, error) { + // Source code container + source := client.Host().Directory(t.ProjectDir) + testFiles := client.Host().Directory(t.TestDir, dagger.HostDirectoryOpts{ + Include: []string{"test.go", "go.mod"}, + }) + + // Build the CLI + cli := client.Container(). + From("golang:1.23-alpine"). + WithExec([]string{"apk", "add", "--no-cache", "git"}). + WithDirectory("/src", source). + WithWorkdir("/src"). + WithExec([]string{"go", "mod", "tidy"}). + WithExec([]string{"go", "mod", "download"}). + WithExec([]string{"go", "build", "-o", "cli", "./cmd/openfeature"}) + + // Generate Go client + generated := cli.WithExec([]string{ + "./cli", "generate", "go", + "--manifest=/src/sample/sample_manifest.json", + "--output=/tmp/generated", + "--package-name=openfeature", + }) + + // Get generated files + generatedFiles := generated.Directory("/tmp/generated") + + // Test Go compilation with the generated files + goContainer := client.Container(). + From("golang:1.23-alpine"). + WithExec([]string{"apk", "add", "--no-cache", "git"}). + WithWorkdir("/app"). + WithDirectory("/app", testFiles). + WithDirectory("/app/openfeature", generatedFiles). + WithExec([]string{"go", "mod", "tidy"}). + WithExec([]string{"go", "build", "-o", "test", "-v"}). + WithExec([]string{"./test"}) + + return goContainer, nil +} + +// Name returns the name of the integration test +func (t *Test) Name() string { + return "go" +} + +func main() { + ctx := context.Background() + + // Get project root + projectDir, err := filepath.Abs(os.Getenv("PWD")) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to get project dir: %v\n", err) + os.Exit(1) + } + + // Get test directory + testDir, err := filepath.Abs(filepath.Join(projectDir, "test/go-integration")) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to get test dir: %v\n", err) + os.Exit(1) + } + + // Create and run the Go integration test + test := New(projectDir, testDir) + + if err := integration.RunTest(ctx, test); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} diff --git a/test/integration/cmd/run.go b/test/integration/cmd/run.go index 0306461..20e6c3e 100644 --- a/test/integration/cmd/run.go +++ b/test/integration/cmd/run.go @@ -20,6 +20,15 @@ func main() { os.Exit(1) } + // Run the Go integration test + goCmd := exec.Command("go", "run", "github.com/open-feature/cli/test/integration/cmd/go") + goCmd.Stdout = os.Stdout + goCmd.Stderr = os.Stderr + if err := goCmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "Error running Go integration test: %v\n", err) + os.Exit(1) + } + // Add more tests here as they are available fmt.Println("=== All integration tests passed successfully ===")