Skip to content

Conversation

@kriscoleman
Copy link
Collaborator

@kriscoleman kriscoleman commented Oct 14, 2025

Summary

This PR introduces a new push command to the OpenFeature CLI, providing the inverse functionality of the existing pull command. The push command allows users to upload local flag configurations to remote flag management services.

Closes #166

Motivation

As discussed in #166, there has been considerable interest in having a push operation to complement the existing pull functionality. This implementation provides an opinionated first version focused on a straightforward use case: pushing to HTTP/HTTPS endpoints. This initial implementation will help inform the design of a future extensible plugin system.

Changes

Core Implementation

  • New push command (internal/cmd/push.go): Mirrors the structure and UX of the pull command
  • SaveToRemote function (internal/manifest/manage.go): Handles HTTP/HTTPS uploads with authentication
  • Configuration flags (internal/config/flags.go): Added push-specific flags and getters
  • Command registration (internal/cmd/root.go): Integrated push command into the CLI

Features

  • ✅ Push flag configurations to HTTP/HTTPS endpoints
  • ✅ Bearer token authentication support
  • ✅ Configurable HTTP methods (POST/PUT)
  • ✅ Dry-run mode to preview changes
  • ✅ Automatic conversion to array-based "source flags" format
  • ✅ Comprehensive validation and error messages

