diff --git a/examples/Go/ConsoleApp/README.md b/examples/Go/ConsoleApp/README.md new file mode 100644 index 00000000..0283fe2a --- /dev/null +++ b/examples/Go/ConsoleApp/README.md @@ -0,0 +1,67 @@ +# Azure App Configuration Console App Example + +This example demonstrates how to use the refresh functionality of Azure App Configuration in a console/command-line application. + +## Overview + +This console application: + +1. Loads configuration values from Azure App Configuration +2. Binds them to target configuration struct +3. Automatically refreshes the configuration when changed in Azure App Configuration + +## Running the Example + +### Prerequisites + +You need [an Azure subscription](https://azure.microsoft.com/free/) and the following Azure resources to run the examples: + +- [Azure App Configuration store](https://learn.microsoft.com/en-us/azure/azure-app-configuration/quickstart-azure-app-configuration-create?tabs=azure-portal) + +The examples retrieve credentials to access your App Configuration store from environment variables. + +### Add key-values + +Add the following key-values to the App Configuration store and leave **Label** and **Content Type** with their default values: + +| Key | Value | +|------------------------|----------------| +| *Config.Message* | *Hello World!* | +| *Config.Font.Color* | *blue* | +| *Config.Font.Size* | *12* | + +### Setup + +1. Initialize a new Go module. + + ```bash + go mod init console-example-refresh + ``` +1. Add the Azure App Configuration provider as a dependency. + + ```bash + go get github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration + ``` + +1. Set the connection string as an environment variable: + + ```bash + # Windows + set AZURE_APPCONFIG_CONNECTION_STRING=your-connection-string + + # Linux/macOS + export AZURE_APPCONFIG_CONNECTION_STRING=your-connection-string + ``` + +### Run the Application + +```bash +go run main.go +``` + +### Testing the Refresh Functionality + +1. Start the application +2. While it's running, modify the values in your Azure App Configuration store +3. Within 10 seconds (the configured refresh interval), the application should detect and apply the changes +4. You don't need to restart the application to see the updated values \ No newline at end of file diff --git a/examples/Go/ConsoleApp/main.go b/examples/Go/ConsoleApp/main.go new file mode 100644 index 00000000..35f99f47 --- /dev/null +++ b/examples/Go/ConsoleApp/main.go @@ -0,0 +1,133 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "os/signal" + "syscall" + "time" + + "github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration" +) + +type Config struct { + Font Font + Message string +} + +type Font struct { + Color string + Size int +} + +func main() { + // Load configuration from Azure App Configuration + configProvider, err := loadAzureAppConfiguration() + if err != nil { + log.Fatalf("Error loading configuration: %s", err) + } + + // Parse initial configuration into struct + var config Config + err = configProvider.Unmarshal(&config, nil) + if err != nil { + log.Fatalf("Error unmarshalling configuration: %s", err) + } + + // Display the initial configuration + displayConfig(config) + + // Register refresh callback to update and display the configuration + configProvider.OnRefreshSuccess(func() { + fmt.Println("\n Configuration changed! Updating values...") + + // Re-unmarshal the configuration + var updatedConfig Config + err := configProvider.Unmarshal(&updatedConfig, nil) + if err != nil { + log.Printf("Error unmarshalling updated configuration: %s", err) + return + } + + // Update our working config + config = updatedConfig + + // Display the updated configuration + displayConfig(config) + }) + + // Setup a channel to listen for termination signals + done := make(chan os.Signal, 1) + signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) + + fmt.Println("\nWaiting for configuration changes...") + fmt.Println("(Update values in Azure App Configuration to see refresh in action)") + fmt.Println("Press Ctrl+C to exit") + + // Start a ticker to periodically trigger refresh + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + // Keep the application running until terminated + for { + select { + case <-ticker.C: + // Trigger refresh in background + go func() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := configProvider.Refresh(ctx); err != nil { + log.Printf("Error refreshing configuration: %s", err) + } + }() + case <-done: + fmt.Println("\nExiting...") + return + } + } +} + +// loadAzureAppConfiguration loads the configuration from Azure App Configuration +func loadAzureAppConfiguration() (*azureappconfiguration.AzureAppConfiguration, error) { + // Get connection string from environment variable + connectionString := os.Getenv("AZURE_APPCONFIG_CONNECTION_STRING") + + // Options setup + options := &azureappconfiguration.Options{ + Selectors: []azureappconfiguration.Selector{ + { + KeyFilter: "Config.*", + }, + }, + // Remove the prefix when mapping to struct fields + TrimKeyPrefixes: []string{"Config."}, + // Enable refresh every 10 seconds + RefreshOptions: azureappconfiguration.KeyValueRefreshOptions{ + Enabled: true, + Interval: 10 * time.Second, + }, + } + + authOptions := azureappconfiguration.AuthenticationOptions{ + ConnectionString: connectionString, + } + + // Create configuration provider with timeout + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + return azureappconfiguration.Load(ctx, authOptions, options) +} + +// displayConfig prints the current configuration values +func displayConfig(config Config) { + fmt.Println("\nCurrent Configuration Values:") + fmt.Println("--------------------") + fmt.Printf("Font Color: %s\n", config.Font.Color) + fmt.Printf("Font Size: %d\n", config.Font.Size) + fmt.Printf("Message: %s\n", config.Message) + fmt.Println("--------------------") +} diff --git a/examples/Go/WebApp/README.md b/examples/Go/WebApp/README.md new file mode 100644 index 00000000..de2ae79e --- /dev/null +++ b/examples/Go/WebApp/README.md @@ -0,0 +1,51 @@ +# Gin Feature Flags Web App + +This is a Gin web application using a feature flag in Azure App Configuration to dynamically control the availability of a new web page without restarting or redeploying it. + +## Prerequisites + +- An Azure account with an active subscription +- An Azure App Configuration store +- A feature flag named "Beta" in your App Configuration store +- Go 1.23 or later + +## Running the Example + +1. **Create a feature flag in Azure App Configuration:** + + Add a feature flag called *Beta* to the App Configuration store and leave **Label** and **Description** with their default values. For more information about how to add feature flags to a store using the Azure portal or the CLI, go to [Create a feature flag](https://learn.microsoft.com/azure/azure-app-configuration/manage-feature-flags?tabs=azure-portal#create-a-feature-flag). + +2. **Set environment variable:** + + **Windows PowerShell:** + ```powershell + $env:AZURE_APPCONFIG_CONNECTION_STRING = "your-connection-string" + ``` + + **Windows Command Prompt:** + ```cmd + setx AZURE_APPCONFIG_CONNECTION_STRING "your-connection-string" + ``` + + **Linux/macOS:** + ```bash + export AZURE_APPCONFIG_CONNECTION_STRING="your-connection-string" + ``` + +## Running the Application + +```bash +go run main.go +``` + +Open http://localhost:8080 in your browser. + +## Testing the Feature Flag + +1. **Start the application** - Beta menu item should be hidden (feature disabled) +2. **Go to Azure portal** → App Configuration → Feature manager +3. **Enable the "Beta" feature flag** +4. **Wait up to 30 seconds** and refresh the page +5. **Observe the Beta menu item** appears in navigation +6. **Click the Beta menu** to access the Beta page +7. **Disable the flag again** and test that `/beta` returns 404 \ No newline at end of file diff --git a/examples/Go/WebApp/main.go b/examples/Go/WebApp/main.go new file mode 100644 index 00000000..60adf125 --- /dev/null +++ b/examples/Go/WebApp/main.go @@ -0,0 +1,157 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + + "github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration" + "github.com/gin-gonic/gin" + "github.com/microsoft/Featuremanagement-Go/featuremanagement" + "github.com/microsoft/Featuremanagement-Go/featuremanagement/providers/azappconfig" +) + +type WebApp struct { + featureManager *featuremanagement.FeatureManager + appConfig *azureappconfiguration.AzureAppConfiguration +} + +func main() { + ctx := context.Background() + + // Load config from Azure App Configuration + appConfig, err := loadAzureAppConfiguration(ctx) + if err != nil { + log.Fatalf("Error loading Azure App Configuration: %v", err) + } + + // Create feature flag provider + featureFlagProvider, err := azappconfig.NewFeatureFlagProvider(appConfig) + if err != nil { + log.Fatalf("Error creating feature flag provider: %v", err) + } + + // Create feature manager + featureManager, err := featuremanagement.NewFeatureManager(featureFlagProvider, nil) + if err != nil { + log.Fatalf("Error creating feature manager: %v", err) + } + + // Create web app + app := &WebApp{ + featureManager: featureManager, + appConfig: appConfig, + } + + // Setup Gin with default middleware (Logger and Recovery) + r := gin.Default() + + // Setup routes + app.setupRoutes(r) + + // Start server + fmt.Println("Starting server on http://localhost:8080") + fmt.Println("Open http://localhost:8080 in your browser") + fmt.Println("Toggle the 'Beta' feature flag in Azure portal to see changes") + fmt.Println() + + if err := r.Run(":8080"); err != nil { + log.Fatalf("Failed to start server: %v", err) + } +} + +func loadAzureAppConfiguration(ctx context.Context) (*azureappconfiguration.AzureAppConfiguration, error) { + connectionString := os.Getenv("AZURE_APPCONFIG_CONNECTION_STRING") + if connectionString == "" { + return nil, fmt.Errorf("AZURE_APPCONFIG_CONNECTION_STRING environment variable is not set") + } + + authOptions := azureappconfiguration.AuthenticationOptions{ + ConnectionString: connectionString, + } + + options := &azureappconfiguration.Options{ + FeatureFlagOptions: azureappconfiguration.FeatureFlagOptions{ + Enabled: true, + Selectors: []azureappconfiguration.Selector{ + { + KeyFilter: "*", + LabelFilter: "", + }, + }, + RefreshOptions: azureappconfiguration.RefreshOptions{ + Enabled: true, + }, + }, + } + + appConfig, err := azureappconfiguration.Load(ctx, authOptions, options) + if err != nil { + return nil, fmt.Errorf("failed to load configuration: %w", err) + } + + return appConfig, nil +} + +func (app *WebApp) featureMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Refresh configuration to get latest feature flags + ctx := context.Background() + if err := app.appConfig.Refresh(ctx); err != nil { + log.Printf("Error refreshing configuration: %v", err) + } + + // Check if Beta feature is enabled + betaEnabled, err := app.featureManager.IsEnabled("Beta") + if err != nil { + log.Printf("Error checking Beta feature: %v", err) + betaEnabled = false + } + + // Store feature flag status for use in templates + c.Set("betaEnabled", betaEnabled) + c.Next() + } +} + +func (app *WebApp) setupRoutes(r *gin.Engine) { + // Apply feature middleware to all routes + r.Use(app.featureMiddleware()) + + // Load HTML templates + r.LoadHTMLGlob("templates/*.html") + + // Routes + r.GET("/", app.homeHandler) + r.GET("/beta", app.betaHandler) +} + +// Home page handler +func (app *WebApp) homeHandler(c *gin.Context) { + betaEnabled := c.GetBool("betaEnabled") + + c.HTML(http.StatusOK, "index.html", gin.H{ + "title": "Feature Management Demo", + "betaEnabled": betaEnabled, + }) +} + +// Beta page handler +func (app *WebApp) betaHandler(c *gin.Context) { + betaEnabled := c.GetBool("betaEnabled") + + // Feature gate logic - return 404 if feature is not enabled + if !betaEnabled { + c.HTML(http.StatusNotFound, "404.html", gin.H{ + "title": "Page Not Found", + "message": "The page you are looking for does not exist or is not available.", + }) + return + } + + c.HTML(http.StatusOK, "beta.html", gin.H{ + "title": "Beta Page", + }) +} diff --git a/examples/Go/WebApp/templates/404.html b/examples/Go/WebApp/templates/404.html new file mode 100644 index 00000000..681c4d40 --- /dev/null +++ b/examples/Go/WebApp/templates/404.html @@ -0,0 +1,37 @@ + + + + + + {{.title}} + + + +
+
+
+
+

