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
6 changes: 3 additions & 3 deletions docs/commands/openfeature_init.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ openfeature init [flags]
### Options

```
--flag-source-url string The URL of the flag source
-h, --help help for init
--override Override an existing configuration
-h, --help help for init
--override Override an existing configuration
--provider-url string The URL of the flag provider
```

### Options inherited from parent commands
Expand Down
8 changes: 4 additions & 4 deletions docs/commands/openfeature_pull.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ openfeature pull [flags]
### Options

```
--auth-token string The auth token for the flag source
--flag-source-url string The URL of the flag source
-h, --help help for pull
--no-prompt Disable interactive prompts for missing default values
--auth-token string The auth token for the flag provider
-h, --help help for pull
--no-prompt Disable interactive prompts for missing default values
--provider-url string The URL of the flag provider
```

### Options inherited from parent commands
Expand Down
20 changes: 10 additions & 10 deletions docs/commands/openfeature_push.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,25 @@ openfeature push [flags]

```
# Push flags to a remote HTTPS endpoint (smart push: creates and updates as needed)
openfeature push --flag-source-url https://api.example.com --auth-token secret-token
openfeature push --provider-url https://api.example.com --auth-token secret-token

# Push flags to an HTTP endpoint (development)
openfeature push --flag-source-url http://localhost:8080
openfeature push --provider-url http://localhost:8080

# Dry run to preview what would be sent
openfeature push --flag-source-url https://api.example.com --dry-run
openfeature push --provider-url https://api.example.com --dry-run
```

### Options

```
--auth-token string The auth token for the flag destination
--debug Enable debug logging
--dry-run Preview changes without pushing
--flag-source-url string The URL of the flag destination
-h, --help help for push
-m, --manifest string Path to the flag manifest (default "flags.json")
--no-input Disable interactive prompts
--auth-token string The auth token for the flag provider
--debug Enable debug logging
--dry-run Preview changes without pushing
-h, --help help for push
-m, --manifest string Path to the flag manifest (default "flags.json")
--no-input Disable interactive prompts
--provider-url string The URL of the flag provider
```

### SEE ALSO
Expand Down
53 changes: 29 additions & 24 deletions internal/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ func GetInitCmd() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
manifestPath := config.GetManifestPath(cmd)
override := config.GetOverride(cmd)
flagSourceUrl := config.GetFlagSourceUrl(cmd)
providerUrl := config.GetFlagSourceUrl(cmd)

if err := handleManifestCreation(manifestPath, override); err != nil {
return err
}

