github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/admin/project.go (about)

     1  package admin
     2  
     3  import (
     4  	"context"
     5  	stderrors "errors"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	cmdutil "github.com/argoproj/argo-cd/v3/cmd/util"
    12  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    13  	appclientset "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned"
    14  	appclient "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/typed/application/v1alpha1"
    15  	"github.com/argoproj/argo-cd/v3/util/cli"
    16  	"github.com/argoproj/argo-cd/v3/util/errors"
    17  	utilio "github.com/argoproj/argo-cd/v3/util/io"
    18  	"github.com/argoproj/argo-cd/v3/util/templates"
    19  
    20  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    21  	"github.com/spf13/cobra"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	"k8s.io/client-go/tools/clientcmd"
    24  )
    25  
    26  func NewProjectsCommand() *cobra.Command {
    27  	command := &cobra.Command{
    28  		Use:   "proj",
    29  		Short: "Manage projects configuration",
    30  		Run: func(c *cobra.Command, args []string) {
    31  			c.HelpFunc()(c, args)
    32  		},
    33  	}
    34  
    35  	command.AddCommand(NewGenProjectSpecCommand())
    36  	command.AddCommand(NewUpdatePolicyRuleCommand())
    37  	command.AddCommand(NewProjectAllowListGenCommand())
    38  	return command
    39  }
    40  
    41  // NewGenProjectSpecCommand generates declarative configuration file for given project
    42  func NewGenProjectSpecCommand() *cobra.Command {
    43  	var (
    44  		opts         cmdutil.ProjectOpts
    45  		fileURL      string
    46  		outputFormat string
    47  		inline       bool
    48  	)
    49  	command := &cobra.Command{
    50  		Use:   "generate-spec PROJECT",
    51  		Short: "Generate declarative config for a project",
    52  		Example: templates.Examples(`  
    53    # Generate a YAML configuration for a project named "myproject"
    54    argocd admin proj generate-spec myproject
    55  	  
    56    # Generate a JSON configuration for a project named "anotherproject" and specify an output file
    57    argocd admin proj generate-spec anotherproject --output json --file config.json
    58  	  
    59    # Generate a YAML configuration for a project named "someproject" and write it back to the input file
    60    argocd admin proj generate-spec someproject --inline  
    61    		`),
    62  
    63  		Run: func(c *cobra.Command, args []string) {
    64  			proj, err := cmdutil.ConstructAppProj(fileURL, args, opts, c)
    65  			errors.CheckError(err)
    66  
    67  			out, closer, err := getOutWriter(inline, fileURL)
    68  			errors.CheckError(err)
    69  			defer utilio.Close(closer)
    70  
    71  			errors.CheckError(PrintResources(outputFormat, out, proj))
    72  		},
    73  	}
    74  	command.Flags().StringVarP(&outputFormat, "output", "o", "yaml", "Output format. One of: json|yaml")
    75  	command.Flags().StringVarP(&fileURL, "file", "f", "", "Filename or URL to Kubernetes manifests for the project")
    76  	command.Flags().BoolVarP(&inline, "inline", "i", false, "If set then generated resource is written back to the file specified in --file flag")
    77  
    78  	// Only complete files with appropriate extension.
    79  	err := command.Flags().SetAnnotation("file", cobra.BashCompFilenameExt, []string{"json", "yaml", "yml"})
    80  	errors.CheckError(err)
    81  
    82  	cmdutil.AddProjFlags(command, &opts)
    83  	return command
    84  }
    85  
    86  func globMatch(pattern string, val string) bool {
    87  	if pattern == "*" {
    88  		return true
    89  	}
    90  	if ok, err := filepath.Match(pattern, val); ok && err == nil {
    91  		return true
    92  	}
    93  	return false
    94  }
    95  
    96  func getModification(modification string, resource string, scope string, permission string) (func(string, string) string, error) {
    97  	switch modification {
    98  	case "set":
    99  		if scope == "" {
   100  			return nil, stderrors.New("flag --group cannot be empty if permission should be set in role")
   101  		}
   102  		if permission == "" {
   103  			return nil, stderrors.New("flag --permission cannot be empty if permission should be set in role")
   104  		}
   105  		return func(proj string, action string) string {
   106  			return fmt.Sprintf("%s, %s, %s/%s, %s", resource, action, proj, scope, permission)
   107  		}, nil
   108  	case "remove":
   109  		return func(_ string, _ string) string {
   110  			return ""
   111  		}, nil
   112  	}
   113  	return nil, fmt.Errorf("modification %s is not supported", modification)
   114  }
   115  
   116  func saveProject(ctx context.Context, updated v1alpha1.AppProject, orig v1alpha1.AppProject, projectsIf appclient.AppProjectInterface, dryRun bool) error {
   117  	fmt.Printf("===== %s ======\n", updated.Name)
   118  	target, err := kube.ToUnstructured(&updated)
   119  	errors.CheckError(err)
   120  	live, err := kube.ToUnstructured(&orig)
   121  	if err != nil {
   122  		return fmt.Errorf("error converting project to unstructured: %w", err)
   123  	}
   124  	_ = cli.PrintDiff(updated.Name, target, live)
   125  	if !dryRun {
   126  		_, err = projectsIf.Update(ctx, &updated, metav1.UpdateOptions{})
   127  		if err != nil {
   128  			return fmt.Errorf("error while updating project:  %w", err)
   129  		}
   130  	}
   131  	return nil
   132  }
   133  
   134  func formatPolicy(proj string, role string, permission string) string {
   135  	return fmt.Sprintf("p, proj:%s:%s, %s", proj, role, permission)
   136  }
   137  
   138  func split(input string, delimiter string) []string {
   139  	parts := strings.Split(input, delimiter)
   140  	for i := range parts {
   141  		parts[i] = strings.TrimSpace(parts[i])
   142  	}
   143  	return parts
   144  }
   145  
   146  func NewUpdatePolicyRuleCommand() *cobra.Command {
   147  	var (
   148  		clientConfig clientcmd.ClientConfig
   149  		resource     string
   150  		scope        string
   151  		rolePattern  string
   152  		permission   string
   153  		dryRun       bool
   154  	)
   155  	command := &cobra.Command{
   156  		Use:   "update-role-policy PROJECT_GLOB MODIFICATION ACTION",
   157  		Short: "Implement bulk project role update. Useful to back-fill existing project policies or remove obsolete actions.",
   158  		Example: `  # Add policy that allows executing any action (action/*) to roles which name matches to *deployer* in all projects  
   159    argocd admin proj update-role-policy '*' set 'action/*' --role '*deployer*' --resource applications --scope '*' --permission allow
   160  
   161    # Remove policy that which manages running (action/*) from all roles which name matches *deployer* in all projects
   162    argocd admin proj update-role-policy '*' remove override --role '*deployer*'
   163  `,
   164  		Run: func(c *cobra.Command, args []string) {
   165  			ctx := c.Context()
   166  
   167  			if len(args) != 3 {
   168  				c.HelpFunc()(c, args)
   169  				os.Exit(1)
   170  			}
   171  			projectGlob := args[0]
   172  			modificationType := args[1]
   173  			action := args[2]
   174  
   175  			config, err := clientConfig.ClientConfig()
   176  			errors.CheckError(err)
   177  			config.QPS = 100
   178  			config.Burst = 50
   179  
   180  			namespace, _, err := clientConfig.Namespace()
   181  			errors.CheckError(err)
   182  			appclients := appclientset.NewForConfigOrDie(config)
   183  
   184  			modification, err := getModification(modificationType, resource, scope, permission)
   185  			errors.CheckError(err)
   186  			projIf := appclients.ArgoprojV1alpha1().AppProjects(namespace)
   187  
   188  			err = updateProjects(ctx, projIf, projectGlob, rolePattern, action, modification, dryRun)
   189  			errors.CheckError(err)
   190  		},
   191  	}
   192  	command.Flags().StringVar(&resource, "resource", "", "Resource e.g. 'applications'")
   193  	command.Flags().StringVar(&scope, "scope", "", "Resource scope e.g. '*'")
   194  	command.Flags().StringVar(&rolePattern, "role", "*", "Role name pattern e.g. '*deployer*'")
   195  	command.Flags().StringVar(&permission, "permission", "", "Action permission")
   196  	command.Flags().BoolVar(&dryRun, "dry-run", true, "Dry run")
   197  	clientConfig = cli.AddKubectlFlagsToCmd(command)
   198  	return command
   199  }
   200  
   201  func updateProjects(ctx context.Context, projIf appclient.AppProjectInterface, projectGlob string, rolePattern string, action string, modification func(string, string) string, dryRun bool) error {
   202  	projects, err := projIf.List(ctx, metav1.ListOptions{})
   203  	if err != nil {
   204  		return fmt.Errorf("error listing the projects: %w", err)
   205  	}
   206  	for _, proj := range projects.Items {
   207  		if !globMatch(projectGlob, proj.Name) {
   208  			continue
   209  		}
   210  		origProj := proj.DeepCopy()
   211  		updated := false
   212  		for i, role := range proj.Spec.Roles {
   213  			if !globMatch(rolePattern, role.Name) {
   214  				continue
   215  			}
   216  			actionPolicyIndex := -1
   217  			for i := range role.Policies {
   218  				parts := split(role.Policies[i], ",")
   219  				if len(parts) != 6 || parts[3] != action {
   220  					continue
   221  				}
   222  				actionPolicyIndex = i
   223  				break
   224  			}
   225  			policyPermission := modification(proj.Name, action)
   226  			switch {
   227  			case actionPolicyIndex == -1 && policyPermission != "":
   228  				updated = true
   229  				role.Policies = append(role.Policies, formatPolicy(proj.Name, role.Name, policyPermission))
   230  			case actionPolicyIndex > -1 && policyPermission == "":
   231  				updated = true
   232  				role.Policies = append(role.Policies[:actionPolicyIndex], role.Policies[actionPolicyIndex+1:]...)
   233  			case actionPolicyIndex > -1 && policyPermission != "":
   234  				updated = true
   235  				role.Policies[actionPolicyIndex] = formatPolicy(proj.Name, role.Name, policyPermission)
   236  			}
   237  			proj.Spec.Roles[i] = role
   238  		}
   239  		if updated {
   240  			err = saveProject(ctx, proj, *origProj, projIf, dryRun)
   241  			if err != nil {
   242  				return fmt.Errorf("error saving the project: %w", err)
   243  			}
   244  		}
   245  	}
   246  	return nil
   247  }