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 }