if err := handleConfigFile(flagSourceUrl, override); err != nil {
if err := handleConfigFile(providerUrl, override); err != nil {
return err
}

Expand Down Expand Up @@ -84,46 +84,46 @@ func handleManifestCreation(manifestPath string, override bool) error {
return nil
}

func handleConfigFile(flagSourceUrl string, override bool) error {
func handleConfigFile(providerURL string, override bool) error {
configPath := ".openfeature.yaml"
configExists, err := filesystem.Exists(configPath)
if err != nil {
return fmt.Errorf("failed to check if config file exists: %w", err)
}

if !configExists {
return writeConfigFile(flagSourceUrl, "Creating .openfeature.yaml configuration file")
return writeConfigFile(providerURL, "Creating .openfeature.yaml configuration file")
}

if flagSourceUrl == "" {
if providerURL == "" {
return nil // no config to write
}

if override {
return writeConfigFile(flagSourceUrl, "Updating flag source URL in .openfeature.yaml")
return writeConfigFile(providerURL, "Updating provider URL in .openfeature.yaml")
}

shouldOverride, err := confirmOverride("configuration file", configPath)
if err != nil {
return fmt.Errorf("failed to get user confirmation: %w", err)
}
if shouldOverride {
return writeConfigFile(flagSourceUrl, "Updating flag source URL in .openfeature.yaml")
return writeConfigFile(providerURL, "Updating provider URL in .openfeature.yaml")
}

logger.Default.Info("Configuration file was not modified.")
return nil
}

func writeConfigFile(flagSourceUrl, message string) error {
pterm.Info.Println(message, pterm.LightWhite(flagSourceUrl))
template := getConfigTemplate(flagSourceUrl)
func writeConfigFile(providerURL, message string) error {
pterm.Info.Println(message, pterm.LightWhite(providerURL))
template := getConfigTemplate(providerURL)
return filesystem.WriteFile(".openfeature.yaml", []byte(template))
}

type configTemplateData struct {
FlagSourceURL string
HasFlagSourceURL bool
ProviderURL string
HasProviderURL bool
}

const configTemplateText = `# OpenFeature CLI Configuration
Expand All @@ -134,11 +134,11 @@ const configTemplateText = `# OpenFeature CLI Configuration
# Path to your flag manifest file (default: "flags.json")
# manifest: "flags.json"

# URL of your flag source for the 'pull' command
# Supports http://, https://, and file:// protocols
{{if .HasFlagSourceURL}}flagSourceURL: {{.FlagSourceURL}}{{else}}# flagSourceUrl: "https://your-flag-service.com/api/flags"{{end}}
# URL of your flag provider for the 'pull' and 'push' commands
# Supports http://, https://, and file:// protocols (file:// only for pull)
{{if .HasProviderURL}}provider: {{.ProviderURL}}{{else}}# provider: "https://your-flag-service.com/api/flags"{{end}}

# Authentication token for remote flag sources (if required)
# Authentication token for remote flag providers (if required)
# authToken: "your-bearer-token"

# Enable debug logging (default: false)
Expand All @@ -151,36 +151,41 @@ const configTemplateText = `# OpenFeature CLI Configuration
# Override global settings for specific commands

# pull:
# flag-source-url: "https://api.example.com/flags"
# provider: "https://api.example.com/flags"
# auth-token: "pull-specific-token"
# no-prompt: false

# push:
# provider: "https://api.example.com/flags"
# auth-token: "push-specific-token"
# dry-run: false

# generate:
# output: "generated"
#
#
# # Language-specific generator options
# go:
# output: "go/flags"
# package-name: "openfeature"
#
#
# typescript:
# output: "ts/flags"
#
#
# csharp:
# output: "csharp/flags"
# namespace: "OpenFeature"
#
#
# java:
# output: "java/flags"
# package-name: "com.example.openfeature"
`

func getConfigTemplate(flagSourceUrl string) string {
func getConfigTemplate(providerURL string) string {
tmpl := template.Must(template.New("config").Parse(configTemplateText))

data := configTemplateData{
FlagSourceURL: flagSourceUrl,
HasFlagSourceURL: flagSourceUrl != "",
ProviderURL: providerURL,
HasProviderURL: providerURL != "",
}

var buf bytes.Buffer
Expand Down
14 changes: 7 additions & 7 deletions internal/cmd/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,17 @@ Why pull from a remote source:
return initializeConfig(cmd, "pull")
},
RunE: func(cmd *cobra.Command, args []string) error {
flagSourceUrl := config.GetFlagSourceUrl(cmd)
providerURL := config.GetFlagSourceUrl(cmd)
manifestPath := config.GetManifestPath(cmd)
authToken := config.GetAuthToken(cmd)
noPrompt := config.GetNoPrompt(cmd)

if flagSourceUrl == "" {
return fmt.Errorf("flagSourceUrl not set in config")
if providerURL == "" {
return fmt.Errorf("provider URL not set in config. Please provide --provider-url or set 'provider' in .openfeature.yaml")
}

// fetch the flags from the remote source
parsedURL, err := url.Parse(flagSourceUrl)
parsedURL, err := url.Parse(providerURL)
if err != nil {
return fmt.Errorf("invalid URL: %w", err)
}
Expand All @@ -69,14 +69,14 @@ Why pull from a remote source:
urlContainsAFileExtension := manifest.URLLooksLikeAFile(parsedURL.String())
if urlContainsAFileExtension {
// Use direct HTTP requests for pulling flags from file-like URLs
loadedFlags, err := manifest.LoadFromRemote(flagSourceUrl, authToken)
loadedFlags, err := manifest.LoadFromRemote(providerURL, authToken)
if err != nil {
return fmt.Errorf("error fetching flags from remote source: %w", err)
}
flags = loadedFlags
} else {
// Use the sync API client for pulling flags
loadedFlags, err := manifest.LoadFromSyncAPI(flagSourceUrl, authToken)
loadedFlags, err := manifest.LoadFromSyncAPI(providerURL, authToken)
if err != nil {
return fmt.Errorf("error fetching flags from remote source: %w", err)
}
Expand All @@ -101,7 +101,7 @@ Why pull from a remote source:
}
}

pterm.Success.Printfln("Successfully fetched flags from %s", flagSourceUrl)
pterm.Success.Printfln("Successfully fetched flags from %s", providerURL)
if err := manifest.Write(manifestPath, *flags); err != nil {
return fmt.Errorf("error writing manifest: %w", err)
}
Expand Down
72 changes: 63 additions & 9 deletions internal/cmd/pull_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func setupTest(t *testing.T) afero.Fs {
}

func TestPull(t *testing.T) {
t.Run("pull no flag source url", func(t *testing.T) {
t.Run("pull no provider url", func(t *testing.T) {
setupTest(t)
cmd := GetPullCmd()

Expand All @@ -35,10 +35,10 @@ func TestPull(t *testing.T) {
// Run command
err := cmd.Execute()
assert.Error(t, err)
assert.Contains(t, err.Error(), "flagSourceUrl not set in config")
assert.Contains(t, err.Error(), "provider URL not set in config")
})

t.Run("pull with flag source url", func(t *testing.T) {
t.Run("pull with provider url", func(t *testing.T) {
fs := setupTest(t)
defer gock.Off()

Expand Down Expand Up @@ -74,7 +74,7 @@ func TestPull(t *testing.T) {
// Prepare command arguments - use base URL only
args := []string{
"pull",
"--flag-source-url", "https://example.com",
"--provider-url", "https://example.com",
"--manifest", "manifest/path.json",
}

Expand Down Expand Up @@ -124,7 +124,7 @@ func TestPull(t *testing.T) {
// Prepare command arguments - use base URL only
args := []string{
"pull",
"--flag-source-url", "https://example.com",
"--provider-url", "https://example.com",
"--manifest", "manifest/path.json",
}

Expand Down Expand Up @@ -165,7 +165,7 @@ func TestPull(t *testing.T) {
// Prepare command arguments - URL with .json extension
args := []string{
"pull",
"--flag-source-url", "https://example.com/flags.json",
"--provider-url", "https://example.com/flags.json",
"--manifest", "manifest/path.json",
}

Expand Down Expand Up @@ -218,7 +218,7 @@ func TestPull(t *testing.T) {
// Prepare command arguments - URL with .yaml extension
args := []string{
"pull",
"--flag-source-url", "https://example.com/flags.yaml",
"--provider-url", "https://example.com/flags.yaml",
"--manifest", "manifest/path.json",
}

Expand Down Expand Up @@ -271,7 +271,7 @@ func TestPull(t *testing.T) {
// Prepare command arguments - URL with .yml extension
args := []string{
"pull",
"--flag-source-url", "https://example.com/config.yml",
"--provider-url", "https://example.com/config.yml",
"--manifest", "manifest/path.json",
}

Expand Down Expand Up @@ -325,7 +325,7 @@ func TestPull(t *testing.T) {
// Prepare command arguments - base URL without file extension
args := []string{
"pull",
"--flag-source-url", "https://api.example.com",
"--provider-url", "https://api.example.com",
"--manifest", "manifest/path.json",
}

Expand All @@ -348,4 +348,58 @@ func TestPull(t *testing.T) {
_, exists := flags["syncApiFlag"]
assert.True(t, exists, "Flag syncApiFlag should exist in manifest")
})

t.Run("backward compatibility with deprecated --flag-source-url", func(t *testing.T) {
fs := setupTest(t)
defer gock.Off()

// Mock response in OpenAPI ManifestEnvelope format
manifestResponse := map[string]any{
"flags": []map[string]any{
{
"key": "backwardCompatFlag",
"type": "boolean",
"defaultValue": true,
"description": "Test backward compatibility",
},
},
}

// Mock the sync API endpoint
gock.New("https://example.com").
Get("/openfeature/v0/manifest").
Reply(200).
JSON(manifestResponse)

cmd := GetPullCmd()

// global flag exists on root only.
config.AddRootFlags(cmd)

// Use the deprecated flag to test backward compatibility
args := []string{
"pull",
"--flag-source-url", "https://example.com",
"--manifest", "manifest/path.json",
}

cmd.SetArgs(args)

// Run command - should work but show deprecation warning
err := cmd.Execute()
assert.NoError(t, err)

// Verify the manifest was written correctly
content, err := afero.ReadFile(fs, "manifest/path.json")
assert.NoError(t, err)

var manifestFlags map[string]interface{}
err = json.Unmarshal(content, &manifestFlags)
assert.NoError(t, err)

// Verify the flag exists in the manifest
flags := manifestFlags["flags"].(map[string]interface{})
_, exists := flags["backwardCompatFlag"]
assert.True(t, exists, "Flag backwardCompatFlag should exist in manifest")
})
}
Loading
Loading