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

     1  package util
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"log"
     7  	"net/url"
     8  	"os"
     9  	"strings"
    10  
    11  	"github.com/spf13/cobra"
    12  	"github.com/spf13/pflag"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/utils/ptr"
    15  
    16  	"github.com/argoproj/argo-cd/v3/pkg/apis/application"
    17  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    18  	"github.com/argoproj/argo-cd/v3/util/config"
    19  	"github.com/argoproj/argo-cd/v3/util/gpg"
    20  )
    21  
    22  type ProjectOpts struct {
    23  	Description                string
    24  	destinations               []string
    25  	destinationServiceAccounts []string
    26  	Sources                    []string
    27  	SignatureKeys              []string
    28  	SourceNamespaces           []string
    29  
    30  	orphanedResourcesEnabled   bool
    31  	orphanedResourcesWarn      bool
    32  	allowedClusterResources    []string
    33  	deniedClusterResources     []string
    34  	allowedNamespacedResources []string
    35  	deniedNamespacedResources  []string
    36  }
    37  
    38  func AddProjFlags(command *cobra.Command, opts *ProjectOpts) {
    39  	command.Flags().StringVarP(&opts.Description, "description", "", "", "Project description")
    40  	command.Flags().StringArrayVarP(&opts.destinations, "dest", "d", []string{},
    41  		"Permitted destination server and namespace (e.g. https://192.168.99.100:8443,default)")
    42  	command.Flags().StringArrayVarP(&opts.Sources, "src", "s", []string{}, "Permitted source repository URL")
    43  	command.Flags().StringSliceVar(&opts.SignatureKeys, "signature-keys", []string{}, "GnuPG public key IDs for commit signature verification")
    44  	command.Flags().BoolVar(&opts.orphanedResourcesEnabled, "orphaned-resources", false, "Enables orphaned resources monitoring")
    45  	command.Flags().BoolVar(&opts.orphanedResourcesWarn, "orphaned-resources-warn", false, "Specifies if applications should have a warning condition when orphaned resources detected")
    46  	command.Flags().StringArrayVar(&opts.allowedClusterResources, "allow-cluster-resource", []string{}, "List of allowed cluster level resources")
    47  	command.Flags().StringArrayVar(&opts.deniedClusterResources, "deny-cluster-resource", []string{}, "List of denied cluster level resources")
    48  	command.Flags().StringArrayVar(&opts.allowedNamespacedResources, "allow-namespaced-resource", []string{}, "List of allowed namespaced resources")
    49  	command.Flags().StringArrayVar(&opts.deniedNamespacedResources, "deny-namespaced-resource", []string{}, "List of denied namespaced resources")
    50  	command.Flags().StringSliceVar(&opts.SourceNamespaces, "source-namespaces", []string{}, "List of source namespaces for applications")
    51  	command.Flags().StringArrayVar(&opts.destinationServiceAccounts, "dest-service-accounts", []string{},
    52  		"Destination server, namespace and target service account (e.g. https://192.168.99.100:8443,default,default-sa)")
    53  }
    54  
    55  func getGroupKindList(values []string) []metav1.GroupKind {
    56  	var res []metav1.GroupKind
    57  	for _, val := range values {
    58  		if parts := strings.Split(val, "/"); len(parts) == 2 {
    59  			res = append(res, metav1.GroupKind{Group: parts[0], Kind: parts[1]})
    60  		} else if len(parts) == 1 {
    61  			res = append(res, metav1.GroupKind{Kind: parts[0]})
    62  		}
    63  	}
    64  	return res
    65  }
    66  
    67  func (opts *ProjectOpts) GetAllowedClusterResources() []metav1.GroupKind {
    68  	return getGroupKindList(opts.allowedClusterResources)
    69  }
    70  
    71  func (opts *ProjectOpts) GetDeniedClusterResources() []metav1.GroupKind {
    72  	return getGroupKindList(opts.deniedClusterResources)
    73  }
    74  
    75  func (opts *ProjectOpts) GetAllowedNamespacedResources() []metav1.GroupKind {
    76  	return getGroupKindList(opts.allowedNamespacedResources)
    77  }
    78  
    79  func (opts *ProjectOpts) GetDeniedNamespacedResources() []metav1.GroupKind {
    80  	return getGroupKindList(opts.deniedNamespacedResources)
    81  }
    82  
    83  func (opts *ProjectOpts) GetDestinations() []v1alpha1.ApplicationDestination {
    84  	destinations := make([]v1alpha1.ApplicationDestination, 0)
    85  	for _, destStr := range opts.destinations {
    86  		parts := strings.Split(destStr, ",")
    87  		if len(parts) != 2 {
    88  			log.Fatalf("Expected destination of the form: server,namespace. Received: %s", destStr)
    89  		}
    90  		destinations = append(destinations, v1alpha1.ApplicationDestination{
    91  			Server:    parts[0],
    92  			Namespace: parts[1],
    93  		})
    94  	}
    95  	return destinations
    96  }
    97  
    98  func (opts *ProjectOpts) GetDestinationServiceAccounts() []v1alpha1.ApplicationDestinationServiceAccount {
    99  	destinationServiceAccounts := make([]v1alpha1.ApplicationDestinationServiceAccount, 0)
   100  	for _, destStr := range opts.destinationServiceAccounts {
   101  		parts := strings.Split(destStr, ",")
   102  		if len(parts) != 3 {
   103  			log.Fatalf("Expected destination service account of the form: server,namespace, defaultServiceAccount. Received: %s", destStr)
   104  		}
   105  		destinationServiceAccounts = append(destinationServiceAccounts, v1alpha1.ApplicationDestinationServiceAccount{
   106  			Server:                parts[0],
   107  			Namespace:             parts[1],
   108  			DefaultServiceAccount: parts[2],
   109  		})
   110  	}
   111  	return destinationServiceAccounts
   112  }
   113  
   114  // GetSignatureKeys TODO: Get configured keys and emit warning when a key is specified that is not configured
   115  func (opts *ProjectOpts) GetSignatureKeys() []v1alpha1.SignatureKey {
   116  	signatureKeys := make([]v1alpha1.SignatureKey, 0)
   117  	for _, keyStr := range opts.SignatureKeys {
   118  		if !gpg.IsShortKeyID(keyStr) && !gpg.IsLongKeyID(keyStr) {
   119  			log.Fatalf("'%s' is not a valid GnuPG key ID", keyStr)
   120  		}
   121  		signatureKeys = append(signatureKeys, v1alpha1.SignatureKey{KeyID: gpg.KeyID(keyStr)})
   122  	}
   123  	return signatureKeys
   124  }
   125  
   126  func (opts *ProjectOpts) GetSourceNamespaces() []string {
   127  	return opts.SourceNamespaces
   128  }
   129  
   130  func GetOrphanedResourcesSettings(flagSet *pflag.FlagSet, opts ProjectOpts) *v1alpha1.OrphanedResourcesMonitorSettings {
   131  	warnChanged := flagSet.Changed("orphaned-resources-warn")
   132  	if opts.orphanedResourcesEnabled || warnChanged {
   133  		settings := v1alpha1.OrphanedResourcesMonitorSettings{}
   134  		if warnChanged {
   135  			settings.Warn = ptr.To(opts.orphanedResourcesWarn)
   136  		}
   137  		return &settings
   138  	}
   139  	return nil
   140  }
   141  
   142  func readProjFromStdin(proj *v1alpha1.AppProject) error {
   143  	reader := bufio.NewReader(os.Stdin)
   144  	err := config.UnmarshalReader(reader, &proj)
   145  	if err != nil {
   146  		return fmt.Errorf("unable to read manifest from stdin: %w", err)
   147  	}
   148  	return nil
   149  }
   150  
   151  func readProjFromURI(fileURL string, proj *v1alpha1.AppProject) error {
   152  	parsedURL, err := url.ParseRequestURI(fileURL)
   153  	if err != nil || (parsedURL.Scheme != "http" && parsedURL.Scheme != "https") {
   154  		err = config.UnmarshalLocalFile(fileURL, &proj)
   155  	} else {
   156  		err = config.UnmarshalRemoteFile(fileURL, &proj)
   157  	}
   158  	if err != nil {
   159  		return fmt.Errorf("error reading proj from uri: %w", err)
   160  	}
   161  	return nil
   162  }
   163  
   164  func SetProjSpecOptions(flags *pflag.FlagSet, spec *v1alpha1.AppProjectSpec, projOpts *ProjectOpts) int {
   165  	visited := 0
   166  	flags.Visit(func(f *pflag.Flag) {
   167  		visited++
   168  		switch f.Name {
   169  		case "description":
   170  			spec.Description = projOpts.Description
   171  		case "dest":
   172  			spec.Destinations = projOpts.GetDestinations()
   173  		case "src":
   174  			spec.SourceRepos = projOpts.Sources
   175  		case "signature-keys":
   176  			spec.SignatureKeys = projOpts.GetSignatureKeys()
   177  		case "allow-cluster-resource":
   178  			spec.ClusterResourceWhitelist = projOpts.GetAllowedClusterResources()
   179  		case "deny-cluster-resource":
   180  			spec.ClusterResourceBlacklist = projOpts.GetDeniedClusterResources()
   181  		case "allow-namespaced-resource":
   182  			spec.NamespaceResourceWhitelist = projOpts.GetAllowedNamespacedResources()
   183  		case "deny-namespaced-resource":
   184  			spec.NamespaceResourceBlacklist = projOpts.GetDeniedNamespacedResources()
   185  		case "source-namespaces":
   186  			spec.SourceNamespaces = projOpts.GetSourceNamespaces()
   187  		case "dest-service-accounts":
   188  			spec.DestinationServiceAccounts = projOpts.GetDestinationServiceAccounts()
   189  		}
   190  	})
   191  	if flags.Changed("orphaned-resources") || flags.Changed("orphaned-resources-warn") {
   192  		spec.OrphanedResources = GetOrphanedResourcesSettings(flags, *projOpts)
   193  		visited++
   194  	}
   195  	return visited
   196  }
   197  
   198  func ConstructAppProj(fileURL string, args []string, opts ProjectOpts, c *cobra.Command) (*v1alpha1.AppProject, error) {
   199  	proj := v1alpha1.AppProject{
   200  		TypeMeta: metav1.TypeMeta{
   201  			Kind:       application.AppProjectKind,
   202  			APIVersion: application.Group + "/v1alpha1",
   203  		},
   204  	}
   205  	switch {
   206  	case fileURL == "-":
   207  		// read stdin
   208  		err := readProjFromStdin(&proj)
   209  		if err != nil {
   210  			return nil, err
   211  		}
   212  	case fileURL != "":
   213  		// read uri
   214  		err := readProjFromURI(fileURL, &proj)
   215  		if err != nil {
   216  			return nil, err
   217  		}
   218  
   219  		if len(args) == 1 && args[0] != proj.Name {
   220  			return nil, fmt.Errorf("project name '%s' does not match project spec metadata.name '%s'", args[0], proj.Name)
   221  		}
   222  	default:
   223  		// read arguments
   224  		if len(args) == 0 {
   225  			c.HelpFunc()(c, args)
   226  			os.Exit(1)
   227  		}
   228  		proj.Name = args[0]
   229  	}
   230  	SetProjSpecOptions(c.Flags(), &proj.Spec, &opts)
   231  	return &proj, nil
   232  }