github.com/argoproj/argo-cd@v1.8.7/cmd/argocd-util/commands/projects.go (about)

     1  package commands
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
    11  	appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
    12  	appclient "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/typed/application/v1alpha1"
    13  	"github.com/argoproj/argo-cd/util/cli"
    14  	"github.com/argoproj/argo-cd/util/errors"
    15  
    16  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    17  	"github.com/spf13/cobra"
    18  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/client-go/tools/clientcmd"
    20  )
    21  
    22  func NewProjectsCommand() *cobra.Command {
    23  	var command = &cobra.Command{
    24  		Use:   "projects",
    25  		Short: "Utility commands operate on ArgoCD Projects",
    26  		Run: func(c *cobra.Command, args []string) {
    27  			c.HelpFunc()(c, args)
    28  		},
    29  	}
    30  
    31  	command.AddCommand(NewUpdatePolicyRuleCommand())
    32  	command.AddCommand(NewProjectAllowListGenCommand())
    33  	return command
    34  }
    35  
    36  func globMatch(pattern string, val string) bool {
    37  	if pattern == "*" {
    38  		return true
    39  	}
    40  	if ok, err := filepath.Match(pattern, val); ok && err == nil {
    41  		return true
    42  	}
    43  	return false
    44  }
    45  
    46  func getModification(modification string, resource string, scope string, permission string) (func(string, string) string, error) {
    47  	switch modification {
    48  	case "set":
    49  		if scope == "" {
    50  			return nil, fmt.Errorf("Flag --group cannot be empty if permission should be set in role")
    51  		}
    52  		if permission == "" {
    53  			return nil, fmt.Errorf("Flag --permission cannot be empty if permission should be set in role")
    54  		}
    55  		return func(proj string, action string) string {
    56  			return fmt.Sprintf("%s, %s, %s/%s, %s", resource, action, proj, scope, permission)
    57  		}, nil
    58  	case "remove":
    59  		return func(proj string, action string) string {
    60  			return ""
    61  		}, nil
    62  	}
    63  	return nil, fmt.Errorf("modification %s is not supported", modification)
    64  }
    65  
    66  func saveProject(updated v1alpha1.AppProject, orig v1alpha1.AppProject, projectsIf appclient.AppProjectInterface, dryRun bool) error {
    67  	fmt.Printf("===== %s ======\n", updated.Name)
    68  	target, err := kube.ToUnstructured(&updated)
    69  	errors.CheckError(err)
    70  	live, err := kube.ToUnstructured(&orig)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	_ = cli.PrintDiff(updated.Name, target, live)
    75  	if !dryRun {
    76  		_, err = projectsIf.Update(context.Background(), &updated, v1.UpdateOptions{})
    77  		if err != nil {
    78  			return err
    79  		}
    80  	}
    81  	return nil
    82  }
    83  
    84  func formatPolicy(proj string, role string, permission string) string {
    85  	return fmt.Sprintf("p, proj:%s:%s, %s", proj, role, permission)
    86  }
    87  
    88  func split(input string, delimiter string) []string {
    89  	parts := strings.Split(input, delimiter)
    90  	for i := range parts {
    91  		parts[i] = strings.TrimSpace(parts[i])
    92  	}
    93  	return parts
    94  }
    95  
    96  func NewUpdatePolicyRuleCommand() *cobra.Command {
    97  	var (
    98  		clientConfig clientcmd.ClientConfig
    99  		resource     string
   100  		scope        string
   101  		rolePattern  string
   102  		permission   string
   103  		dryRun       bool
   104  	)
   105  	var command = &cobra.Command{
   106  		Use:   "update-role-policy PROJECT_GLOB MODIFICATION ACTION",
   107  		Short: "Implement bulk project role update. Useful to back-fill existing project policies or remove obsolete actions.",
   108  		Example: `  # Add policy that allows executing any action (action/*) to roles which name matches to *deployer* in all projects  
   109    argocd-util projects update-role-policy '*' set 'action/*' --role '*deployer*' --resource applications --scope '*' --permission allow
   110  
   111    # Remove policy that which manages running (action/*) from all roles which name matches *deployer* in all projects
   112    argocd-util projects update-role-policy '*' remove override --role '*deployer*'
   113  `,
   114  		Run: func(c *cobra.Command, args []string) {
   115  			if len(args) != 3 {
   116  				c.HelpFunc()(c, args)
   117  				os.Exit(1)
   118  			}
   119  			projectGlob := args[0]
   120  			modificationType := args[1]
   121  			action := args[2]
   122  
   123  			config, err := clientConfig.ClientConfig()
   124  			errors.CheckError(err)
   125  			config.QPS = 100
   126  			config.Burst = 50
   127  
   128  			namespace, _, err := clientConfig.Namespace()
   129  			errors.CheckError(err)
   130  			appclients := appclientset.NewForConfigOrDie(config)
   131  
   132  			modification, err := getModification(modificationType, resource, scope, permission)
   133  			errors.CheckError(err)
   134  			projIf := appclients.ArgoprojV1alpha1().AppProjects(namespace)
   135  
   136  			err = updateProjects(projIf, projectGlob, rolePattern, action, modification, dryRun)
   137  			errors.CheckError(err)
   138  		},
   139  	}
   140  	command.Flags().StringVar(&resource, "resource", "", "Resource e.g. 'applications'")
   141  	command.Flags().StringVar(&scope, "scope", "", "Resource scope e.g. '*'")
   142  	command.Flags().StringVar(&rolePattern, "role", "*", "Role name pattern e.g. '*deployer*'")
   143  	command.Flags().StringVar(&permission, "permission", "", "Action permission")
   144  	command.Flags().BoolVar(&dryRun, "dry-run", true, "Dry run")
   145  	clientConfig = cli.AddKubectlFlagsToCmd(command)
   146  	return command
   147  }
   148  
   149  func updateProjects(projIf appclient.AppProjectInterface, projectGlob string, rolePattern string, action string, modification func(string, string) string, dryRun bool) error {
   150  	projects, err := projIf.List(context.Background(), v1.ListOptions{})
   151  	if err != nil {
   152  		return err
   153  	}
   154  	for _, proj := range projects.Items {
   155  		if !globMatch(projectGlob, proj.Name) {
   156  			continue
   157  		}
   158  		origProj := proj.DeepCopy()
   159  		updated := false
   160  		for i, role := range proj.Spec.Roles {
   161  			if !globMatch(rolePattern, role.Name) {
   162  				continue
   163  			}
   164  			actionPolicyIndex := -1
   165  			for i := range role.Policies {
   166  				parts := split(role.Policies[i], ",")
   167  				if len(parts) != 6 || parts[3] != action {
   168  					continue
   169  				}
   170  				actionPolicyIndex = i
   171  				break
   172  			}
   173  			policyPermission := modification(proj.Name, action)
   174  			if actionPolicyIndex == -1 && policyPermission != "" {
   175  				updated = true
   176  				role.Policies = append(role.Policies, formatPolicy(proj.Name, role.Name, policyPermission))
   177  			} else if actionPolicyIndex > -1 && policyPermission == "" {
   178  				updated = true
   179  				role.Policies = append(role.Policies[:actionPolicyIndex], role.Policies[actionPolicyIndex+1:]...)
   180  			} else if actionPolicyIndex > -1 && policyPermission != "" {
   181  				updated = true
   182  				role.Policies[actionPolicyIndex] = formatPolicy(proj.Name, role.Name, policyPermission)
   183  			}
   184  			proj.Spec.Roles[i] = role
   185  		}
   186  		if updated {
   187  			err = saveProject(proj, *origProj, projIf, dryRun)
   188  			if err != nil {
   189  				return err
   190  			}
   191  		}
   192  	}
   193  	return nil
   194  }