mirror of
https://github.com/hanzoai/deploy.git
synced 2026-10-07 08:44:50 +00:00
go.mod still declared `module github.com/hanzoai/deploy` after the repo was renamed. A Go module path is not cosmetic — it is the resolvable location. So `go get github.com/hanzoai/cd` could not work, and anything importing this module had to spell a name that no longer exists at that address. The build log of v3.6.0 is where it surfaced: the compiler was still emitting github.com/hanzoai/deploy/cmd/cd/commands. 3541 import paths across 690 .go, 19 .proto, 4 .yaml, 3 .sh, 2 .mod, Procfile and Makefile. Plus the 62 ghcr.io/hanzoai/deploy image references, moved to ghcr.io/hanzoai/cd to match hanzo.yml's build target — a distinct string from the module path, replaced separately so neither could clobber the other. go mod tidy clean, CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./... clean. Co-authored-by: Hanzo Dev <[email protected]>
263 lines
9.3 KiB
Go
263 lines
9.3 KiB
Go
package commands
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"text/tabwriter"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
"google.golang.org/grpc/codes"
|
|
"sigs.k8s.io/yaml"
|
|
|
|
"github.com/hanzoai/cd/cmd/cd/commands/headless"
|
|
"github.com/hanzoai/cd/cmd/util"
|
|
cdclient "github.com/hanzoai/cd/pkg/apiclient"
|
|
applicationpkg "github.com/hanzoai/cd/pkg/apiclient/application"
|
|
"github.com/hanzoai/cd/pkg/apis/application"
|
|
"github.com/hanzoai/cd/pkg/apis/application/v1alpha1"
|
|
"github.com/hanzoai/cd/util/cd"
|
|
"github.com/hanzoai/cd/util/errors"
|
|
"github.com/hanzoai/cd/util/grpc"
|
|
utilio "github.com/hanzoai/cd/util/io"
|
|
"github.com/hanzoai/cd/util/templates"
|
|
)
|
|
|
|
type DisplayedAction struct {
|
|
Group string
|
|
Kind string
|
|
Name string
|
|
Action string
|
|
Disabled bool
|
|
}
|
|
|
|
var appActionExample = templates.Examples(`
|
|
# List all the available actions for an application
|
|
cd app actions list APPNAME
|
|
|
|
# Run an available action for an application
|
|
cd app actions run APPNAME ACTION --kind KIND [--resource-name RESOURCE] [--namespace NAMESPACE] [--group GROUP]
|
|
`)
|
|
|
|
// NewApplicationResourceActionsCommand returns a new instance of an `cd app actions` command
|
|
func NewApplicationResourceActionsCommand(clientOpts *cdclient.ClientOptions) *cobra.Command {
|
|
command := &cobra.Command{
|
|
Use: "actions",
|
|
Short: "Manage Resource actions",
|
|
Example: appActionExample,
|
|
Run: func(c *cobra.Command, args []string) {
|
|
c.HelpFunc()(c, args)
|
|
os.Exit(1)
|
|
},
|
|
}
|
|
command.AddCommand(NewApplicationResourceActionsListCommand(clientOpts))
|
|
command.AddCommand(NewApplicationResourceActionsRunCommand(clientOpts))
|
|
return command
|
|
}
|
|
|
|
// NewApplicationResourceActionsListCommand returns a new instance of an `cd app actions list` command
|
|
func NewApplicationResourceActionsListCommand(clientOpts *cdclient.ClientOptions) *cobra.Command {
|
|
var namespace string
|
|
var kind string
|
|
var group string
|
|
var resourceName string
|
|
var output string
|
|
var appNamespace string
|
|
command := &cobra.Command{
|
|
Use: "list APPNAME",
|
|
Short: "Lists available actions on a resource",
|
|
Example: templates.Examples(`
|
|
# List all the available actions for an application
|
|
cd app actions list APPNAME
|
|
`),
|
|
}
|
|
command.Run = func(c *cobra.Command, args []string) {
|
|
ctx := c.Context()
|
|
|
|
if len(args) != 1 {
|
|
c.HelpFunc()(c, args)
|
|
os.Exit(1)
|
|
}
|
|
appName, appNs := cd.ParseFromQualifiedName(args[0], appNamespace)
|
|
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
|
|
defer utilio.Close(conn)
|
|
resources, err := getActionableResourcesForApplication(ctx, appIf, &appNs, &appName)
|
|
errors.CheckError(err)
|
|
filteredObjects, err := util.FilterResources(command.Flags().Changed("group"), resources, group, kind, namespace, resourceName, true)
|
|
errors.CheckError(err)
|
|
var availableActions []DisplayedAction
|
|
for i := range filteredObjects {
|
|
obj := filteredObjects[i]
|
|
gvk := obj.GroupVersionKind()
|
|
availActionsForResource, err := appIf.ListResourceActions(ctx, &applicationpkg.ApplicationResourceRequest{
|
|
Name: &appName,
|
|
AppNamespace: &appNs,
|
|
Namespace: new(obj.GetNamespace()),
|
|
ResourceName: new(obj.GetName()),
|
|
Group: new(gvk.Group),
|
|
Kind: new(gvk.Kind),
|
|
Version: new(gvk.Version),
|
|
})
|
|
errors.CheckError(err)
|
|
for _, action := range availActionsForResource.Actions {
|
|
displayAction := DisplayedAction{
|
|
Group: gvk.Group,
|
|
Kind: gvk.Kind,
|
|
Name: obj.GetName(),
|
|
Action: action.Name,
|
|
Disabled: action.Disabled,
|
|
}
|
|
availableActions = append(availableActions, displayAction)
|
|
}
|
|
}
|
|
|
|
switch output {
|
|
case "yaml":
|
|
yamlBytes, err := yaml.Marshal(availableActions)
|
|
errors.CheckError(err)
|
|
fmt.Println(string(yamlBytes))
|
|
case "json":
|
|
jsonBytes, err := json.MarshalIndent(availableActions, "", " ")
|
|
errors.CheckError(err)
|
|
fmt.Println(string(jsonBytes))
|
|
case "":
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
fmt.Fprint(w, "GROUP\tKIND\tNAME\tACTION\tDISABLED\n")
|
|
for _, action := range availableActions {
|
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", action.Group, action.Kind, action.Name, action.Action, strconv.FormatBool(action.Disabled))
|
|
}
|
|
_ = w.Flush()
|
|
}
|
|
}
|
|
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Namespace of the application")
|
|
command.Flags().StringVar(&resourceName, "resource-name", "", "Name of resource")
|
|
command.Flags().StringVar(&kind, "kind", "", "Kind")
|
|
command.Flags().StringVar(&group, "group", "", "Group")
|
|
command.Flags().StringVar(&namespace, "namespace", "", "Namespace")
|
|
command.Flags().StringVarP(&output, "out", "o", "", "Output format. One of: yaml, json")
|
|
|
|
return command
|
|
}
|
|
|
|
// NewApplicationResourceActionsRunCommand returns a new instance of an `cd app actions run` command
|
|
func NewApplicationResourceActionsRunCommand(clientOpts *cdclient.ClientOptions) *cobra.Command {
|
|
var namespace string
|
|
var appNamespace string
|
|
var resourceName string
|
|
var kind string
|
|
var group string
|
|
var all bool
|
|
command := &cobra.Command{
|
|
Use: "run APPNAME ACTION",
|
|
Short: "Runs an available action on resource(s) matching the specified filters.",
|
|
Long: "All filters except --kind are optional. Use --all to run the action on all matching resources if more than one resource matches the filters. Actions may only be run on resources that are represented in git and cannot be run on child resources.",
|
|
Example: templates.Examples(`
|
|
# Run an available action for an application
|
|
cd app actions run APPNAME ACTION --kind KIND [--resource-name RESOURCE] [--namespace NAMESPACE] [--group GROUP]
|
|
`),
|
|
}
|
|
|
|
command.Flags().StringVar(&resourceName, "resource-name", "", "Name of resource on which the action should be run")
|
|
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Namespace of the application")
|
|
command.Flags().StringVar(&namespace, "namespace", "", "Namespace of the resource on which the action should be run")
|
|
command.Flags().StringVar(&kind, "kind", "", "Kind of the resource on which the action should be run")
|
|
command.Flags().StringVar(&group, "group", "", "Group of the resource on which the action should be run")
|
|
errors.CheckError(command.MarkFlagRequired("kind"))
|
|
command.Flags().BoolVar(&all, "all", false, "Indicates whether to run the action on multiple matching resources")
|
|
|
|
command.Run = func(c *cobra.Command, args []string) {
|
|
ctx := c.Context()
|
|
|
|
if len(args) != 2 {
|
|
c.HelpFunc()(c, args)
|
|
os.Exit(1)
|
|
}
|
|
appName, appNs := cd.ParseFromQualifiedName(args[0], appNamespace)
|
|
actionName := args[1]
|
|
|
|
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
|
|
defer utilio.Close(conn)
|
|
resources, err := getActionableResourcesForApplication(ctx, appIf, &appNs, &appName)
|
|
errors.CheckError(err)
|
|
filteredObjects, err := util.FilterResources(command.Flags().Changed("group"), resources, group, kind, namespace, resourceName, all)
|
|
errors.CheckError(err)
|
|
resGroup := filteredObjects[0].GroupVersionKind().Group
|
|
for i := range filteredObjects[1:] {
|
|
if filteredObjects[i].GroupVersionKind().Group != resGroup {
|
|
log.Fatal("Ambiguous resource group. Use flag --group to specify resource group explicitly.")
|
|
}
|
|
}
|
|
|
|
for i := range filteredObjects {
|
|
obj := filteredObjects[i]
|
|
gvk := obj.GroupVersionKind()
|
|
objResourceName := obj.GetName()
|
|
_, err := appIf.RunResourceActionV2(ctx, &applicationpkg.ResourceActionRunRequestV2{
|
|
Name: &appName,
|
|
AppNamespace: &appNs,
|
|
Namespace: new(obj.GetNamespace()),
|
|
ResourceName: new(objResourceName),
|
|
Group: new(gvk.Group),
|
|
Kind: new(gvk.Kind),
|
|
Version: new(gvk.GroupVersion().Version),
|
|
Action: new(actionName),
|
|
// TODO: add support for parameters
|
|
})
|
|
if err == nil {
|
|
continue
|
|
}
|
|
if grpc.UnwrapGRPCStatus(err).Code() != codes.Unimplemented {
|
|
errors.CheckError(err)
|
|
}
|
|
fmt.Println("RunResourceActionV2 is not supported by the server, falling back to RunResourceAction.")
|
|
//nolint:staticcheck // RunResourceAction is deprecated, but we still need to support it for backward compatibility.
|
|
_, err = appIf.RunResourceAction(ctx, &applicationpkg.ResourceActionRunRequest{
|
|
Name: &appName,
|
|
AppNamespace: &appNs,
|
|
Namespace: new(obj.GetNamespace()),
|
|
ResourceName: new(objResourceName),
|
|
Group: new(gvk.Group),
|
|
Kind: new(gvk.Kind),
|
|
Version: new(gvk.GroupVersion().Version),
|
|
Action: new(actionName),
|
|
})
|
|
errors.CheckError(err)
|
|
}
|
|
}
|
|
return command
|
|
}
|
|
|
|
func getActionableResourcesForApplication(ctx context.Context, appIf applicationpkg.ApplicationServiceClient, appNs *string, appName *string) ([]*v1alpha1.ResourceDiff, error) {
|
|
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{
|
|
ApplicationName: appName,
|
|
AppNamespace: appNs,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{
|
|
Name: appName,
|
|
AppNamespace: appNs,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
app.Kind = application.ApplicationKind
|
|
app.APIVersion = "apps.hanzo.ai/v1alpha1"
|
|
appManifest, err := json.Marshal(app)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
appGVK := app.GroupVersionKind()
|
|
return append(resources.Items, &v1alpha1.ResourceDiff{
|
|
Group: appGVK.Group,
|
|
Kind: appGVK.Kind,
|
|
Namespace: app.Namespace,
|
|
Name: *appName,
|
|
LiveState: string(appManifest),
|
|
}), nil
|
|
}
|