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
}
}