Skip to content

Commit c517e87

Browse files
kriscolemanjsalaberbeeme1mr
authored
feat: openfeature pull command (#147)
* feat: added functionality to create yaml config file and preliminary pull command Signed-off-by: Jason Salaber <[email protected]> feat: added prompts for default values if not defined Signed-off-by: Jason Salaber <[email protected]> chore: conditionally add auth header if auth token is not empty Signed-off-by: Jason Salaber <[email protected]> chore: added tests for pull command Signed-off-by: Jason Salaber <[email protected]> feat: added ability to pull from local file for source flags Signed-off-by: Jason Salaber <[email protected]> chore: refactor of pull command Signed-off-by: Kris Coleman <[email protected]> * refactor(pull): improves default value prompting Refactors the default value prompting logic for flags to handle errors more gracefully. The changes improve error handling and ensure that the user is prompted again if an invalid input is provided. Also ensures object types cannot be prompted for as they should be defined in code. Signed-off-by: Kris Coleman <[email protected]> * refactor(init): streamlines config file creation logic Consolidates the conditional logic for creating or updating the .openfeature.yaml file. This change improves readability and reduces code duplication by using a flag to determine whether the config file should be written. Signed-off-by: Kris Coleman <[email protected]> * refactor(manifest): improves remote flag loading Refactors remote flag loading to handle errors more gracefully. This change ensures that the function returns nil immediately upon encountering an error, preventing potential issues with uninitialized flagsets. Additionally, initializes the flagset object only when standard manifest parsing is attempted. It then returns the flagset with source flags upon successful parsing. Signed-off-by: Kris Coleman <[email protected]> * refactor: uses generic prompt for input validation Refactors the prompt logic for integer and float default values to use a new generic function. This improves code reusability and reduces duplication by centralizing the input validation logic. Signed-off-by: Kris Coleman <[email protected]> * refactor: improves prompt readability Moves the prompt definition outside the switch statement to avoid repetition and improves code readability. Signed-off-by: Kris Coleman <[email protected]> * refactors init command for clarity Refactors the init command to improve readability and maintainability. Moves logic for manifest creation and config file handling into separate functions for better organization. Introduces a confirmOverride function to reduce duplication and improve user experience when overriding existing files. Signed-off-by: Kris Coleman <[email protected]> * fix: handles errors during confirmation prompts This commit improves error handling in the init command. It addresses the issue where errors during confirmation prompts were not being properly handled, leading to unexpected behavior. Now, the application gracefully handles errors that may arise during the confirmation prompt, providing a more robust user experience. Signed-off-by: Kris Coleman <[email protected]> * refactor(init): uses template for config file Refactors the way the default configuration file is generated. Instead of using string concatenation, it uses a template to generate the configuration file. This makes the configuration file more readable and easier to maintain and allows to handle conditional content. Signed-off-by: Kris Coleman <[email protected]> * feat: adds json marshalling for flagset Adds the ability to marshal a Flagset into JSON format, making it compatible with the expected manifest structure. Also updates the write function to use the new marshalling logic to properly format the flags in the manifest file. Signed-off-by: Kris Coleman <[email protected]> * refactor: improves manifest creation and writing Refactors manifest creation and writing logic into separate, reusable functions. This improves code readability and reduces duplication by extracting the manifest creation and writing processes into dedicated functions. This allows for more consistent handling of manifest operations across different parts of the codebase. Signed-off-by: Kris Coleman <[email protected]> * refactor(manifest): uses afero filesystem for file access Uses the abstract afero filesystem to read the flags file. This change improves testability and allows for easier manipulation of the filesystem in different environments. Signed-off-by: Kris Coleman <[email protected]> * fix: writes manifest file atomically Ensures that the manifest file is written to disk atomically. This prevents potential data corruption or incomplete writes if the process is interrupted during the write operation. This implementation writes the manifest to a temporary file, then renames it to the target path, guaranteeing atomicity. Signed-off-by: Kris Coleman <[email protected]> * refactor: moves prompt functions to the bottom Moves the promptWithValidation and promptForDefaultValue functions to the bottom of the file. This improves code organization and readability by grouping related functionality together. Signed-off-by: Kris Coleman <[email protected]> * fix: uses pointer for flags to handle defaults Fixes a bug where flag default values were not being correctly handled when prompting the user for input. This commit updates the code to use a pointer to the flag, ensuring that the default value is correctly assigned after prompting. Signed-off-by: Kris Coleman <[email protected]> * fix: corrects json unmarshaling error handling Updates the error handling when unmarshaling JSON to provide a more accurate error message. Refactors the flagset writing process to directly process flag data, avoiding intermediate marshaling and unmarshaling steps. This simplifies the code and improves efficiency. Signed-off-by: Kris Coleman <[email protected]> * refactor(pull): improves url handling for flag sources Refactors the flag source URL handling to use the `net/url` package. This change improves the robustness and clarity of the code by utilizing the `net/url` package to parse and validate the flag source URL. It now supports `file`, `http`, and `https` schemes. Signed-off-by: Kris Coleman <[email protected]> * fix(manifest): handles fs.remove errors gracefully Ensures that errors during temporary file removal are handled gracefully, preventing potential resource leaks or incomplete operations. It prioritizes the original error during manifest writing/renaming and logs the removal error to avoid masking the initial issue. Signed-off-by: Kris Coleman <[email protected]> * wip: lint fixes Signed-off-by: Kris Coleman <[email protected]> --------- Signed-off-by: Kris Coleman <[email protected]> Co-authored-by: Jason Salaber <[email protected]> Co-authored-by: Michael Beemer <[email protected]>
1 parent 9d635ac commit c517e87

32 files changed

+914
-164
lines changed

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*.dll
88
*.so
99
*.dylib
10+
cli
1011

1112
# Test binary, built with `go test -c`
1213
*.test
@@ -33,4 +34,8 @@ dist
3334
node_modules/
3435
npm-debug.log*
3536
generated/
36-
*.log
37+
*.log
38+
39+
# generated files from running the CLI
40+
flags.json
41+
generated/

docs/commands/openfeature.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ openfeature [flags]
2626
* [openfeature compare](openfeature_compare.md) - Compare two feature flag manifests
2727
* [openfeature generate](openfeature_generate.md) - Generate typesafe OpenFeature accessors.
2828
* [openfeature init](openfeature_init.md) - Initialize a new project
29+
* [openfeature pull](openfeature_pull.md) - Pull a flag manifest from a remote source
2930
* [openfeature version](openfeature_version.md) - Print the version number of the OpenFeature CLI
3031

docs/commands/openfeature_init.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ openfeature init [flags]
1515
### Options
1616

1717
```
18-
-h, --help help for init
19-
--override Override an existing configuration
18+
--flag-source-url string The URL of the flag source
19+
-h, --help help for init
20+
--override Override an existing configuration
2021
```
2122

2223
### Options inherited from parent commands

docs/commands/openfeature_pull.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!-- markdownlint-disable-file -->
2+
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
3+
## openfeature pull
4+
5+
Pull a flag manifest from a remote source
6+
7+
### Synopsis
8+
9+
Pull a flag manifest from a remote source.
10+
11+
This command fetches feature flag configurations from a specified remote source and saves them locally as a manifest file.
12+
13+
Supported URL schemes:
14+
- http:// - HTTP remote sources
15+
- https:// - HTTPS remote sources
16+
- file:// - Local file paths
17+
18+
How it works:
19+
1. Connects to the specified flag source URL
20+
2. Downloads the flag configuration data
21+
3. Validates and processes each flag definition
22+
4. Prompts for missing default values (unless --no-prompt is used)
23+
5. Writes the complete manifest to the local file system
24+
25+
Why pull from a remote source:
26+
- Centralized flag management: Keep all flag definitions in a central repository or service
27+
- Team collaboration: Share flag configurations across team members and environments
28+
- Version control: Track changes to flag configurations over time
29+
- Environment consistency: Ensure the same flag definitions are used across different environments
30+
- Configuration as code: Treat flag definitions as versioned artifacts that can be reviewed and deployed
31+
32+
```
33+
openfeature pull [flags]
34+
```
35+
36+
### Options
37+
38+
```
39+
--auth-token string The auth token for the flag source
40+
--flag-source-url string The URL of the flag source
41+
-h, --help help for pull
42+
--no-prompt Disable interactive prompts for missing default values
43+
```
44+
45+
### Options inherited from parent commands
46+
47+
```
48+
--debug Enable debug logging
49+
-m, --manifest string Path to the flag manifest (default "flags.json")
50+
--no-input Disable interactive prompts
51+
```
52+
53+
### SEE ALSO
54+
55+
* [openfeature](openfeature.md) - CLI for OpenFeature.
56+

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23.0
55
require (
66
dagger.io/dagger v0.18.12
77
github.com/google/go-cmp v0.7.0
8+
github.com/h2non/gock v1.2.0
89
github.com/iancoleman/strcase v0.3.0
910
github.com/invopop/jsonschema v0.13.0
1011
github.com/pterm/pterm v0.12.81
@@ -38,6 +39,7 @@ require (
3839
github.com/google/uuid v1.6.0 // indirect
3940
github.com/gookit/color v1.5.4 // indirect
4041
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
42+
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
4143
github.com/inconshreveable/mousetrap v1.1.0 // indirect
4244
github.com/lithammer/fuzzysearch v1.1.8 // indirect
4345
github.com/mailru/easyjson v0.9.0 // indirect

go.sum

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,8 @@ atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
66
atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
77
atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=
88
atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
9-
dagger.io/dagger v0.18.10 h1:Ibyz5LqxjjEHfLMlaU9PJ3xt3ju7p29RWy0lVfvSNU0=
10-
dagger.io/dagger v0.18.10/go.mod h1:VSj+2HMd/EnaCVt7gTY70p8LBW+oQDYjA1XTadr8vBE=
11-
dagger.io/dagger v0.18.11 h1:6lSfemlbGM2HmdOjhgevrX2+orMDGKU/xTaBMZ+otyY=
12-
dagger.io/dagger v0.18.11/go.mod h1:azlZ24m2br95t0jQHUBpL5SiafeqtVDLl1Itlq6GO+4=
139
dagger.io/dagger v0.18.12 h1:s7v8aHlzDUogZ/jW92lHC+gljCNRML+0mosfh13R4vs=
1410
dagger.io/dagger v0.18.12/go.mod h1:azlZ24m2br95t0jQHUBpL5SiafeqtVDLl1Itlq6GO+4=
15-
github.com/99designs/gqlgen v0.17.74 h1:1FuVtkXxOc87xpKio3f6sohREmec+Jvy86PcYOuwgWo=
16-
github.com/99designs/gqlgen v0.17.74/go.mod h1:a+iR6mfRLNRp++kDpooFHiPWYiWX3Yu1BIilQRHgh10=
1711
github.com/99designs/gqlgen v0.17.75 h1:GwHJsptXWLHeY7JO8b7YueUI4w9Pom6wJTICosDtQuI=
1812
github.com/99designs/gqlgen v0.17.75/go.mod h1:p7gbTpdnHyl70hmSpM8XG8GiKwmCv+T5zkdY8U8bLog=
1913
github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs=
@@ -75,6 +69,10 @@ github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
7569
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
7670
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
7771
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
72+
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
73+
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
74+
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
75+
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
7876
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
7977
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
8078
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -102,6 +100,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
102100
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
103101
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
104102
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
103+
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
104+
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
105105
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
106106
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
107107
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -151,8 +151,6 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
151151
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
152152
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
153153
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
154-
github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s=
155-
github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
156154
github.com/vektah/gqlparser/v2 v2.5.28 h1:bIulcl3LF69ba6EiZVGD88y4MkM+Jxrf3P2MX8xLRkY=
157155
github.com/vektah/gqlparser/v2 v2.5.28/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
158156
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=

internal/cmd/compare.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"github.com/open-feature/cli/internal/manifest"
1111
"github.com/pterm/pterm"
1212
"github.com/spf13/cobra"
13-
"gopkg.in/yaml.v3"
13+
yaml "gopkg.in/yaml.v3"
1414
)
1515

1616
func GetCompareCmd() *cobra.Command {

internal/cmd/generate.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"strings"
55

66
"github.com/open-feature/cli/internal/config"
7-
"github.com/open-feature/cli/internal/flagset"
87
"github.com/open-feature/cli/internal/generators"
98
"github.com/open-feature/cli/internal/generators/csharp"
109
"github.com/open-feature/cli/internal/generators/golang"
@@ -14,6 +13,7 @@ import (
1413
"github.com/open-feature/cli/internal/generators/python"
1514
"github.com/open-feature/cli/internal/generators/react"
1615
"github.com/open-feature/cli/internal/logger"
16+
"github.com/open-feature/cli/internal/manifest"
1717
"github.com/spf13/cobra"
1818
)
1919

@@ -87,7 +87,7 @@ func getGenerateNodeJSCmd() *cobra.Command {
8787
OutputPath: outputPath,
8888
Custom: nodejs.Params{},
8989
}
90-
flagset, err := flagset.Load(manifestPath)
90+
flagset, err := manifest.LoadFlagSet(manifestPath)
9191
if err != nil {
9292
return err
9393
}
@@ -131,7 +131,7 @@ func getGenerateReactCmd() *cobra.Command {
131131
OutputPath: outputPath,
132132
Custom: react.Params{},
133133
}
134-
flagset, err := flagset.Load(manifestPath)
134+
flagset, err := manifest.LoadFlagSet(manifestPath)
135135
if err != nil {
136136
return err
137137
}
@@ -171,7 +171,7 @@ func GetGenerateNestJsCmd() *cobra.Command {
171171

172172
logger.Default.GenerationStarted("NestJS")
173173

174-
flagset, err := flagset.Load(manifestPath)
174+
flagset, err := manifest.LoadFlagSet(manifestPath)
175175
if err != nil {
176176
return err
177177
}
@@ -232,7 +232,7 @@ func getGenerateCSharpCmd() *cobra.Command {
232232
Namespace: namespace,
233233
},
234234
}
235-
flagset, err := flagset.Load(manifestPath)
235+
flagset, err := manifest.LoadFlagSet(manifestPath)
236236
if err != nil {
237237
return err
238238
}
@@ -283,7 +283,7 @@ func getGenerateJavaCmd() *cobra.Command {
283283
},
284284
}
285285

286-
flagset, err := flagset.Load(manifestPath)
286+
flagset, err := manifest.LoadFlagSet(manifestPath)
287287
if err != nil {
288288
return err
289289
}
@@ -335,7 +335,7 @@ func getGenerateGoCmd() *cobra.Command {
335335
},
336336
}
337337

338-
flagset, err := flagset.Load(manifestPath)
338+
flagset, err := manifest.LoadFlagSet(manifestPath)
339339
if err != nil {
340340
return err
341341
}
@@ -379,7 +379,7 @@ func getGeneratePythonCmd() *cobra.Command {
379379
OutputPath: outputPath,
380380
Custom: python.Params{},
381381
}
382-
flagset, err := flagset.Load(manifestPath)
382+
flagset, err := manifest.LoadFlagSet(manifestPath)
383383
if err != nil {
384384
return err
385385
}

0 commit comments

Comments
 (0)