-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Agent license inspect command (#4813)
# New Command: `bacalhau agent license inspect` This PR introduces a new command that allows users to inspect the license information of a Bacalhau orchestrator node without exposing the license itself. The command provides a secure way to verify license status, capabilities, and metadata while maintaining the confidentiality of the underlying license file. ### Features - New command: `bacalhau agent license inspect` - Supports multiple output formats (default, JSON, YAML) - Displays key license information: - Product name - License ID - Customer ID - Expiration date - Version - Capabilities - Custom metadata ### Add License Manager for Node Orchestration Introduce a LicenseManager component that handles license validation for orchestrator nodes. Key features: - Validates node count against licensed limits - Simple API for license verification and capability checks Usage example: ```golang // Initialize license manager (config is bacalhau License config) // IF any issues, other than expiry date validity, appears during initialization, it will fail licenseManager, err := licensing.NewLicenseManager(config) // Get current license claims, it will be nil if no license is configured for the orchestrator licenseClaims := licenseManager.License() // Then these helper functions can be called on the licenseClaims struct licenseClaims.IsExpired() // Returns only a boolean licenseClaims.MaxNumberOfNodes() // Returns only a number ``` ### Security Considerations - Does not expose the raw license file or cryptographic material - Only returns parsed, necessary information for verification - Maintains license confidentiality while providing essential details ### License File Structure The license file, fed to the orchestrator node config, uses a JSON format to support future extensibility and dynamic configuration. The structure is intentionally simple: ```json { "license": "your_license_token_here" } ``` We chose JSON format for the license file because: - It allows for easy addition of future configuration options - Provides a structured way to include additional metadata if needed ### Configuration To add a license to an orchestrator, you need to configure your orchestrator node. Here's a sample configuration example: ```yaml NameProvider: "uuid" API: Port: 1234 Orchestrator: Enabled: true Auth: Token: "your_secret_token_here" License: LocalPath: "/path/to/your/license.json" Labels: label1: label1Value label2: label2Value ``` Key configuration points: - The `Orchestrator.License.LocalPath` field specifies the path to your license file - If no license is configured, the command will return an error message saying that no license was configured - If the license if expired, the inspect command will return the same license details, but will not that it is expired. ### Example Usage ```bash # Default format $ bacalhau agent license inspect # JSON format $ bacalhau agent license inspect --output=json # YAML format $ bacalhau agent license inspect --output=yaml ``` ### Example Output For `bacalhau agent license inspect`: ```bash Product = Bacalhau License ID = 2d58c7c9-ec29-45a5-a5cd-cb8f7fee6678 Customer ID = test-customer-id-123 Valid Until = 2045-07-28 Version = v1 Expired = false Capabilities = max_nodes=1 Metadata = someMetadata=valueOfSomeMetadata ``` For `bacalhau agent license inspect --output=yaml`: ```yaml capabilities: max_nodes: "1" customer_id: test-customer-id-123 exp: 2384889682 iat: 1736889682 iss: https://expanso.io/ jti: 2d58c7c9-ec29-45a5-a5cd-cb8f7fee6678 license_id: 2d58c7c9-ec29-45a5-a5cd-cb8f7fee6678 license_type: standard license_version: v1 metadata: someMetadata: valueOfSomeMetadata product: Bacalhau sub: test-customer-id-123 ``` ### Documentation - Added command documentation with usage examples - Included field descriptions in help text Linear: https://linear.app/expanso/issue/ENG-498/license-path-configuration <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a new CLI command to inspect agent license information. - Introduced a new API endpoint to retrieve agent license details. - Implemented license configuration support for orchestrator nodes. - **Configuration** - Added a new configuration option for specifying local license file path. - Enhanced configuration to support orchestrator settings with license metadata. - **API Enhancements** - Created a new method to retrieve license information via API client. - Updated Swagger documentation to include license-related endpoints. - **Testing** - Added comprehensive integration tests for license inspection scenarios, including expired licenses. - Included test cases for various license configuration states and error handling. - Enhanced tests for validating license output formats and error messages. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Walid Baruni <[email protected]>
- Loading branch information
Showing
31 changed files
with
1,747 additions
and
115 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package license | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"time" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/bacalhau-project/bacalhau/cmd/util" | ||
"github.com/bacalhau-project/bacalhau/cmd/util/flags/cliflags" | ||
"github.com/bacalhau-project/bacalhau/cmd/util/output" | ||
"github.com/bacalhau-project/bacalhau/pkg/lib/collections" | ||
"github.com/bacalhau-project/bacalhau/pkg/publicapi/client/v2" | ||
) | ||
|
||
// AgentLicenseInspectOptions is a struct to support license command | ||
type AgentLicenseInspectOptions struct { | ||
OutputOpts output.NonTabularOutputOptions | ||
} | ||
|
||
// NewAgentLicenseInspectOptions returns initialized Options | ||
func NewAgentLicenseInspectOptions() *AgentLicenseInspectOptions { | ||
return &AgentLicenseInspectOptions{ | ||
OutputOpts: output.NonTabularOutputOptions{}, | ||
} | ||
} | ||
|
||
func NewAgentLicenseInspectCmd() *cobra.Command { | ||
o := NewAgentLicenseInspectOptions() | ||
licenseCmd := &cobra.Command{ | ||
Use: "inspect", | ||
Short: "Get the agent license information", | ||
Args: cobra.NoArgs, | ||
RunE: func(cmd *cobra.Command, _ []string) error { | ||
cfg, err := util.SetupRepoConfig(cmd) | ||
if err != nil { | ||
return fmt.Errorf("failed to setup repo: %w", err) | ||
} | ||
api, err := util.GetAPIClientV2(cmd, cfg) | ||
if err != nil { | ||
return fmt.Errorf("failed to create api client: %w", err) | ||
} | ||
return o.runAgentLicense(cmd, api) | ||
}, | ||
} | ||
licenseCmd.Flags().AddFlagSet(cliflags.OutputNonTabularFormatFlags(&o.OutputOpts)) | ||
return licenseCmd | ||
} | ||
|
||
// Run executes license command | ||
func (o *AgentLicenseInspectOptions) runAgentLicense(cmd *cobra.Command, api client.API) error { | ||
ctx := cmd.Context() | ||
response, err := api.Agent().License(ctx) | ||
if err != nil { | ||
return fmt.Errorf("error retrieving agent license: %w", err) | ||
} | ||
|
||
// For JSON/YAML output | ||
if o.OutputOpts.Format == output.JSONFormat || o.OutputOpts.Format == output.YAMLFormat { | ||
return output.OutputOneNonTabular(cmd, o.OutputOpts, response.LicenseClaims) | ||
} | ||
|
||
// Create header data pairs for key-value output | ||
headerData := []collections.Pair[string, any]{ | ||
{Left: "Product", Right: response.Product}, | ||
{Left: "License ID", Right: response.LicenseID}, | ||
{Left: "Customer ID", Right: response.CustomerID}, | ||
{Left: "Valid Until", Right: response.ExpiresAt.Format(time.DateOnly)}, | ||
{Left: "Version", Right: response.LicenseVersion}, | ||
{Left: "Expired", Right: response.IsExpired()}, | ||
} | ||
|
||
// Always show Capabilities | ||
capabilitiesStr := "{}" | ||
if len(response.Capabilities) > 0 { | ||
var caps []string | ||
for k, v := range response.Capabilities { | ||
caps = append(caps, fmt.Sprintf("%s=%s", k, v)) | ||
} | ||
capabilitiesStr = strings.Join(caps, ", ") | ||
} | ||
headerData = append(headerData, collections.Pair[string, any]{ | ||
Left: "Capabilities", | ||
Right: capabilitiesStr, | ||
}) | ||
|
||
// Always show Metadata | ||
metadataStr := "{}" | ||
if len(response.Metadata) > 0 { | ||
var meta []string | ||
for k, v := range response.Metadata { | ||
meta = append(meta, fmt.Sprintf("%s=%s", k, v)) | ||
} | ||
metadataStr = strings.Join(meta, ", ") | ||
} | ||
headerData = append(headerData, collections.Pair[string, any]{ | ||
Left: "Metadata", | ||
Right: metadataStr, | ||
}) | ||
|
||
output.KeyValue(cmd, headerData) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package license | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func NewAgentLicenseRootCmd() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "license", | ||
Short: "Commands to interact with the orchestrator license", | ||
} | ||
|
||
cmd.AddCommand(NewAgentLicenseInspectCmd()) | ||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.