-
Notifications
You must be signed in to change notification settings - Fork 26
feat(container): add registry list command and public link on container create #614
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "io" | ||
|
|
||
| "github.com/pkg/errors" | ||
| "github.com/pterm/pterm" | ||
| "github.com/qovery/qovery-cli/utils" | ||
| "github.com/qovery/qovery-client-go" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var containerRegistryId string | ||
| var containerPort int32 | ||
| var containerCpu int32 | ||
| var containerMemory int32 | ||
| var containerMinRunningInstances int32 | ||
| var containerMaxRunningInstances int32 | ||
|
|
||
| var containerCreateCmd = &cobra.Command{ | ||
| Use: "create", | ||
| Short: "Create a container service", | ||
| Run: func(cmd *cobra.Command, args []string) { | ||
| utils.Capture(cmd) | ||
|
|
||
| tokenType, token, err := utils.GetAccessToken() | ||
| utils.CheckError(err) | ||
|
|
||
| client := utils.GetQoveryClient(tokenType, token) | ||
| _, _, envId, err := getOrganizationProjectEnvironmentContextResourcesIds(client) | ||
| utils.CheckError(err) | ||
|
|
||
| var ports []qovery.ServicePortRequestPortsInner | ||
| if containerPort > 0 { | ||
| portName := fmt.Sprintf("p%d", containerPort) | ||
| protocol := qovery.PORTPROTOCOLENUM_HTTP | ||
| ports = append(ports, qovery.ServicePortRequestPortsInner{ | ||
| Name: &portName, | ||
| InternalPort: containerPort, | ||
| ExternalPort: utils.Int32(443), | ||
| PubliclyAccessible: true, | ||
| IsDefault: utils.Bool(true), | ||
| Protocol: &protocol, | ||
| }) | ||
| } | ||
|
|
||
| req := qovery.ContainerRequest{ | ||
| Name: containerName, | ||
| RegistryId: containerRegistryId, | ||
| ImageName: containerImageName, | ||
| Tag: containerTag, | ||
| Ports: ports, | ||
| Cpu: utils.Int32(containerCpu), | ||
| Memory: utils.Int32(containerMemory), | ||
| MinRunningInstances: utils.Int32(containerMinRunningInstances), | ||
| MaxRunningInstances: utils.Int32(containerMaxRunningInstances), | ||
| Healthchecks: *qovery.NewHealthcheck(), | ||
| } | ||
|
|
||
| created, res, err := client.ContainersAPI.CreateContainer(context.Background(), envId).ContainerRequest(req).Execute() | ||
| if err != nil && res != nil && res.StatusCode != 201 { | ||
| result, _ := io.ReadAll(res.Body) | ||
| utils.PrintlnError(errors.Errorf("status code: %s ; body: %s", res.Status, string(result))) | ||
| } | ||
| utils.CheckError(err) | ||
|
|
||
| var publicLink string | ||
| if len(ports) > 0 { | ||
| links, _, err := client.ContainerMainCallsAPI.ListContainerLinks(context.Background(), created.Id).Execute() | ||
| if err == nil { | ||
| for _, link := range links.GetResults() { | ||
| publicLink = link.Url | ||
| break | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if jsonFlag { | ||
| out := struct { | ||
| Id string `json:"id"` | ||
| Name string `json:"name"` | ||
| PublicLink string `json:"public_link,omitempty"` | ||
| }{Id: created.Id, Name: created.Name, PublicLink: publicLink} | ||
| j, _ := json.Marshal(out) | ||
| utils.Println(string(j)) | ||
| return | ||
| } | ||
|
|
||
| msg := fmt.Sprintf("Container service %s created! (id: %s)", pterm.FgBlue.Sprintf("%s", created.Name), pterm.FgBlue.Sprintf("%s", created.Id)) | ||
| if publicLink != "" { | ||
| msg += fmt.Sprintf(" - Public link: %s", pterm.FgBlue.Sprintf("%s", publicLink)) | ||
| } | ||
| utils.Println(msg) | ||
| }, | ||
| } | ||
|
|
||
| func init() { | ||
| containerCmd.AddCommand(containerCreateCmd) | ||
| containerCreateCmd.Flags().StringVarP(&organizationName, "organization", "", "", "Organization Name") | ||
| containerCreateCmd.Flags().StringVarP(&projectName, "project", "", "", "Project Name") | ||
| containerCreateCmd.Flags().StringVarP(&environmentName, "environment", "", "", "Environment Name") | ||
| containerCreateCmd.Flags().StringVarP(&containerName, "container", "n", "", "Container Name") | ||
| containerCreateCmd.Flags().StringVarP(&containerRegistryId, "registry", "", "", "Container Registry ID") | ||
| containerCreateCmd.Flags().StringVarP(&containerImageName, "image-name", "", "", "Container Image Name") | ||
| containerCreateCmd.Flags().StringVarP(&containerTag, "tag", "t", "", "Container Image Tag") | ||
| containerCreateCmd.Flags().Int32VarP(&containerPort, "port", "p", 0, "Container Port (0 = no port exposed)") | ||
| containerCreateCmd.Flags().Int32VarP(&containerCpu, "cpu", "", 500, "CPU in millicores (e.g. 500 = 0.5 vCPU)") | ||
| containerCreateCmd.Flags().Int32VarP(&containerMemory, "memory", "", 512, "Memory in MB") | ||
| containerCreateCmd.Flags().Int32VarP(&containerMinRunningInstances, "min-instances", "", 1, "Minimum number of running instances") | ||
| containerCreateCmd.Flags().Int32VarP(&containerMaxRunningInstances, "max-instances", "", 1, "Maximum number of running instances") | ||
| containerCreateCmd.Flags().BoolVarP(&jsonFlag, "json", "", false, "JSON output") | ||
|
|
||
| _ = containerCreateCmd.MarkFlagRequired("container") | ||
| _ = containerCreateCmd.MarkFlagRequired("registry") | ||
| _ = containerCreateCmd.MarkFlagRequired("image-name") | ||
| _ = containerCreateCmd.MarkFlagRequired("tag") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "os" | ||
|
|
||
| "github.com/qovery/qovery-cli/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var containerRegistryCmd = &cobra.Command{ | ||
| Use: "registry", | ||
| Short: "Manage container registries", | ||
| Run: func(cmd *cobra.Command, args []string) { | ||
| utils.Capture(cmd) | ||
|
|
||
| if len(args) == 0 { | ||
| _ = cmd.Help() | ||
| os.Exit(0) | ||
| } | ||
| }, | ||
| } | ||
|
|
||
| func init() { | ||
| containerCmd.AddCommand(containerRegistryCmd) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,80 @@ | ||||||||||||||
| package cmd | ||||||||||||||
|
|
||||||||||||||
| import ( | ||||||||||||||
| "context" | ||||||||||||||
| "encoding/json" | ||||||||||||||
|
|
||||||||||||||
| "github.com/qovery/qovery-cli/pkg/usercontext" | ||||||||||||||
| "github.com/qovery/qovery-cli/utils" | ||||||||||||||
| "github.com/qovery/qovery-client-go" | ||||||||||||||
| "github.com/spf13/cobra" | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| var containerRegistryListCmd = &cobra.Command{ | ||||||||||||||
| Use: "list", | ||||||||||||||
| Short: "List container registries", | ||||||||||||||
| Run: func(cmd *cobra.Command, args []string) { | ||||||||||||||
| utils.Capture(cmd) | ||||||||||||||
|
|
||||||||||||||
| tokenType, token, err := utils.GetAccessToken() | ||||||||||||||
| utils.CheckError(err) | ||||||||||||||
|
|
||||||||||||||
| client := utils.GetQoveryClient(tokenType, token) | ||||||||||||||
| organizationId, err := usercontext.GetOrganizationContextResourceId(client, organizationName) | ||||||||||||||
| utils.CheckError(err) | ||||||||||||||
|
|
||||||||||||||
| registries, _, err := client.ContainerRegistriesAPI.ListContainerRegistry(context.Background(), organizationId).Execute() | ||||||||||||||
| utils.CheckError(err) | ||||||||||||||
|
|
||||||||||||||
| if jsonFlag { | ||||||||||||||
| utils.Println(getContainerRegistryJsonOutput(registries.GetResults())) | ||||||||||||||
| return | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| var data [][]string | ||||||||||||||
| for _, registry := range registries.GetResults() { | ||||||||||||||
| url := "" | ||||||||||||||
| if registry.Url != nil { | ||||||||||||||
| url = *registry.Url | ||||||||||||||
| } | ||||||||||||||
| kind := "" | ||||||||||||||
| if registry.Kind != nil { | ||||||||||||||
| kind = string(*registry.Kind) | ||||||||||||||
| } | ||||||||||||||
| data = append(data, []string{registry.Id, *registry.Name, kind, url}) | ||||||||||||||
|
||||||||||||||
| data = append(data, []string{registry.Id, *registry.Name, kind, url}) | |
| name := "" | |
| if registry.Name != nil { | |
| name = *registry.Name | |
| } | |
| data = append(data, []string{registry.Id, name, kind, url}) |
Copilot
AI
Mar 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In getContainerRegistryJsonOutput, registry.Name is stored directly into the map as a *string (line 82), unlike url and kind which are always resolved to plain string values (lines 72-79). When Name is nil, json.Marshal will output "name": null rather than "name": "", making the JSON output inconsistent with the other fields. The name field should be resolved to a plain string (possibly using a nil guard or a getter) just like url and kind are.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
json.Marshalerror is silently discarded with_on this line (j, _ := json.Marshal(out)). Throughout the codebase, every other use ofjson.Marshalin JSON output paths (e.g.,container_list.go:85,cluster_list.go:79,container_registry_list.go:88) assigns the error and handles it withPrintlnError+os.Exit(1). This is inconsistent with the established codebase convention and could silently produce an empty output on a marshalling failure. The error should be checked and handled.