Testing

  • 12 comprehensive test cases (internal/cmd/push_test.go) covering:
    • HTTP/HTTPS endpoints
    • Authentication flows
    • Dry-run functionality
    • Error scenarios (404, 500, validation failures)
    • Request body format validation
    • Scheme validation (file://, unsupported schemes)

Documentation

  • Auto-generated command documentation
  • Clear usage examples
  • Detailed flag descriptions

Design Decisions

  1. No file:// support: Local file operations are better handled by standard shell commands (cp, mv)
  2. Array-based format: Converts to source flags format for compatibility with remote providers
  3. Validation: Ensures all flags have default values before pushing
  4. Default POST method: Most APIs expect POST for creating/updating resources

Usage Examples

# Push to HTTPS endpoint with authentication
openfeature push --flag-destination-url https://api.example.com/flags --auth-token secret-token

# Push using PUT method
openfeature push --flag-destination-url https://api.example.com/flags/my-app --method PUT

# Dry run to preview changes
openfeature push --flag-destination-url https://api.example.com/flags --dry-run

Testing

All tests pass:

go test ./internal/cmd -run TestPush -v

Future Considerations

This implementation lays the groundwork for future extensibility:

  • Plugin system for multiple providers
  • Additional authentication methods
  • Custom format transformations
  • Batch operations

Checklist

  • Tests pass locally
  • Documentation generated
  • Follows existing code patterns
  • Mirrors pull command UX

@kriscoleman
Copy link
Collaborator Author

@beeme1mr will need to test this with your flagd project

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new push command to the OpenFeature CLI that enables users to upload local flag configurations to remote flag management services via HTTP/HTTPS endpoints. The implementation provides the inverse functionality of the existing pull command with similar UX patterns.

  • New push command with HTTP/HTTPS support and authentication
  • Dry-run functionality for previewing changes before pushing
  • Comprehensive validation and error handling with detailed messages

Reviewed Changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
internal/manifest/manage.go Adds SaveToRemote function for HTTP/HTTPS uploads with authentication
internal/config/flags.go Adds push-specific command flags and configuration getters
internal/cmd/root.go Registers the new push command with the CLI
internal/cmd/push.go Core push command implementation with validation and URL scheme handling
internal/cmd/push_test.go Comprehensive test suite covering HTTP methods, authentication, and error scenarios
docs/commands/openfeature_push.md Auto-generated documentation for the push command
docs/commands/openfeature.md Updated main documentation to include push command reference

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@kriscoleman kriscoleman force-pushed the feat/push branch 2 times, most recently from a122e46 to d7082b5 Compare October 14, 2025 04:37
@kriscoleman kriscoleman requested a review from Copilot October 14, 2025 04:37
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@kriscoleman
Copy link
Collaborator Author

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a push command, which is a great addition for managing flag configurations. The implementation is well-structured and follows the existing patterns in the CLI. The test coverage for the new command is comprehensive and covers various success and failure scenarios.

I've identified a few areas for improvement:

  • There's an opportunity to make the configuration handling more efficient by removing redundant file reads.
  • Adding validation for the HTTP method in the push command would improve robustness.
  • There's some code duplication that could be refactored to improve maintainability.

Overall, this is a solid contribution. My detailed comments are below.

Comment on lines 127 to 130
func getConfigValueWithFallback(value string, configKey string) string {
if value != "" {
return value
}

viper.SetConfigName(".openfeature")
viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err != nil {
return ""
}
if !viper.IsSet(configKey) {
return ""
}
return viper.GetString(configKey)
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The getConfigValueWithFallback function re-configures and re-reads the viper configuration on every call. This is inefficient as it causes redundant file I/O. The initializeConfig function, called in PreRunE, is already responsible for setting up viper and reading the configuration file once.

This helper function should simply retrieve the value from the already-configured viper instance. viper.GetString(key) will return an empty string if the key is not found, which preserves the intended fallback logic.

func getConfigValueWithFallback(value string, configKey string) string {
	if value != "" {
		return value
	}
	return viper.GetString(configKey)
}

Comment on lines 43 to 44
httpMethod := config.GetPushMethod(cmd)
dryRun := config.GetDryRun(cmd)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The httpMethod is retrieved from the config but not validated. The help text says only POST and PUT are supported, but the code will accept any string. This could lead to unexpected behavior if a user provides an unsupported method. It's better to validate the method and return a clear error message.

I suggest converting the method to uppercase and checking if it's one of the supported methods (POST or PUT). You will need to add "strings" to your imports for this change.

            httpMethod := strings.ToUpper(config.GetPushMethod(cmd))
            if httpMethod != "POST" && httpMethod != "PUT" {
                return fmt.Errorf("invalid HTTP method: %s. Only POST and PUT are supported", config.GetPushMethod(cmd))
            }
            dryRun := config.GetDryRun(cmd)

Comment on lines 183 to 188
type SourceFlag struct {
Key string `json:"key"`
Type string `json:"type"`
Description string `json:"description"`
DefaultValue any `json:"defaultValue"`
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This SourceFlag struct is a duplicate of the one defined locally within LoadFromSourceFlags in internal/flagset/flagset.go. Duplicating struct definitions can lead to maintenance issues if they diverge.

To improve maintainability, consider defining SourceFlag as a public type in the flagset package and reusing it in both LoadFromSourceFlags and SaveToRemote.

@kriscoleman
Copy link
Collaborator Author

@beeme1mr suggested:

  • we should work the OpenAPI schema into this PR, I can work on that
  • --flag-destination-url and other flags for push should be consolidated around our other flags for consistency

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@beeme1mr would love your 👀 on this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a sync API that contains both a pull and push API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was originally thinking the push API would be for a single flag. I'll have to think about the implications of a bulk API like you've proposed. One challenge is dealing with partial successes. For example, one flag is successfully updated but another can't be because a property (say flag type) is immutable. I do think properties like flag type will be immutable is most flag management systems.

Let me mess around with this in my test app to try and identify any challenges we may run into.

@kriscoleman kriscoleman requested a review from Copilot October 22, 2025 16:46
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 15 out of 17 changed files in this pull request and generated 1 comment.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

"key": "usernameMaxLength",
"type": "integer",
"description": "Maximum allowed length for usernames.",
"defaultValue": float64(50), // JSON unmarshals numbers as float64
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The comment indicates JSON unmarshals integers as float64, but the expected value uses an explicit float64 conversion. This is correct for JSON unmarshaling behavior, but the test should verify that the API correctly handles integer types. Consider documenting whether the remote API is expected to accept float64 for integer flag types or if type preservation is required.

Suggested change
"defaultValue": float64(50), // JSON unmarshals numbers as float64
"defaultValue": 50, // Use integer to verify type preservation for integer flag

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a sync API that contains both a pull and push API.

application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
'201':
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A 201 doesn't make sense if we're returning a response body.

content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: The error example is invalid auth, which doesn't match a 400

application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we should also include a 403?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, I think that's a good idea

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was originally thinking the push API would be for a single flag. I'll have to think about the implications of a bulk API like you've proposed. One challenge is dealing with partial successes. For example, one flag is successfully updated but another can't be because a property (say flag type) is immutable. I do think properties like flag type will be immutable is most flag management systems.

Let me mess around with this in my test app to try and identify any challenges we may run into.

@kriscoleman
Copy link
Collaborator Author

I think this should be a sync API that contains both a pull and push API.
@beeme1mr I agree, I was planning on making the pull API spec in a follow up pr

Adds a new command to the CLI that allows users to push
local flag configurations to a remote flag management
service over HTTP/HTTPS.
It also prevents local file scheme usage to avoid overwriting
local files and validates if flags have default values set.

Signed-off-by: Kris Coleman <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: push command

2 participants