404

+

Page Not Found

+

{{.message}}

+ + Return to Home +
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/Go/WebApp/templates/beta.html b/examples/Go/WebApp/templates/beta.html new file mode 100644 index 00000000..b0e06a3f --- /dev/null +++ b/examples/Go/WebApp/templates/beta.html @@ -0,0 +1,34 @@ + + + + + + {{.title}} + + + +
+
+
+

This is the beta website.

+
+
+
+ + + + \ No newline at end of file diff --git a/examples/Go/WebApp/templates/index.html b/examples/Go/WebApp/templates/index.html new file mode 100644 index 00000000..05878360 --- /dev/null +++ b/examples/Go/WebApp/templates/index.html @@ -0,0 +1,39 @@ + + + + + + {{.title}} + + + +
+
+
+

Hello from Azure App Configuration

+
+
+ + + + \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index d2c4bb30..aa2862a5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -70,4 +70,19 @@ This example shows how to use the Azure App Configuration Python Provider in you ### [python-flask-webapp-sample](./Python/python-flask-webapp-sample/) -This example shows how to use the Azure App Configuration Python Provider in your python Flask app. \ No newline at end of file +This example shows how to use the Azure App Configuration Python Provider in your python Flask app. + +## Go Samples + +### [AI Chat App](./Go/ChatApp/) + +This example showcases a Go console application that retrieves chat responses from Azure OpenAI. It demonstrates how to configure chat completion using AI Configuration from Azure App Configuration, enabling rapid prompt iteration and frequent tuning of model parameters—without requiring application restarts, rebuilds, or redeployments. + +### [Console app](./Go/ConsoleApp/) + +This example demonstrates how to enable dynamic configuration from App Configuration in a Go console application. + +### [Gin web app](./Go/WebApp/) + +This example demonstrates how to enable dynamic configuration and use feature flags from App Configuration in a web application built with the Gin framework. +