commands/cluster/agent/check_manifest_usage/check_manifest_usage.go (154 lines of code) (raw):

package check_manifest_usage import ( "fmt" "github.com/spf13/cobra" gitlab "gitlab.com/gitlab-org/api/client-go" "gitlab.com/gitlab-org/cli/api" "gitlab.com/gitlab-org/cli/commands/cmdutils" "gitlab.com/gitlab-org/cli/pkg/iostreams" "gitlab.com/gitlab-org/cli/pkg/text" "gopkg.in/yaml.v3" ) type Options struct { Group string Recursive bool ProjectPerPage int ProjectPage int AgentPerPage int AgentPage int HTTPClient func() (*gitlab.Client, error) IO *iostreams.IOStreams } type AgentConfig struct { GitOps struct { ManifestProjects []struct{} `yaml:"manifest_projects,omitempty"` } `yaml:"gitops,omitempty"` } func NewCmdCheckManifestUsage(f *cmdutils.Factory) *cobra.Command { opts := &Options{ IO: f.IO, } checkManifestUsageCmd := &cobra.Command{ Use: "check_manifest_usage [flags]", Short: `Check agent configuration files for built-in GitOps manifests usage. (EXPERIMENTAL.)`, Long: `Checks the descendants of a group for registered agents with configuration files that rely on the deprecated GitOps manifests settings. The output can be piped to a tab-separated value (TSV) file. ` + text.ExperimentalString, RunE: func(cmd *cobra.Command, args []string) error { opts.HTTPClient = f.HttpClient return checkManifestUsageInGroup(opts) }, } // Boolean to authorize experimental features checkManifestUsageCmd.Flags().StringVarP(&opts.Group, "group", "g", "", "Group ID to check.") cobra.CheckErr(checkManifestUsageCmd.MarkFlagRequired("group")) checkManifestUsageCmd.Flags().IntVarP(&opts.ProjectPage, "page", "p", 1, "Page number for projects.") checkManifestUsageCmd.Flags().IntVarP(&opts.ProjectPerPage, "per-page", "P", 30, "Number of projects to list per page.") checkManifestUsageCmd.Flags().IntVarP(&opts.AgentPage, "agent-page", "a", 1, "Page number for projects.") checkManifestUsageCmd.Flags().IntVarP(&opts.AgentPerPage, "agent-per-page", "A", 30, "Number of projects to list per page.") checkManifestUsageCmd.Flags().BoolVarP(&opts.Recursive, "recursive", "r", false, "Recursively check subgroups.") return checkManifestUsageCmd } func checkManifestUsageInGroup(opts *Options) error { apiClient, err := opts.HTTPClient() if err != nil { return err } // new line err = checkGroup(apiClient, opts.Group, opts) if err != nil { return err } if opts.Recursive { var groups []*gitlab.Group groups, _, err = listAllGroupsForGroup(apiClient, opts.Group) if err != nil { return err } for _, group := range groups { err = checkGroup(apiClient, group.FullPath, opts) if err != nil { return err } } } return nil } func checkGroup(apiClient *gitlab.Client, group string, opts *Options) error { var projects []*gitlab.Project var resp *gitlab.Response projects, resp, err := listAllProjectsForGroup(apiClient, group, *opts) if err != nil { return err } color := opts.IO.Color() opts.IO.Log(color.ProgressIcon(), fmt.Sprintf("Checking %d of %d projects (Page %d of %d)\n", len(projects), resp.TotalItems, resp.CurrentPage, resp.TotalPages)) for _, prj := range projects { err = checkManifestUsageInProject(apiClient, opts, prj) if err != nil { return err } } opts.IO.Log() return nil } func listAllGroupsForGroup(apiClient *gitlab.Client, group string) ([]*gitlab.Group, *gitlab.Response, error) { l := &gitlab.ListDescendantGroupsOptions{} return apiClient.Groups.ListDescendantGroups(group, l) } func listAllProjectsForGroup(apiClient *gitlab.Client, group string, opts Options) ([]*gitlab.Project, *gitlab.Response, error) { l := &gitlab.ListGroupProjectsOptions{ ListOptions: gitlab.ListOptions{ PerPage: opts.ProjectPerPage, Page: opts.ProjectPage, }, } return apiClient.Groups.ListGroupProjects(group, l) } func checkManifestUsageInProject(apiClient *gitlab.Client, opts *Options, project *gitlab.Project) error { color := opts.IO.Color() opts.IO.StartSpinner(fmt.Sprintf("Checking project %s for agents.\n", project.PathWithNamespace)) defer opts.IO.StopSpinner("") agents, err := api.ListAgents(apiClient, project.ID, &gitlab.ListAgentsOptions{ Page: opts.AgentPage, PerPage: opts.AgentPerPage, }) if err != nil { return err } opts.IO.Log(color.ProgressIcon(), fmt.Sprintf("Found %d agents.\n", len(agents))) for _, agent := range agents { found, err := agentUsesManifestProjects(apiClient, opts, agent) if err != nil { opts.IO.Log(color.RedCheck(), "An error happened.", err) continue } if found { opts.IO.LogInfo(fmt.Sprintf("%s\t%s\t%d", agent.ConfigProject.PathWithNamespace, agent.Name, 1)) } else { opts.IO.LogInfo(fmt.Sprintf("%s\t%s\t%d", agent.ConfigProject.PathWithNamespace, agent.Name, 0)) } } return nil } func agentUsesManifestProjects(apiClient *gitlab.Client, opts *Options, agent *gitlab.Agent) (bool, error) { color := opts.IO.Color() opts.IO.StartSpinner(fmt.Sprintf("Checking manifests of agent %s.\n", agent.Name)) defer opts.IO.StopSpinner("") // GetRawFile file, _, err := apiClient.RepositoryFiles.GetRawFile(agent.ConfigProject.ID, ".gitlab/agents/"+agent.Name+"/config.yaml", &gitlab.GetRawFileOptions{}) if err != nil { opts.IO.Log(color.WarnIcon(), fmt.Sprintf("Agent %s uses the default configuration.", agent.Name)) return false, nil } // Check that gitops.manifest_projects json path does not exist in configFile var configData AgentConfig err = yaml.Unmarshal(file, &configData) if err != nil { opts.IO.Log("Unmarshal error", fmt.Sprintf("%s\n", string(file))) return false, err } if len(configData.GitOps.ManifestProjects) == 0 { opts.IO.Log(color.GreenCheck(), fmt.Sprintf("Agent %s does not have manifest projects configured.", agent.Name)) return false, nil } else { opts.IO.Log(color.FailedIcon(), fmt.Sprintf("Agent %s has %d manifest projects configured.", agent.Name, len(configData.GitOps.ManifestProjects))) return true, nil } }