From 3494ae8c88af5ea7d55bc6a3b845b3817a417c35 Mon Sep 17 00:00:00 2001 From: saiflayouni Date: Mon, 29 Jun 2026 21:19:10 +0100 Subject: [PATCH] feat: support JSON file input for API Hub deployment create/update (#680, #698) - Add `CreateDeploymentFromFile` and `UpdateDeploymentFromFile` to the hub client to POST/PATCH raw JSON, which allows any attribute supported by the API - Add `--file`/`-f` flag to `apigeecli apihub deployments create` and `update` - When `--file` is provided the individual field flags are ignored, enabling users to pass custom attributes that have no dedicated flags --- internal/client/hub/hub.go | 21 ++++++++++++++ internal/cmd/apihub/deployments/create.go | 34 +++++++++++++++++++---- internal/cmd/apihub/deployments/update.go | 15 ++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/internal/client/hub/hub.go b/internal/client/hub/hub.go index c45ac24c..51aaa1a4 100644 --- a/internal/client/hub/hub.go +++ b/internal/client/hub/hub.go @@ -677,6 +677,27 @@ func CreateDeployment(deploymentID string, displayName string, description strin return respBody, err } +// CreateDeploymentFromFile creates a deployment using raw JSON from a file. +func CreateDeploymentFromFile(deploymentID string, contents []byte) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.GetApigeeRegistryURL()) + u.Path = path.Join(u.Path, "deployments") + if deploymentID != "" { + q := u.Query() + q.Set("deploymentId", deploymentID) + u.RawQuery = q.Encode() + } + respBody, err = apiclient.HttpClient(u.String(), string(contents)) + return respBody, err +} + +// UpdateDeploymentFromFile updates a deployment using raw JSON from a file. +func UpdateDeploymentFromFile(deploymentID string, contents []byte) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.GetApigeeRegistryURL()) + u.Path = path.Join(u.Path, "deployments", deploymentID) + respBody, err = apiclient.HttpClient(u.String(), string(contents), "PATCH") + return respBody, err +} + func GetDeployment(deploymentID string) (respBody []byte, err error) { u, _ := url.Parse(apiclient.GetApigeeRegistryURL()) u.Path = path.Join(u.Path, "deployments", deploymentID) diff --git a/internal/cmd/apihub/deployments/create.go b/internal/cmd/apihub/deployments/create.go index 1a8fd0b3..c5b396a1 100644 --- a/internal/cmd/apihub/deployments/create.go +++ b/internal/cmd/apihub/deployments/create.go @@ -15,8 +15,10 @@ package deployments import ( + "fmt" "internal/apiclient" "internal/client/hub" + "os" "github.com/spf13/cobra" ) @@ -32,6 +34,30 @@ var CrtCmd = &cobra.Command{ }, RunE: func(cmd *cobra.Command, args []string) (err error) { cmd.SilenceUsage = true + + if filePath != "" { + var contents []byte + if contents, err = os.ReadFile(filePath); err != nil { + return err + } + _, err = hub.CreateDeploymentFromFile(deploymentID, contents) + return + } + + // Validate required flags when not using a file + if displayName == "" { + return fmt.Errorf("required flag \"display-name\" not set") + } + if resourceURI == "" { + return fmt.Errorf("required flag \"resource-uri\" not set") + } + if len(endpoints) == 0 { + return fmt.Errorf("required flag \"endpoints\" not set") + } + if string(d) == "" { + return fmt.Errorf("required flag \"dep-type\" not set") + } + _, err = hub.CreateDeployment(deploymentID, displayName, description, deploymentName, externalURI, resourceURI, endpoints, d, e, s) return @@ -45,6 +71,7 @@ var ( d hub.DeploymentType e hub.EnvironmentType s hub.SloType + filePath string ) func init() { @@ -65,9 +92,6 @@ func init() { CrtCmd.Flags().Var(&d, "dep-type", "The type of deployment") CrtCmd.Flags().Var(&e, "env-type", "The environment mapping to this deployment") CrtCmd.Flags().Var(&s, "slo-type", "The SLO for this deployment") - - _ = CrtCmd.MarkFlagRequired("display-name") - _ = CrtCmd.MarkFlagRequired("resource-uri") - _ = CrtCmd.MarkFlagRequired("endpoints") - _ = CrtCmd.MarkFlagRequired("dep-type") + CrtCmd.Flags().StringVarP(&filePath, "file", "f", + "", "Path to a JSON file containing the deployment definition (including custom attributes)") } diff --git a/internal/cmd/apihub/deployments/update.go b/internal/cmd/apihub/deployments/update.go index 9791f7c2..fdc21252 100644 --- a/internal/cmd/apihub/deployments/update.go +++ b/internal/cmd/apihub/deployments/update.go @@ -17,6 +17,7 @@ package deployments import ( "internal/apiclient" "internal/client/hub" + "os" "github.com/spf13/cobra" ) @@ -32,11 +33,23 @@ var UpdateCmd = &cobra.Command{ }, RunE: func(cmd *cobra.Command, args []string) (err error) { cmd.SilenceUsage = true + + if updateFilePath != "" { + var contents []byte + if contents, err = os.ReadFile(updateFilePath); err != nil { + return err + } + _, err = hub.UpdateDeploymentFromFile(deploymentID, contents) + return + } + _, err = hub.UpdateDeployment(deploymentID, displayName, description, externalURI, resourceURI, endpoints, d, e, s) return }, } +var updateFilePath string + func init() { UpdateCmd.Flags().StringVarP(&deploymentID, "id", "i", "", "Deployment ID") @@ -53,4 +66,6 @@ func init() { UpdateCmd.Flags().Var(&d, "dep-type", "The type of deployment") UpdateCmd.Flags().Var(&e, "env-type", "The environment mapping to this deployment") UpdateCmd.Flags().Var(&s, "slo-type", "The SLO for this deployment") + UpdateCmd.Flags().StringVarP(&updateFilePath, "file", "f", + "", "Path to a JSON file containing the deployment definition to update (supports custom attributes)") }