github.com/argoproj/argo-cd@v1.8.7/pkg/apis/application/v1alpha1/types.go (about)

     1  package v1alpha1
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	math "math"
     7  	"net"
     8  	"net/http"
     9  	"net/url"
    10  	"os"
    11  	"path/filepath"
    12  	"reflect"
    13  	"regexp"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/argoproj/gitops-engine/pkg/health"
    20  	synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
    21  	"github.com/ghodss/yaml"
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/robfig/cron"
    24  	log "github.com/sirupsen/logrus"
    25  	"google.golang.org/grpc/codes"
    26  	"google.golang.org/grpc/status"
    27  	v1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	utilnet "k8s.io/apimachinery/pkg/util/net"
    32  	"k8s.io/apimachinery/pkg/watch"
    33  	"k8s.io/client-go/rest"
    34  	"k8s.io/client-go/tools/clientcmd"
    35  	"k8s.io/client-go/tools/clientcmd/api"
    36  
    37  	"github.com/argoproj/argo-cd/common"
    38  	"github.com/argoproj/argo-cd/util/cert"
    39  	"github.com/argoproj/argo-cd/util/git"
    40  	"github.com/argoproj/argo-cd/util/glob"
    41  	"github.com/argoproj/argo-cd/util/helm"
    42  )
    43  
    44  // Application is a definition of Application resource.
    45  // +genclient
    46  // +genclient:noStatus
    47  // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    48  // +kubebuilder:resource:path=applications,shortName=app;apps
    49  // +kubebuilder:printcolumn:name="Sync Status",type=string,JSONPath=`.status.sync.status`
    50  // +kubebuilder:printcolumn:name="Health Status",type=string,JSONPath=`.status.health.status`
    51  // +kubebuilder:printcolumn:name="Revision",type=string,JSONPath=`.status.sync.revision`,priority=10
    52  type Application struct {
    53  	metav1.TypeMeta   `json:",inline"`
    54  	metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
    55  	Spec              ApplicationSpec   `json:"spec" protobuf:"bytes,2,opt,name=spec"`
    56  	Status            ApplicationStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
    57  	Operation         *Operation        `json:"operation,omitempty" protobuf:"bytes,4,opt,name=operation"`
    58  }
    59  
    60  // ApplicationSpec represents desired application state. Contains link to repository with application definition and additional parameters link definition revision.
    61  type ApplicationSpec struct {
    62  	// Source is a reference to the location ksonnet application definition
    63  	Source ApplicationSource `json:"source" protobuf:"bytes,1,opt,name=source"`
    64  	// Destination overrides the kubernetes server and namespace defined in the environment ksonnet app.yaml
    65  	Destination ApplicationDestination `json:"destination" protobuf:"bytes,2,name=destination"`
    66  	// Project is a application project name. Empty name means that application belongs to 'default' project.
    67  	Project string `json:"project" protobuf:"bytes,3,name=project"`
    68  	// SyncPolicy controls when a sync will be performed
    69  	SyncPolicy *SyncPolicy `json:"syncPolicy,omitempty" protobuf:"bytes,4,name=syncPolicy"`
    70  	// IgnoreDifferences controls resources fields which should be ignored during comparison
    71  	IgnoreDifferences []ResourceIgnoreDifferences `json:"ignoreDifferences,omitempty" protobuf:"bytes,5,name=ignoreDifferences"`
    72  	// Infos contains a list of useful information (URLs, email addresses, and plain text) that relates to the application
    73  	Info []Info `json:"info,omitempty" protobuf:"bytes,6,name=info"`
    74  	// This limits this number of items kept in the apps revision history.
    75  	// This should only be changed in exceptional circumstances.
    76  	// Setting to zero will store no history. This will reduce storage used.
    77  	// Increasing will increase the space used to store the history, so we do not recommend increasing it.
    78  	// Default is 10.
    79  	RevisionHistoryLimit *int64 `json:"revisionHistoryLimit,omitempty" protobuf:"bytes,7,name=revisionHistoryLimit"`
    80  }
    81  
    82  // ResourceIgnoreDifferences contains resource filter and list of json paths which should be ignored during comparison with live state.
    83  type ResourceIgnoreDifferences struct {
    84  	Group        string   `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"`
    85  	Kind         string   `json:"kind" protobuf:"bytes,2,opt,name=kind"`
    86  	Name         string   `json:"name,omitempty" protobuf:"bytes,3,opt,name=name"`
    87  	Namespace    string   `json:"namespace,omitempty" protobuf:"bytes,4,opt,name=namespace"`
    88  	JSONPointers []string `json:"jsonPointers" protobuf:"bytes,5,opt,name=jsonPointers"`
    89  }
    90  
    91  type EnvEntry struct {
    92  	// the name, usually uppercase
    93  	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
    94  	// the value
    95  	Value string `json:"value" protobuf:"bytes,2,opt,name=value"`
    96  }
    97  
    98  func (a *EnvEntry) IsZero() bool {
    99  	return a == nil || a.Name == "" && a.Value == ""
   100  }
   101  
   102  type Env []*EnvEntry
   103  
   104  func (e Env) IsZero() bool {
   105  	return len(e) == 0
   106  }
   107  
   108  func (e Env) Environ() []string {
   109  	var environ []string
   110  	for _, item := range e {
   111  		if !item.IsZero() {
   112  			environ = append(environ, fmt.Sprintf("%s=%s", item.Name, item.Value))
   113  		}
   114  	}
   115  	return environ
   116  }
   117  
   118  // does an operation similar to `envsubst` tool,
   119  // but unlike envsubst it does not change missing names into empty string
   120  // see https://linux.die.net/man/1/envsubst
   121  func (e Env) Envsubst(s string) string {
   122  	for _, v := range e {
   123  		s = strings.ReplaceAll(s, fmt.Sprintf("$%s", v.Name), v.Value)
   124  		s = strings.ReplaceAll(s, fmt.Sprintf("${%s}", v.Name), v.Value)
   125  	}
   126  	return s
   127  }
   128  
   129  // ApplicationSource contains information about github repository, path within repository and target application environment.
   130  type ApplicationSource struct {
   131  	// RepoURL is the repository URL of the application manifests
   132  	RepoURL string `json:"repoURL" protobuf:"bytes,1,opt,name=repoURL"`
   133  	// Path is a directory path within the Git repository
   134  	Path string `json:"path,omitempty" protobuf:"bytes,2,opt,name=path"`
   135  	// TargetRevision defines the commit, tag, or branch in which to sync the application to.
   136  	// If omitted, will sync to HEAD
   137  	TargetRevision string `json:"targetRevision,omitempty" protobuf:"bytes,4,opt,name=targetRevision"`
   138  	// Helm holds helm specific options
   139  	Helm *ApplicationSourceHelm `json:"helm,omitempty" protobuf:"bytes,7,opt,name=helm"`
   140  	// Kustomize holds kustomize specific options
   141  	Kustomize *ApplicationSourceKustomize `json:"kustomize,omitempty" protobuf:"bytes,8,opt,name=kustomize"`
   142  	// Ksonnet holds ksonnet specific options
   143  	Ksonnet *ApplicationSourceKsonnet `json:"ksonnet,omitempty" protobuf:"bytes,9,opt,name=ksonnet"`
   144  	// Directory holds path/directory specific options
   145  	Directory *ApplicationSourceDirectory `json:"directory,omitempty" protobuf:"bytes,10,opt,name=directory"`
   146  	// ConfigManagementPlugin holds config management plugin specific options
   147  	Plugin *ApplicationSourcePlugin `json:"plugin,omitempty" protobuf:"bytes,11,opt,name=plugin"`
   148  	// Chart is a Helm chart name
   149  	Chart string `json:"chart,omitempty" protobuf:"bytes,12,opt,name=chart"`
   150  }
   151  
   152  // AllowsConcurrentProcessing returns true if given application source can be processed concurrently
   153  func (a *ApplicationSource) AllowsConcurrentProcessing() bool {
   154  	switch {
   155  	// Kustomize with parameters requires changing kustomization.yaml file
   156  	case a.Kustomize != nil:
   157  		return a.Kustomize.AllowsConcurrentProcessing()
   158  	// Kustomize with parameters requires changing params.libsonnet file
   159  	case a.Ksonnet != nil:
   160  		return a.Ksonnet.AllowsConcurrentProcessing()
   161  	}
   162  	return true
   163  }
   164  
   165  func (a *ApplicationSource) IsHelm() bool {
   166  	return a.Chart != ""
   167  }
   168  
   169  func (a *ApplicationSource) IsHelmOci() bool {
   170  	if a.Chart == "" {
   171  		return false
   172  	}
   173  	return helm.IsHelmOciChart(a.Chart)
   174  }
   175  
   176  func (a *ApplicationSource) IsZero() bool {
   177  	return a == nil ||
   178  		a.RepoURL == "" &&
   179  			a.Path == "" &&
   180  			a.TargetRevision == "" &&
   181  			a.Helm.IsZero() &&
   182  			a.Kustomize.IsZero() &&
   183  			a.Ksonnet.IsZero() &&
   184  			a.Directory.IsZero() &&
   185  			a.Plugin.IsZero()
   186  }
   187  
   188  type ApplicationSourceType string
   189  
   190  const (
   191  	ApplicationSourceTypeHelm      ApplicationSourceType = "Helm"
   192  	ApplicationSourceTypeKustomize ApplicationSourceType = "Kustomize"
   193  	ApplicationSourceTypeKsonnet   ApplicationSourceType = "Ksonnet"
   194  	ApplicationSourceTypeDirectory ApplicationSourceType = "Directory"
   195  	ApplicationSourceTypePlugin    ApplicationSourceType = "Plugin"
   196  )
   197  
   198  type RefreshType string
   199  
   200  const (
   201  	RefreshTypeNormal RefreshType = "normal"
   202  	RefreshTypeHard   RefreshType = "hard"
   203  )
   204  
   205  // ApplicationSourceHelm holds helm specific options
   206  type ApplicationSourceHelm struct {
   207  	// ValuesFiles is a list of Helm value files to use when generating a template
   208  	ValueFiles []string `json:"valueFiles,omitempty" protobuf:"bytes,1,opt,name=valueFiles"`
   209  	// Parameters are parameters to the helm template
   210  	Parameters []HelmParameter `json:"parameters,omitempty" protobuf:"bytes,2,opt,name=parameters"`
   211  	// The Helm release name. If omitted it will use the application name
   212  	ReleaseName string `json:"releaseName,omitempty" protobuf:"bytes,3,opt,name=releaseName"`
   213  	// Values is Helm values, typically defined as a block
   214  	Values string `json:"values,omitempty" protobuf:"bytes,4,opt,name=values"`
   215  	// FileParameters are file parameters to the helm template
   216  	FileParameters []HelmFileParameter `json:"fileParameters,omitempty" protobuf:"bytes,5,opt,name=fileParameters"`
   217  	// Version is the Helm version to use for templating with
   218  	Version string `json:"version,omitempty" protobuf:"bytes,6,opt,name=version"`
   219  }
   220  
   221  // HelmParameter is a parameter to a helm template
   222  type HelmParameter struct {
   223  	// Name is the name of the helm parameter
   224  	Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
   225  	// Value is the value for the helm parameter
   226  	Value string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"`
   227  	// ForceString determines whether to tell Helm to interpret booleans and numbers as strings
   228  	ForceString bool `json:"forceString,omitempty" protobuf:"bytes,3,opt,name=forceString"`
   229  }
   230  
   231  // HelmFileParameter is a file parameter to a helm template
   232  type HelmFileParameter struct {
   233  	// Name is the name of the helm parameter
   234  	Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
   235  	// Path is the path value for the helm parameter
   236  	Path string `json:"path,omitempty" protobuf:"bytes,2,opt,name=path"`
   237  }
   238  
   239  var helmParameterRx = regexp.MustCompile(`([^\\]),`)
   240  
   241  func NewHelmParameter(text string, forceString bool) (*HelmParameter, error) {
   242  	parts := strings.SplitN(text, "=", 2)
   243  	if len(parts) != 2 {
   244  		return nil, fmt.Errorf("Expected helm parameter of the form: param=value. Received: %s", text)
   245  	}
   246  	return &HelmParameter{
   247  		Name:        parts[0],
   248  		Value:       helmParameterRx.ReplaceAllString(parts[1], `$1\,`),
   249  		ForceString: forceString,
   250  	}, nil
   251  }
   252  
   253  func NewHelmFileParameter(text string) (*HelmFileParameter, error) {
   254  	parts := strings.SplitN(text, "=", 2)
   255  	if len(parts) != 2 {
   256  		return nil, fmt.Errorf("Expected helm file parameter of the form: param=path. Received: %s", text)
   257  	}
   258  	return &HelmFileParameter{
   259  		Name: parts[0],
   260  		Path: helmParameterRx.ReplaceAllString(parts[1], `$1\,`),
   261  	}, nil
   262  }
   263  
   264  func (in *ApplicationSourceHelm) AddParameter(p HelmParameter) {
   265  	found := false
   266  	for i, cp := range in.Parameters {
   267  		if cp.Name == p.Name {
   268  			found = true
   269  			in.Parameters[i] = p
   270  			break
   271  		}
   272  	}
   273  	if !found {
   274  		in.Parameters = append(in.Parameters, p)
   275  	}
   276  }
   277  
   278  func (in *ApplicationSourceHelm) AddFileParameter(p HelmFileParameter) {
   279  	found := false
   280  	for i, cp := range in.FileParameters {
   281  		if cp.Name == p.Name {
   282  			found = true
   283  			in.FileParameters[i] = p
   284  			break
   285  		}
   286  	}
   287  	if !found {
   288  		in.FileParameters = append(in.FileParameters, p)
   289  	}
   290  }
   291  
   292  func (h *ApplicationSourceHelm) IsZero() bool {
   293  	return h == nil || (h.Version == "") && (h.ReleaseName == "") && len(h.ValueFiles) == 0 && len(h.Parameters) == 0 && len(h.FileParameters) == 0 && h.Values == ""
   294  }
   295  
   296  type KustomizeImage string
   297  
   298  func (i KustomizeImage) delim() string {
   299  	for _, d := range []string{"=", ":", "@"} {
   300  		if strings.Contains(string(i), d) {
   301  			return d
   302  		}
   303  	}
   304  	return ":"
   305  }
   306  
   307  // if the image name matches (i.e. up to the first delimiter)
   308  func (i KustomizeImage) Match(j KustomizeImage) bool {
   309  	delim := j.delim()
   310  	if !strings.Contains(string(j), delim) {
   311  		return false
   312  	}
   313  	return strings.HasPrefix(string(i), strings.Split(string(j), delim)[0])
   314  }
   315  
   316  type KustomizeImages []KustomizeImage
   317  
   318  // find the image or -1
   319  func (images KustomizeImages) Find(image KustomizeImage) int {
   320  	for i, a := range images {
   321  		if a.Match(image) {
   322  			return i
   323  		}
   324  	}
   325  	return -1
   326  }
   327  
   328  // ApplicationSourceKustomize holds kustomize specific options
   329  type ApplicationSourceKustomize struct {
   330  	// NamePrefix is a prefix appended to resources for kustomize apps
   331  	NamePrefix string `json:"namePrefix,omitempty" protobuf:"bytes,1,opt,name=namePrefix"`
   332  	// NameSuffix is a suffix appended to resources for kustomize apps
   333  	NameSuffix string `json:"nameSuffix,omitempty" protobuf:"bytes,2,opt,name=nameSuffix"`
   334  	// Images are kustomize image overrides
   335  	Images KustomizeImages `json:"images,omitempty" protobuf:"bytes,3,opt,name=images"`
   336  	// CommonLabels adds additional kustomize commonLabels
   337  	CommonLabels map[string]string `json:"commonLabels,omitempty" protobuf:"bytes,4,opt,name=commonLabels"`
   338  	// Version contains optional Kustomize version
   339  	Version string `json:"version,omitempty" protobuf:"bytes,5,opt,name=version"`
   340  	// CommonAnnotations adds additional kustomize commonAnnotations
   341  	CommonAnnotations map[string]string `json:"commonAnnotations,omitempty" protobuf:"bytes,6,opt,name=commonAnnotations"`
   342  }
   343  
   344  func (k *ApplicationSourceKustomize) AllowsConcurrentProcessing() bool {
   345  	return len(k.Images) == 0 &&
   346  		len(k.CommonLabels) == 0 &&
   347  		k.NamePrefix == "" &&
   348  		k.NameSuffix == ""
   349  }
   350  
   351  func (k *ApplicationSourceKustomize) IsZero() bool {
   352  	return k == nil ||
   353  		k.NamePrefix == "" &&
   354  			k.NameSuffix == "" &&
   355  			k.Version == "" &&
   356  			len(k.Images) == 0 &&
   357  			len(k.CommonLabels) == 0 &&
   358  			len(k.CommonAnnotations) == 0
   359  }
   360  
   361  // either updates or adds the images
   362  func (k *ApplicationSourceKustomize) MergeImage(image KustomizeImage) {
   363  	i := k.Images.Find(image)
   364  	if i >= 0 {
   365  		k.Images[i] = image
   366  	} else {
   367  		k.Images = append(k.Images, image)
   368  	}
   369  }
   370  
   371  // JsonnetVar is a jsonnet variable
   372  type JsonnetVar struct {
   373  	Name  string `json:"name" protobuf:"bytes,1,opt,name=name"`
   374  	Value string `json:"value" protobuf:"bytes,2,opt,name=value"`
   375  	Code  bool   `json:"code,omitempty" protobuf:"bytes,3,opt,name=code"`
   376  }
   377  
   378  func NewJsonnetVar(s string, code bool) JsonnetVar {
   379  	parts := strings.SplitN(s, "=", 2)
   380  	if len(parts) == 2 {
   381  		return JsonnetVar{Name: parts[0], Value: parts[1], Code: code}
   382  	} else {
   383  		return JsonnetVar{Name: s, Code: code}
   384  	}
   385  }
   386  
   387  // ApplicationSourceJsonnet holds jsonnet specific options
   388  type ApplicationSourceJsonnet struct {
   389  	// ExtVars is a list of Jsonnet External Variables
   390  	ExtVars []JsonnetVar `json:"extVars,omitempty" protobuf:"bytes,1,opt,name=extVars"`
   391  	// TLAS is a list of Jsonnet Top-level Arguments
   392  	TLAs []JsonnetVar `json:"tlas,omitempty" protobuf:"bytes,2,opt,name=tlas"`
   393  	// Additional library search dirs
   394  	Libs []string `json:"libs,omitempty" protobuf:"bytes,3,opt,name=libs"`
   395  }
   396  
   397  func (j *ApplicationSourceJsonnet) IsZero() bool {
   398  	return j == nil || len(j.ExtVars) == 0 && len(j.TLAs) == 0 && len(j.Libs) == 0
   399  }
   400  
   401  // ApplicationSourceKsonnet holds ksonnet specific options
   402  type ApplicationSourceKsonnet struct {
   403  	// Environment is a ksonnet application environment name
   404  	Environment string `json:"environment,omitempty" protobuf:"bytes,1,opt,name=environment"`
   405  	// Parameters are a list of ksonnet component parameter override values
   406  	Parameters []KsonnetParameter `json:"parameters,omitempty" protobuf:"bytes,2,opt,name=parameters"`
   407  }
   408  
   409  // KsonnetParameter is a ksonnet component parameter
   410  type KsonnetParameter struct {
   411  	Component string `json:"component,omitempty" protobuf:"bytes,1,opt,name=component"`
   412  	Name      string `json:"name" protobuf:"bytes,2,opt,name=name"`
   413  	Value     string `json:"value" protobuf:"bytes,3,opt,name=value"`
   414  }
   415  
   416  func (k *ApplicationSourceKsonnet) AllowsConcurrentProcessing() bool {
   417  	return len(k.Parameters) == 0
   418  }
   419  
   420  func (k *ApplicationSourceKsonnet) IsZero() bool {
   421  	return k == nil || k.Environment == "" && len(k.Parameters) == 0
   422  }
   423  
   424  type ApplicationSourceDirectory struct {
   425  	Recurse bool                     `json:"recurse,omitempty" protobuf:"bytes,1,opt,name=recurse"`
   426  	Jsonnet ApplicationSourceJsonnet `json:"jsonnet,omitempty" protobuf:"bytes,2,opt,name=jsonnet"`
   427  	Exclude string                   `json:"exclude,omitempty" protobuf:"bytes,3,opt,name=exclude"`
   428  }
   429  
   430  func (d *ApplicationSourceDirectory) IsZero() bool {
   431  	return d == nil || !d.Recurse && d.Jsonnet.IsZero()
   432  }
   433  
   434  // ApplicationSourcePlugin holds config management plugin specific options
   435  type ApplicationSourcePlugin struct {
   436  	Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
   437  	Env  `json:"env,omitempty" protobuf:"bytes,2,opt,name=env"`
   438  }
   439  
   440  func (c *ApplicationSourcePlugin) IsZero() bool {
   441  	return c == nil || c.Name == "" && c.Env.IsZero()
   442  }
   443  
   444  // ApplicationDestination contains deployment destination information
   445  type ApplicationDestination struct {
   446  	// Server overrides the environment server value in the ksonnet app.yaml
   447  	Server string `json:"server,omitempty" protobuf:"bytes,1,opt,name=server"`
   448  	// Namespace overrides the environment namespace value in the ksonnet app.yaml
   449  	Namespace string `json:"namespace,omitempty" protobuf:"bytes,2,opt,name=namespace"`
   450  	// Name of the destination cluster which can be used instead of server (url) field
   451  	Name string `json:"name,omitempty" protobuf:"bytes,3,opt,name=name"`
   452  
   453  	// nolint:govet
   454  	isServerInferred bool `json:"-"`
   455  }
   456  
   457  // ApplicationStatus contains information about application sync, health status
   458  type ApplicationStatus struct {
   459  	Resources  []ResourceStatus       `json:"resources,omitempty" protobuf:"bytes,1,opt,name=resources"`
   460  	Sync       SyncStatus             `json:"sync,omitempty" protobuf:"bytes,2,opt,name=sync"`
   461  	Health     HealthStatus           `json:"health,omitempty" protobuf:"bytes,3,opt,name=health"`
   462  	History    RevisionHistories      `json:"history,omitempty" protobuf:"bytes,4,opt,name=history"`
   463  	Conditions []ApplicationCondition `json:"conditions,omitempty" protobuf:"bytes,5,opt,name=conditions"`
   464  	// ReconciledAt indicates when the application state was reconciled using the latest git version
   465  	ReconciledAt   *metav1.Time    `json:"reconciledAt,omitempty" protobuf:"bytes,6,opt,name=reconciledAt"`
   466  	OperationState *OperationState `json:"operationState,omitempty" protobuf:"bytes,7,opt,name=operationState"`
   467  	// ObservedAt indicates when the application state was updated without querying latest git state
   468  	// Deprecated: controller no longer updates ObservedAt field
   469  	ObservedAt *metav1.Time          `json:"observedAt,omitempty" protobuf:"bytes,8,opt,name=observedAt"`
   470  	SourceType ApplicationSourceType `json:"sourceType,omitempty" protobuf:"bytes,9,opt,name=sourceType"`
   471  	Summary    ApplicationSummary    `json:"summary,omitempty" protobuf:"bytes,10,opt,name=summary"`
   472  }
   473  
   474  type JWTTokens struct {
   475  	Items []JWTToken `json:"items,omitempty" protobuf:"bytes,1,opt,name=items"`
   476  }
   477  
   478  // AppProjectStatus contains information about appproj
   479  type AppProjectStatus struct {
   480  	JWTTokensByRole map[string]JWTTokens `json:"jwtTokensByRole,omitempty" protobuf:"bytes,1,opt,name=jwtTokensByRole"`
   481  }
   482  
   483  // OperationInitiator holds information about the operation initiator
   484  type OperationInitiator struct {
   485  	// Name of a user who started operation.
   486  	Username string `json:"username,omitempty" protobuf:"bytes,1,opt,name=username"`
   487  	// Automated is set to true if operation was initiated automatically by the application controller.
   488  	Automated bool `json:"automated,omitempty" protobuf:"bytes,2,opt,name=automated"`
   489  }
   490  
   491  // Operation contains requested operation parameters.
   492  type Operation struct {
   493  	Sync        *SyncOperation     `json:"sync,omitempty" protobuf:"bytes,1,opt,name=sync"`
   494  	InitiatedBy OperationInitiator `json:"initiatedBy,omitempty" protobuf:"bytes,2,opt,name=initiatedBy"`
   495  	Info        []*Info            `json:"info,omitempty" protobuf:"bytes,3,name=info"`
   496  	// Retry controls failed sync retry behavior
   497  	Retry RetryStrategy `json:"retry,omitempty" protobuf:"bytes,4,opt,name=retry"`
   498  }
   499  
   500  func (o *Operation) DryRun() bool {
   501  	if o.Sync != nil {
   502  		return o.Sync.DryRun
   503  	}
   504  	return false
   505  }
   506  
   507  // SyncOperationResource contains resources to sync.
   508  type SyncOperationResource struct {
   509  	Group     string `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"`
   510  	Kind      string `json:"kind" protobuf:"bytes,2,opt,name=kind"`
   511  	Name      string `json:"name" protobuf:"bytes,3,opt,name=name"`
   512  	Namespace string `json:"namespace,omitempty" protobuf:"bytes,4,opt,name=namespace"`
   513  }
   514  
   515  // RevisionHistories is a array of history, oldest first and newest last
   516  type RevisionHistories []RevisionHistory
   517  
   518  func (in RevisionHistories) LastRevisionHistory() RevisionHistory {
   519  	return in[len(in)-1]
   520  }
   521  
   522  func (in RevisionHistories) Trunc(n int) RevisionHistories {
   523  	i := len(in) - n
   524  	if i > 0 {
   525  		in = in[i:]
   526  	}
   527  	return in
   528  }
   529  
   530  // HasIdentity determines whether a sync operation is identified by a manifest
   531  func (r SyncOperationResource) HasIdentity(name string, namespace string, gvk schema.GroupVersionKind) bool {
   532  	if name == r.Name && gvk.Kind == r.Kind && gvk.Group == r.Group && (r.Namespace == "" || namespace == r.Namespace) {
   533  		return true
   534  	}
   535  	return false
   536  }
   537  
   538  // SyncOperation contains sync operation details.
   539  type SyncOperation struct {
   540  	// Revision is the revision in which to sync the application to.
   541  	// If omitted, will use the revision specified in app spec.
   542  	Revision string `json:"revision,omitempty" protobuf:"bytes,1,opt,name=revision"`
   543  	// Prune deletes resources that are no longer tracked in git
   544  	Prune bool `json:"prune,omitempty" protobuf:"bytes,2,opt,name=prune"`
   545  	// DryRun will perform a `kubectl apply --dry-run` without actually performing the sync
   546  	DryRun bool `json:"dryRun,omitempty" protobuf:"bytes,3,opt,name=dryRun"`
   547  	// SyncStrategy describes how to perform the sync
   548  	SyncStrategy *SyncStrategy `json:"syncStrategy,omitempty" protobuf:"bytes,4,opt,name=syncStrategy"`
   549  	// Resources describes which resources to sync
   550  	Resources []SyncOperationResource `json:"resources,omitempty" protobuf:"bytes,6,opt,name=resources"`
   551  	// Source overrides the source definition set in the application.
   552  	// This is typically set in a Rollback operation and nil during a Sync operation
   553  	Source *ApplicationSource `json:"source,omitempty" protobuf:"bytes,7,opt,name=source"`
   554  	// Manifests is an optional field that overrides sync source with a local directory for development
   555  	Manifests []string `json:"manifests,omitempty" protobuf:"bytes,8,opt,name=manifests"`
   556  	// SyncOptions provide per-sync sync-options, e.g. Validate=false
   557  	SyncOptions SyncOptions `json:"syncOptions,omitempty" protobuf:"bytes,9,opt,name=syncOptions"`
   558  }
   559  
   560  func (o *SyncOperation) IsApplyStrategy() bool {
   561  	return o.SyncStrategy != nil && o.SyncStrategy.Apply != nil
   562  }
   563  
   564  // OperationState contains information about state of currently performing operation on application.
   565  type OperationState struct {
   566  	// Operation is the original requested operation
   567  	Operation Operation `json:"operation" protobuf:"bytes,1,opt,name=operation"`
   568  	// Phase is the current phase of the operation
   569  	Phase synccommon.OperationPhase `json:"phase" protobuf:"bytes,2,opt,name=phase"`
   570  	// Message hold any pertinent messages when attempting to perform operation (typically errors).
   571  	Message string `json:"message,omitempty" protobuf:"bytes,3,opt,name=message"`
   572  	// SyncResult is the result of a Sync operation
   573  	SyncResult *SyncOperationResult `json:"syncResult,omitempty" protobuf:"bytes,4,opt,name=syncResult"`
   574  	// StartedAt contains time of operation start
   575  	StartedAt metav1.Time `json:"startedAt" protobuf:"bytes,6,opt,name=startedAt"`
   576  	// FinishedAt contains time of operation completion
   577  	FinishedAt *metav1.Time `json:"finishedAt,omitempty" protobuf:"bytes,7,opt,name=finishedAt"`
   578  	// RetryCount contains time of operation retries
   579  	RetryCount int64 `json:"retryCount,omitempty" protobuf:"bytes,8,opt,name=retryCount"`
   580  }
   581  
   582  type Info struct {
   583  	Name  string `json:"name" protobuf:"bytes,1,name=name"`
   584  	Value string `json:"value" protobuf:"bytes,2,name=value"`
   585  }
   586  
   587  type SyncOptions []string
   588  
   589  func (o SyncOptions) AddOption(option string) SyncOptions {
   590  	for _, j := range o {
   591  		if j == option {
   592  			return o
   593  		}
   594  	}
   595  	return append(o, option)
   596  }
   597  
   598  func (o SyncOptions) RemoveOption(option string) SyncOptions {
   599  	for i, j := range o {
   600  		if j == option {
   601  			return append(o[:i], o[i+1:]...)
   602  		}
   603  	}
   604  	return o
   605  }
   606  
   607  func (o SyncOptions) HasOption(option string) bool {
   608  	for _, i := range o {
   609  		if option == i {
   610  			return true
   611  		}
   612  	}
   613  	return false
   614  }
   615  
   616  // SyncPolicy controls when a sync will be performed in response to updates in git
   617  type SyncPolicy struct {
   618  	// Automated will keep an application synced to the target revision
   619  	Automated *SyncPolicyAutomated `json:"automated,omitempty" protobuf:"bytes,1,opt,name=automated"`
   620  	// Options allow you to specify whole app sync-options
   621  	SyncOptions SyncOptions `json:"syncOptions,omitempty" protobuf:"bytes,2,opt,name=syncOptions"`
   622  	// Retry controls failed sync retry behavior
   623  	Retry *RetryStrategy `json:"retry,omitempty" protobuf:"bytes,3,opt,name=retry"`
   624  }
   625  
   626  func (p *SyncPolicy) IsZero() bool {
   627  	return p == nil || (p.Automated == nil && len(p.SyncOptions) == 0)
   628  }
   629  
   630  type RetryStrategy struct {
   631  	// Limit is the maximum number of attempts when retrying a container
   632  	Limit int64 `json:"limit,omitempty" protobuf:"bytes,1,opt,name=limit"`
   633  
   634  	// Backoff is a backoff strategy
   635  	Backoff *Backoff `json:"backoff,omitempty" protobuf:"bytes,2,opt,name=backoff,casttype=Backoff"`
   636  }
   637  
   638  func parseStringToDuration(durationString string) (time.Duration, error) {
   639  	var suspendDuration time.Duration
   640  	// If no units are attached, treat as seconds
   641  	if val, err := strconv.Atoi(durationString); err == nil {
   642  		suspendDuration = time.Duration(val) * time.Second
   643  	} else if duration, err := time.ParseDuration(durationString); err == nil {
   644  		suspendDuration = duration
   645  	} else {
   646  		return 0, fmt.Errorf("unable to parse %s as a duration", durationString)
   647  	}
   648  	return suspendDuration, nil
   649  }
   650  
   651  func (r *RetryStrategy) NextRetryAt(lastAttempt time.Time, retryCounts int64) (time.Time, error) {
   652  	maxDuration := common.DefaultSyncRetryMaxDuration
   653  	duration := common.DefaultSyncRetryDuration
   654  	factor := common.DefaultSyncRetryFactor
   655  	var err error
   656  	if r.Backoff != nil {
   657  		if r.Backoff.Duration != "" {
   658  			if duration, err = parseStringToDuration(r.Backoff.Duration); err != nil {
   659  				return time.Time{}, err
   660  			}
   661  		}
   662  		if r.Backoff.MaxDuration != "" {
   663  			if maxDuration, err = parseStringToDuration(r.Backoff.MaxDuration); err != nil {
   664  				return time.Time{}, err
   665  			}
   666  		}
   667  		if r.Backoff.Factor != nil {
   668  			factor = *r.Backoff.Factor
   669  		}
   670  
   671  	}
   672  	// Formula: timeToWait = duration * factor^retry_number
   673  	// Note that timeToWait should equal to duration for the first retry attempt.
   674  	timeToWait := duration * time.Duration(math.Pow(float64(factor), float64(retryCounts)))
   675  	if maxDuration > 0 {
   676  		timeToWait = time.Duration(math.Min(float64(maxDuration), float64(timeToWait)))
   677  	}
   678  	return lastAttempt.Add(timeToWait), nil
   679  }
   680  
   681  // Backoff is a backoff strategy to use within retryStrategy
   682  type Backoff struct {
   683  	// Duration is the amount to back off. Default unit is seconds, but could also be a duration (e.g. "2m", "1h")
   684  	Duration string `json:"duration,omitempty" protobuf:"bytes,1,opt,name=duration"`
   685  	// Factor is a factor to multiply the base duration after each failed retry
   686  	Factor *int64 `json:"factor,omitempty" protobuf:"bytes,2,name=factor"`
   687  	// MaxDuration is the maximum amount of time allowed for the backoff strategy
   688  	MaxDuration string `json:"maxDuration,omitempty" protobuf:"bytes,3,opt,name=maxDuration"`
   689  }
   690  
   691  // SyncPolicyAutomated controls the behavior of an automated sync
   692  type SyncPolicyAutomated struct {
   693  	// Prune will prune resources automatically as part of automated sync (default: false)
   694  	Prune bool `json:"prune,omitempty" protobuf:"bytes,1,opt,name=prune"`
   695  	// SelfHeal enables auto-syncing if  (default: false)
   696  	SelfHeal bool `json:"selfHeal,omitempty" protobuf:"bytes,2,opt,name=selfHeal"`
   697  	// AllowEmpty allows apps have zero live resources (default: false)
   698  	AllowEmpty bool `json:"allowEmpty,omitempty" protobuf:"bytes,3,opt,name=allowEmpty"`
   699  }
   700  
   701  // SyncStrategy controls the manner in which a sync is performed
   702  type SyncStrategy struct {
   703  	// Apply will perform a `kubectl apply` to perform the sync.
   704  	Apply *SyncStrategyApply `json:"apply,omitempty" protobuf:"bytes,1,opt,name=apply"`
   705  	// Hook will submit any referenced resources to perform the sync. This is the default strategy
   706  	Hook *SyncStrategyHook `json:"hook,omitempty" protobuf:"bytes,2,opt,name=hook"`
   707  }
   708  
   709  func (m *SyncStrategy) Force() bool {
   710  	if m == nil {
   711  		return false
   712  	} else if m.Apply != nil {
   713  		return m.Apply.Force
   714  	} else if m.Hook != nil {
   715  		return m.Hook.Force
   716  	} else {
   717  		return false
   718  	}
   719  }
   720  
   721  // SyncStrategyApply uses `kubectl apply` to perform the apply
   722  type SyncStrategyApply struct {
   723  	// Force indicates whether or not to supply the --force flag to `kubectl apply`.
   724  	// The --force flag deletes and re-create the resource, when PATCH encounters conflict and has
   725  	// retried for 5 times.
   726  	Force bool `json:"force,omitempty" protobuf:"bytes,1,opt,name=force"`
   727  }
   728  
   729  // SyncStrategyHook will perform a sync using hooks annotations.
   730  // If no hook annotation is specified falls back to `kubectl apply`.
   731  type SyncStrategyHook struct {
   732  	// Embed SyncStrategyApply type to inherit any `apply` options
   733  	// +optional
   734  	SyncStrategyApply `json:",inline" protobuf:"bytes,1,opt,name=syncStrategyApply"`
   735  }
   736  
   737  // data about a specific revision within a repo
   738  type RevisionMetadata struct {
   739  	// who authored this revision,
   740  	// typically their name and email, e.g. "John Doe <john_doe@my-company.com>",
   741  	// but might not match this example
   742  	Author string `json:"author,omitempty" protobuf:"bytes,1,opt,name=author"`
   743  	// when the revision was authored
   744  	Date metav1.Time `json:"date" protobuf:"bytes,2,opt,name=date"`
   745  	// tags on the revision,
   746  	// note - tags can move from one revision to another
   747  	Tags []string `json:"tags,omitempty" protobuf:"bytes,3,opt,name=tags"`
   748  	// the message associated with the revision,
   749  	// probably the commit message,
   750  	// this is truncated to the first newline or 64 characters (which ever comes first)
   751  	Message string `json:"message,omitempty" protobuf:"bytes,4,opt,name=message"`
   752  	// If revision was signed with GPG, and signature verification is enabled,
   753  	// this contains a hint on the signer
   754  	SignatureInfo string `json:"signatureInfo,omitempty" protobuf:"bytes,5,opt,name=signatureInfo"`
   755  }
   756  
   757  // SyncOperationResult represent result of sync operation
   758  type SyncOperationResult struct {
   759  	// Resources holds the sync result of each individual resource
   760  	Resources ResourceResults `json:"resources,omitempty" protobuf:"bytes,1,opt,name=resources"`
   761  	// Revision holds the revision of the sync
   762  	Revision string `json:"revision" protobuf:"bytes,2,opt,name=revision"`
   763  	// Source records the application source information of the sync, used for comparing auto-sync
   764  	Source ApplicationSource `json:"source,omitempty" protobuf:"bytes,3,opt,name=source"`
   765  }
   766  
   767  // ResourceResult holds the operation result details of a specific resource
   768  type ResourceResult struct {
   769  	Group     string `json:"group" protobuf:"bytes,1,opt,name=group"`
   770  	Version   string `json:"version" protobuf:"bytes,2,opt,name=version"`
   771  	Kind      string `json:"kind" protobuf:"bytes,3,opt,name=kind"`
   772  	Namespace string `json:"namespace" protobuf:"bytes,4,opt,name=namespace"`
   773  	Name      string `json:"name" protobuf:"bytes,5,opt,name=name"`
   774  	// the final result of the sync, this is be empty if the resources is yet to be applied/pruned and is always zero-value for hooks
   775  	Status synccommon.ResultCode `json:"status,omitempty" protobuf:"bytes,6,opt,name=status"`
   776  	// message for the last sync OR operation
   777  	Message string `json:"message,omitempty" protobuf:"bytes,7,opt,name=message"`
   778  	// the type of the hook, empty for non-hook resources
   779  	HookType synccommon.HookType `json:"hookType,omitempty" protobuf:"bytes,8,opt,name=hookType"`
   780  	// the state of any operation associated with this resource OR hook
   781  	// note: can contain values for non-hook resources
   782  	HookPhase synccommon.OperationPhase `json:"hookPhase,omitempty" protobuf:"bytes,9,opt,name=hookPhase"`
   783  	// indicates the particular phase of the sync that this is for
   784  	SyncPhase synccommon.SyncPhase `json:"syncPhase,omitempty" protobuf:"bytes,10,opt,name=syncPhase"`
   785  }
   786  
   787  func (r *ResourceResult) GroupVersionKind() schema.GroupVersionKind {
   788  	return schema.GroupVersionKind{
   789  		Group:   r.Group,
   790  		Version: r.Version,
   791  		Kind:    r.Kind,
   792  	}
   793  }
   794  
   795  type ResourceResults []*ResourceResult
   796  
   797  func (r ResourceResults) Find(group string, kind string, namespace string, name string, phase synccommon.SyncPhase) (int, *ResourceResult) {
   798  	for i, res := range r {
   799  		if res.Group == group && res.Kind == kind && res.Namespace == namespace && res.Name == name && res.SyncPhase == phase {
   800  			return i, res
   801  		}
   802  	}
   803  	return 0, nil
   804  }
   805  
   806  func (r ResourceResults) PruningRequired() (num int) {
   807  	for _, res := range r {
   808  		if res.Status == synccommon.ResultCodePruneSkipped {
   809  			num++
   810  		}
   811  	}
   812  	return num
   813  }
   814  
   815  // RevisionHistory contains information relevant to an application deployment
   816  type RevisionHistory struct {
   817  	// Revision holds the revision of the sync
   818  	Revision string `json:"revision" protobuf:"bytes,2,opt,name=revision"`
   819  	// DeployedAt holds the time the deployment completed
   820  	DeployedAt metav1.Time `json:"deployedAt" protobuf:"bytes,4,opt,name=deployedAt"`
   821  	// ID is an auto incrementing identifier of the RevisionHistory
   822  	ID     int64             `json:"id" protobuf:"bytes,5,opt,name=id"`
   823  	Source ApplicationSource `json:"source,omitempty" protobuf:"bytes,6,opt,name=source"`
   824  	// DeployStartedAt holds the time the deployment started
   825  	DeployStartedAt *metav1.Time `json:"deployStartedAt,omitempty" protobuf:"bytes,7,opt,name=deployStartedAt"`
   826  }
   827  
   828  // ApplicationWatchEvent contains information about application change.
   829  type ApplicationWatchEvent struct {
   830  	Type watch.EventType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=k8s.io/apimachinery/pkg/watch.EventType"`
   831  
   832  	// Application is:
   833  	//  * If Type is Added or Modified: the new state of the object.
   834  	//  * If Type is Deleted: the state of the object immediately before deletion.
   835  	//  * If Type is Error: *api.Status is recommended; other types may make sense
   836  	//    depending on context.
   837  	Application Application `json:"application" protobuf:"bytes,2,opt,name=application"`
   838  }
   839  
   840  // ApplicationList is list of Application resources
   841  // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
   842  type ApplicationList struct {
   843  	metav1.TypeMeta `json:",inline"`
   844  	metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
   845  	Items           []Application `json:"items" protobuf:"bytes,2,rep,name=items"`
   846  }
   847  
   848  // ComponentParameter contains information about component parameter value
   849  type ComponentParameter struct {
   850  	Component string `json:"component,omitempty" protobuf:"bytes,1,opt,name=component"`
   851  	Name      string `json:"name" protobuf:"bytes,2,opt,name=name"`
   852  	Value     string `json:"value" protobuf:"bytes,3,opt,name=value"`
   853  }
   854  
   855  // SyncStatusCode is a type which represents possible comparison results
   856  type SyncStatusCode string
   857  
   858  // Possible comparison results
   859  const (
   860  	SyncStatusCodeUnknown   SyncStatusCode = "Unknown"
   861  	SyncStatusCodeSynced    SyncStatusCode = "Synced"
   862  	SyncStatusCodeOutOfSync SyncStatusCode = "OutOfSync"
   863  )
   864  
   865  // ApplicationConditionType represents type of application condition. Type name has following convention:
   866  // prefix "Error" means error condition
   867  // prefix "Warning" means warning condition
   868  // prefix "Info" means informational condition
   869  type ApplicationConditionType = string
   870  
   871  const (
   872  	// ApplicationConditionDeletionError indicates that controller failed to delete application
   873  	ApplicationConditionDeletionError = "DeletionError"
   874  	// ApplicationConditionInvalidSpecError indicates that application source is invalid
   875  	ApplicationConditionInvalidSpecError = "InvalidSpecError"
   876  	// ApplicationConditionComparisonError indicates controller failed to compare application state
   877  	ApplicationConditionComparisonError = "ComparisonError"
   878  	// ApplicationConditionSyncError indicates controller failed to automatically sync the application
   879  	ApplicationConditionSyncError = "SyncError"
   880  	// ApplicationConditionUnknownError indicates an unknown controller error
   881  	ApplicationConditionUnknownError = "UnknownError"
   882  	// ApplicationConditionSharedResourceWarning indicates that controller detected resources which belongs to more than one application
   883  	ApplicationConditionSharedResourceWarning = "SharedResourceWarning"
   884  	// ApplicationConditionRepeatedResourceWarning indicates that application source has resource with same Group, Kind, Name, Namespace multiple times
   885  	ApplicationConditionRepeatedResourceWarning = "RepeatedResourceWarning"
   886  	// ApplicationConditionExcludedResourceWarning indicates that application has resource which is configured to be excluded
   887  	ApplicationConditionExcludedResourceWarning = "ExcludedResourceWarning"
   888  	// ApplicationConditionOrphanedResourceWarning indicates that application has orphaned resources
   889  	ApplicationConditionOrphanedResourceWarning = "OrphanedResourceWarning"
   890  )
   891  
   892  // ApplicationCondition contains details about current application condition
   893  type ApplicationCondition struct {
   894  	// Type is an application condition type
   895  	Type ApplicationConditionType `json:"type" protobuf:"bytes,1,opt,name=type"`
   896  	// Message contains human-readable message indicating details about condition
   897  	Message string `json:"message" protobuf:"bytes,2,opt,name=message"`
   898  	// LastTransitionTime is the time the condition was first observed.
   899  	LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"`
   900  }
   901  
   902  // ComparedTo contains application source and target which was used for resources comparison
   903  type ComparedTo struct {
   904  	Source      ApplicationSource      `json:"source" protobuf:"bytes,1,opt,name=source"`
   905  	Destination ApplicationDestination `json:"destination" protobuf:"bytes,2,opt,name=destination"`
   906  }
   907  
   908  // SyncStatus is a comparison result of application spec and deployed application.
   909  type SyncStatus struct {
   910  	Status     SyncStatusCode `json:"status" protobuf:"bytes,1,opt,name=status,casttype=SyncStatusCode"`
   911  	ComparedTo ComparedTo     `json:"comparedTo,omitempty" protobuf:"bytes,2,opt,name=comparedTo"`
   912  	Revision   string         `json:"revision,omitempty" protobuf:"bytes,3,opt,name=revision"`
   913  }
   914  
   915  type HealthStatus struct {
   916  	Status  health.HealthStatusCode `json:"status,omitempty" protobuf:"bytes,1,opt,name=status"`
   917  	Message string                  `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"`
   918  }
   919  
   920  // InfoItem contains human readable information about object
   921  type InfoItem struct {
   922  	// Name is a human readable title for this piece of information.
   923  	Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
   924  	// Value is human readable content.
   925  	Value string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"`
   926  }
   927  
   928  // ResourceNetworkingInfo holds networking resource related information
   929  type ResourceNetworkingInfo struct {
   930  	TargetLabels map[string]string        `json:"targetLabels,omitempty" protobuf:"bytes,1,opt,name=targetLabels"`
   931  	TargetRefs   []ResourceRef            `json:"targetRefs,omitempty" protobuf:"bytes,2,opt,name=targetRefs"`
   932  	Labels       map[string]string        `json:"labels,omitempty" protobuf:"bytes,3,opt,name=labels"`
   933  	Ingress      []v1.LoadBalancerIngress `json:"ingress,omitempty" protobuf:"bytes,4,opt,name=ingress"`
   934  	// ExternalURLs holds list of URLs which should be available externally. List is populated for ingress resources using rules hostnames.
   935  	ExternalURLs []string `json:"externalURLs,omitempty" protobuf:"bytes,5,opt,name=externalURLs"`
   936  }
   937  
   938  // ApplicationTree holds nodes which belongs to the application
   939  type ApplicationTree struct {
   940  	// Nodes contains list of nodes which either directly managed by the application and children of directly managed nodes.
   941  	Nodes []ResourceNode `json:"nodes,omitempty" protobuf:"bytes,1,rep,name=nodes"`
   942  	// OrphanedNodes contains if or orphaned nodes: nodes which are not managed by the app but in the same namespace. List is populated only if orphaned resources enabled in app project.
   943  	OrphanedNodes []ResourceNode `json:"orphanedNodes,omitempty" protobuf:"bytes,2,rep,name=orphanedNodes"`
   944  }
   945  
   946  // Normalize sorts application tree nodes and hosts. The persistent order allows to
   947  // effectively compare previously cached app tree and allows to unnecessary Redis requests.
   948  func (t *ApplicationTree) Normalize() {
   949  	sort.Slice(t.Nodes, func(i, j int) bool {
   950  		return t.Nodes[i].FullName() < t.Nodes[j].FullName()
   951  	})
   952  	sort.Slice(t.OrphanedNodes, func(i, j int) bool {
   953  		return t.OrphanedNodes[i].FullName() < t.OrphanedNodes[j].FullName()
   954  	})
   955  }
   956  
   957  type ApplicationSummary struct {
   958  	// ExternalURLs holds all external URLs of application child resources.
   959  	ExternalURLs []string `json:"externalURLs,omitempty" protobuf:"bytes,1,opt,name=externalURLs"`
   960  	// Images holds all images of application child resources.
   961  	Images []string `json:"images,omitempty" protobuf:"bytes,2,opt,name=images"`
   962  }
   963  
   964  func (t *ApplicationTree) FindNode(group string, kind string, namespace string, name string) *ResourceNode {
   965  	for _, n := range append(t.Nodes, t.OrphanedNodes...) {
   966  		if n.Group == group && n.Kind == kind && n.Namespace == namespace && n.Name == name {
   967  			return &n
   968  		}
   969  	}
   970  	return nil
   971  }
   972  
   973  func (t *ApplicationTree) GetSummary() ApplicationSummary {
   974  	urlsSet := make(map[string]bool)
   975  	imagesSet := make(map[string]bool)
   976  	for _, node := range t.Nodes {
   977  		if node.NetworkingInfo != nil {
   978  			for _, url := range node.NetworkingInfo.ExternalURLs {
   979  				urlsSet[url] = true
   980  			}
   981  		}
   982  		for _, image := range node.Images {
   983  			imagesSet[image] = true
   984  		}
   985  	}
   986  	urls := make([]string, 0)
   987  	for url := range urlsSet {
   988  		urls = append(urls, url)
   989  	}
   990  	sort.Slice(urls, func(i, j int) bool {
   991  		return urls[i] < urls[j]
   992  	})
   993  	images := make([]string, 0)
   994  	for image := range imagesSet {
   995  		images = append(images, image)
   996  	}
   997  	sort.Slice(images, func(i, j int) bool {
   998  		return images[i] < images[j]
   999  	})
  1000  	return ApplicationSummary{ExternalURLs: urls, Images: images}
  1001  }
  1002  
  1003  // ResourceRef includes fields which unique identify resource
  1004  type ResourceRef struct {
  1005  	Group     string `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"`
  1006  	Version   string `json:"version,omitempty" protobuf:"bytes,2,opt,name=version"`
  1007  	Kind      string `json:"kind,omitempty" protobuf:"bytes,3,opt,name=kind"`
  1008  	Namespace string `json:"namespace,omitempty" protobuf:"bytes,4,opt,name=namespace"`
  1009  	Name      string `json:"name,omitempty" protobuf:"bytes,5,opt,name=name"`
  1010  	UID       string `json:"uid,omitempty" protobuf:"bytes,6,opt,name=uid"`
  1011  }
  1012  
  1013  // ResourceNode contains information about live resource and its children
  1014  type ResourceNode struct {
  1015  	ResourceRef     `json:",inline" protobuf:"bytes,1,opt,name=resourceRef"`
  1016  	ParentRefs      []ResourceRef           `json:"parentRefs,omitempty" protobuf:"bytes,2,opt,name=parentRefs"`
  1017  	Info            []InfoItem              `json:"info,omitempty" protobuf:"bytes,3,opt,name=info"`
  1018  	NetworkingInfo  *ResourceNetworkingInfo `json:"networkingInfo,omitempty" protobuf:"bytes,4,opt,name=networkingInfo"`
  1019  	ResourceVersion string                  `json:"resourceVersion,omitempty" protobuf:"bytes,5,opt,name=resourceVersion"`
  1020  	Images          []string                `json:"images,omitempty" protobuf:"bytes,6,opt,name=images"`
  1021  	Health          *HealthStatus           `json:"health,omitempty" protobuf:"bytes,7,opt,name=health"`
  1022  	CreatedAt       *metav1.Time            `json:"createdAt,omitempty" protobuf:"bytes,8,opt,name=createdAt"`
  1023  }
  1024  
  1025  // FullName returns node full name
  1026  func (n *ResourceNode) FullName() string {
  1027  	return fmt.Sprintf("%s/%s/%s/%s", n.Group, n.Kind, n.Namespace, n.Name)
  1028  }
  1029  
  1030  func (n *ResourceNode) GroupKindVersion() schema.GroupVersionKind {
  1031  	return schema.GroupVersionKind{
  1032  		Group:   n.Group,
  1033  		Version: n.Version,
  1034  		Kind:    n.Kind,
  1035  	}
  1036  }
  1037  
  1038  // ResourceStatus holds the current sync and health status of a resource
  1039  type ResourceStatus struct {
  1040  	Group           string         `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"`
  1041  	Version         string         `json:"version,omitempty" protobuf:"bytes,2,opt,name=version"`
  1042  	Kind            string         `json:"kind,omitempty" protobuf:"bytes,3,opt,name=kind"`
  1043  	Namespace       string         `json:"namespace,omitempty" protobuf:"bytes,4,opt,name=namespace"`
  1044  	Name            string         `json:"name,omitempty" protobuf:"bytes,5,opt,name=name"`
  1045  	Status          SyncStatusCode `json:"status,omitempty" protobuf:"bytes,6,opt,name=status"`
  1046  	Health          *HealthStatus  `json:"health,omitempty" protobuf:"bytes,7,opt,name=health"`
  1047  	Hook            bool           `json:"hook,omitempty" protobuf:"bytes,8,opt,name=hook"`
  1048  	RequiresPruning bool           `json:"requiresPruning,omitempty" protobuf:"bytes,9,opt,name=requiresPruning"`
  1049  }
  1050  
  1051  func (r *ResourceStatus) GroupVersionKind() schema.GroupVersionKind {
  1052  	return schema.GroupVersionKind{Group: r.Group, Version: r.Version, Kind: r.Kind}
  1053  }
  1054  
  1055  // ResourceDiff holds the diff of a live and target resource object
  1056  type ResourceDiff struct {
  1057  	Group     string `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"`
  1058  	Kind      string `json:"kind,omitempty" protobuf:"bytes,2,opt,name=kind"`
  1059  	Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"`
  1060  	Name      string `json:"name,omitempty" protobuf:"bytes,4,opt,name=name"`
  1061  	// TargetState contains the JSON serialized resource manifest defined in the Git/Helm
  1062  	TargetState string `json:"targetState,omitempty" protobuf:"bytes,5,opt,name=targetState"`
  1063  	// TargetState contains the JSON live resource manifest
  1064  	LiveState string `json:"liveState,omitempty" protobuf:"bytes,6,opt,name=liveState"`
  1065  	// Diff contains the JSON patch between target and live resource
  1066  	// Deprecated: use NormalizedLiveState and PredictedLiveState to render the difference
  1067  	Diff string `json:"diff,omitempty" protobuf:"bytes,7,opt,name=diff"`
  1068  	Hook bool   `json:"hook,omitempty" protobuf:"bytes,8,opt,name=hook"`
  1069  	// NormalizedLiveState contains JSON serialized live resource state with applied normalizations
  1070  	NormalizedLiveState string `json:"normalizedLiveState,omitempty" protobuf:"bytes,9,opt,name=normalizedLiveState"`
  1071  	// PredictedLiveState contains JSON serialized resource state that is calculated based on normalized and target resource state
  1072  	PredictedLiveState string `json:"predictedLiveState,omitempty" protobuf:"bytes,10,opt,name=predictedLiveState"`
  1073  }
  1074  
  1075  // FullName returns full name of a node that was used for diffing
  1076  func (r *ResourceDiff) FullName() string {
  1077  	return fmt.Sprintf("%s/%s/%s/%s", r.Group, r.Kind, r.Namespace, r.Name)
  1078  }
  1079  
  1080  // ConnectionStatus represents connection status
  1081  type ConnectionStatus = string
  1082  
  1083  const (
  1084  	ConnectionStatusSuccessful = "Successful"
  1085  	ConnectionStatusFailed     = "Failed"
  1086  	ConnectionStatusUnknown    = "Unknown"
  1087  )
  1088  
  1089  // ConnectionState contains information about remote resource connection state
  1090  type ConnectionState struct {
  1091  	Status     ConnectionStatus `json:"status" protobuf:"bytes,1,opt,name=status"`
  1092  	Message    string           `json:"message" protobuf:"bytes,2,opt,name=message"`
  1093  	ModifiedAt *metav1.Time     `json:"attemptedAt" protobuf:"bytes,3,opt,name=attemptedAt"`
  1094  }
  1095  
  1096  // Cluster is the definition of a cluster resource
  1097  type Cluster struct {
  1098  	// ID is an internal field cluster identifier. Not exposed via API.
  1099  	ID string `json:"-"`
  1100  	// Server is the API server URL of the Kubernetes cluster
  1101  	Server string `json:"server" protobuf:"bytes,1,opt,name=server"`
  1102  	// Name of the cluster. If omitted, will use the server address
  1103  	Name string `json:"name" protobuf:"bytes,2,opt,name=name"`
  1104  	// Config holds cluster information for connecting to a cluster
  1105  	Config ClusterConfig `json:"config" protobuf:"bytes,3,opt,name=config"`
  1106  	// DEPRECATED: use Info.ConnectionState field instead.
  1107  	// ConnectionState contains information about cluster connection state
  1108  	ConnectionState ConnectionState `json:"connectionState,omitempty" protobuf:"bytes,4,opt,name=connectionState"`
  1109  	// DEPRECATED: use Info.ServerVersion field instead.
  1110  	// The server version
  1111  	ServerVersion string `json:"serverVersion,omitempty" protobuf:"bytes,5,opt,name=serverVersion"`
  1112  	// Holds list of namespaces which are accessible in that cluster. Cluster level resources would be ignored if namespace list is not empty.
  1113  	Namespaces []string `json:"namespaces,omitempty" protobuf:"bytes,6,opt,name=namespaces"`
  1114  	// RefreshRequestedAt holds time when cluster cache refresh has been requested
  1115  	RefreshRequestedAt *metav1.Time `json:"refreshRequestedAt,omitempty" protobuf:"bytes,7,opt,name=refreshRequestedAt"`
  1116  	// Holds information about cluster cache
  1117  	Info ClusterInfo `json:"info,omitempty" protobuf:"bytes,8,opt,name=info"`
  1118  	// Shard contains optional shard number. Calculated on the fly by the application controller if not specified.
  1119  	Shard *int64 `json:"shard,omitempty" protobuf:"bytes,9,opt,name=shard"`
  1120  }
  1121  
  1122  func (c *Cluster) Equals(other *Cluster) bool {
  1123  	if c.Server != other.Server {
  1124  		return false
  1125  	}
  1126  	if c.Name != other.Name {
  1127  		return false
  1128  	}
  1129  	if strings.Join(c.Namespaces, ",") != strings.Join(other.Namespaces, ",") {
  1130  		return false
  1131  	}
  1132  	var shard int64 = -1
  1133  	if c.Shard != nil {
  1134  		shard = *c.Shard
  1135  	}
  1136  	var otherShard int64 = -1
  1137  	if other.Shard != nil {
  1138  		otherShard = *other.Shard
  1139  	}
  1140  	if shard != otherShard {
  1141  		return false
  1142  	}
  1143  	return reflect.DeepEqual(c.Config, other.Config)
  1144  }
  1145  
  1146  type ClusterInfo struct {
  1147  	ConnectionState   ConnectionState  `json:"connectionState,omitempty" protobuf:"bytes,1,opt,name=connectionState"`
  1148  	ServerVersion     string           `json:"serverVersion,omitempty" protobuf:"bytes,2,opt,name=serverVersion"`
  1149  	CacheInfo         ClusterCacheInfo `json:"cacheInfo,omitempty" protobuf:"bytes,3,opt,name=cacheInfo"`
  1150  	ApplicationsCount int64            `json:"applicationsCount" protobuf:"bytes,4,opt,name=applicationsCount"`
  1151  }
  1152  
  1153  type ClusterCacheInfo struct {
  1154  	// ResourcesCount holds number of observed Kubernetes resources
  1155  	ResourcesCount int64 `json:"resourcesCount,omitempty" protobuf:"bytes,1,opt,name=resourcesCount"`
  1156  	// APIsCount holds number of observed Kubernetes API count
  1157  	APIsCount int64 `json:"apisCount,omitempty" protobuf:"bytes,2,opt,name=apisCount"`
  1158  	// LastCacheSyncTime holds time of most recent cache synchronization
  1159  	LastCacheSyncTime *metav1.Time `json:"lastCacheSyncTime,omitempty" protobuf:"bytes,3,opt,name=lastCacheSyncTime"`
  1160  }
  1161  
  1162  // ClusterList is a collection of Clusters.
  1163  type ClusterList struct {
  1164  	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
  1165  	Items           []Cluster `json:"items" protobuf:"bytes,2,rep,name=items"`
  1166  }
  1167  
  1168  // AWSAuthConfig is an AWS IAM authentication configuration
  1169  type AWSAuthConfig struct {
  1170  	// ClusterName contains AWS cluster name
  1171  	ClusterName string `json:"clusterName,omitempty" protobuf:"bytes,1,opt,name=clusterName"`
  1172  
  1173  	// RoleARN contains optional role ARN. If set then AWS IAM Authenticator assume a role to perform cluster operations instead of the default AWS credential provider chain.
  1174  	RoleARN string `json:"roleARN,omitempty" protobuf:"bytes,2,opt,name=roleARN"`
  1175  }
  1176  
  1177  // ExecProviderConfig is config used to call an external command to perform cluster authentication
  1178  // See: https://godoc.org/k8s.io/client-go/tools/clientcmd/api#ExecConfig
  1179  type ExecProviderConfig struct {
  1180  	// Command to execute
  1181  	Command string `json:"command,omitempty" protobuf:"bytes,1,opt,name=command"`
  1182  
  1183  	// Arguments to pass to the command when executing it
  1184  	Args []string `json:"args,omitempty" protobuf:"bytes,2,rep,name=args"`
  1185  
  1186  	// Env defines additional environment variables to expose to the process
  1187  	Env map[string]string `json:"env,omitempty" protobuf:"bytes,3,opt,name=env"`
  1188  
  1189  	// Preferred input version of the ExecInfo
  1190  	APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,4,opt,name=apiVersion"`
  1191  
  1192  	// This text is shown to the user when the executable doesn't seem to be present
  1193  	InstallHint string `json:"installHint,omitempty" protobuf:"bytes,5,opt,name=installHint"`
  1194  }
  1195  
  1196  // ClusterConfig is the configuration attributes. This structure is subset of the go-client
  1197  // rest.Config with annotations added for marshalling.
  1198  type ClusterConfig struct {
  1199  	// Server requires Basic authentication
  1200  	Username string `json:"username,omitempty" protobuf:"bytes,1,opt,name=username"`
  1201  	Password string `json:"password,omitempty" protobuf:"bytes,2,opt,name=password"`
  1202  
  1203  	// Server requires Bearer authentication. This client will not attempt to use
  1204  	// refresh tokens for an OAuth2 flow.
  1205  	// TODO: demonstrate an OAuth2 compatible client.
  1206  	BearerToken string `json:"bearerToken,omitempty" protobuf:"bytes,3,opt,name=bearerToken"`
  1207  
  1208  	// TLSClientConfig contains settings to enable transport layer security
  1209  	TLSClientConfig `json:"tlsClientConfig" protobuf:"bytes,4,opt,name=tlsClientConfig"`
  1210  
  1211  	// AWSAuthConfig contains IAM authentication configuration
  1212  	AWSAuthConfig *AWSAuthConfig `json:"awsAuthConfig,omitempty" protobuf:"bytes,5,opt,name=awsAuthConfig"`
  1213  
  1214  	// ExecProviderConfig contains configuration for an exec provider
  1215  	ExecProviderConfig *ExecProviderConfig `json:"execProviderConfig,omitempty" protobuf:"bytes,6,opt,name=execProviderConfig"`
  1216  }
  1217  
  1218  // TLSClientConfig contains settings to enable transport layer security
  1219  type TLSClientConfig struct {
  1220  	// Server should be accessed without verifying the TLS certificate. For testing only.
  1221  	Insecure bool `json:"insecure" protobuf:"bytes,1,opt,name=insecure"`
  1222  	// ServerName is passed to the server for SNI and is used in the client to check server
  1223  	// certificates against. If ServerName is empty, the hostname used to contact the
  1224  	// server is used.
  1225  	ServerName string `json:"serverName,omitempty" protobuf:"bytes,2,opt,name=serverName"`
  1226  	// CertData holds PEM-encoded bytes (typically read from a client certificate file).
  1227  	// CertData takes precedence over CertFile
  1228  	CertData []byte `json:"certData,omitempty" protobuf:"bytes,3,opt,name=certData"`
  1229  	// KeyData holds PEM-encoded bytes (typically read from a client certificate key file).
  1230  	// KeyData takes precedence over KeyFile
  1231  	KeyData []byte `json:"keyData,omitempty" protobuf:"bytes,4,opt,name=keyData"`
  1232  	// CAData holds PEM-encoded bytes (typically read from a root certificates bundle).
  1233  	// CAData takes precedence over CAFile
  1234  	CAData []byte `json:"caData,omitempty" protobuf:"bytes,5,opt,name=caData"`
  1235  }
  1236  
  1237  // KnownTypeField contains mapping between CRD field and known Kubernetes type
  1238  type KnownTypeField struct {
  1239  	Field string `json:"field,omitempty" protobuf:"bytes,1,opt,name=field"`
  1240  	Type  string `json:"type,omitempty" protobuf:"bytes,2,opt,name=type"`
  1241  }
  1242  
  1243  type OverrideIgnoreDiff struct {
  1244  	JSONPointers []string `json:"jsonPointers" protobuf:"bytes,1,rep,name=jSONPointers"`
  1245  }
  1246  
  1247  type rawResourceOverride struct {
  1248  	HealthLua         string           `json:"health.lua,omitempty"`
  1249  	Actions           string           `json:"actions,omitempty"`
  1250  	IgnoreDifferences string           `json:"ignoreDifferences,omitempty"`
  1251  	KnownTypeFields   []KnownTypeField `json:"knownTypeFields,omitempty"`
  1252  }
  1253  
  1254  // ResourceOverride holds configuration to customize resource diffing and health assessment
  1255  type ResourceOverride struct {
  1256  	HealthLua         string             `protobuf:"bytes,1,opt,name=healthLua"`
  1257  	Actions           string             `protobuf:"bytes,3,opt,name=actions"`
  1258  	IgnoreDifferences OverrideIgnoreDiff `protobuf:"bytes,2,opt,name=ignoreDifferences"`
  1259  	KnownTypeFields   []KnownTypeField   `protobuf:"bytes,4,opt,name=knownTypeFields"`
  1260  }
  1261  
  1262  func (s *ResourceOverride) UnmarshalJSON(data []byte) error {
  1263  	raw := &rawResourceOverride{}
  1264  	if err := json.Unmarshal(data, &raw); err != nil {
  1265  		return err
  1266  	}
  1267  	s.KnownTypeFields = raw.KnownTypeFields
  1268  	s.HealthLua = raw.HealthLua
  1269  	s.Actions = raw.Actions
  1270  	return yaml.Unmarshal([]byte(raw.IgnoreDifferences), &s.IgnoreDifferences)
  1271  }
  1272  
  1273  func (s ResourceOverride) MarshalJSON() ([]byte, error) {
  1274  	ignoreDifferencesData, err := yaml.Marshal(s.IgnoreDifferences)
  1275  	if err != nil {
  1276  		return nil, err
  1277  	}
  1278  	raw := &rawResourceOverride{s.HealthLua, s.Actions, string(ignoreDifferencesData), s.KnownTypeFields}
  1279  	return json.Marshal(raw)
  1280  }
  1281  
  1282  func (o *ResourceOverride) GetActions() (ResourceActions, error) {
  1283  	var actions ResourceActions
  1284  	err := yaml.Unmarshal([]byte(o.Actions), &actions)
  1285  	if err != nil {
  1286  		return actions, err
  1287  	}
  1288  	return actions, nil
  1289  }
  1290  
  1291  type ResourceActions struct {
  1292  	ActionDiscoveryLua string                     `json:"discovery.lua,omitempty" yaml:"discovery.lua,omitempty" protobuf:"bytes,1,opt,name=actionDiscoveryLua"`
  1293  	Definitions        []ResourceActionDefinition `json:"definitions,omitempty" protobuf:"bytes,2,rep,name=definitions"`
  1294  }
  1295  
  1296  type ResourceActionDefinition struct {
  1297  	Name      string `json:"name" protobuf:"bytes,1,opt,name=name"`
  1298  	ActionLua string `json:"action.lua" yaml:"action.lua" protobuf:"bytes,2,opt,name=actionLua"`
  1299  }
  1300  
  1301  type ResourceAction struct {
  1302  	Name     string                `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
  1303  	Params   []ResourceActionParam `json:"params,omitempty" protobuf:"bytes,2,rep,name=params"`
  1304  	Disabled bool                  `json:"disabled,omitempty" protobuf:"varint,3,opt,name=disabled"`
  1305  }
  1306  
  1307  type ResourceActionParam struct {
  1308  	Name    string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
  1309  	Value   string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"`
  1310  	Type    string `json:"type,omitempty" protobuf:"bytes,3,opt,name=type"`
  1311  	Default string `json:"default,omitempty" protobuf:"bytes,4,opt,name=default"`
  1312  }
  1313  
  1314  // RepoCreds holds a repository credentials definition
  1315  type RepoCreds struct {
  1316  	// URL is the URL that this credentials matches to
  1317  	URL string `json:"url" protobuf:"bytes,1,opt,name=url"`
  1318  	// Username for authenticating at the repo server
  1319  	Username string `json:"username,omitempty" protobuf:"bytes,2,opt,name=username"`
  1320  	// Password for authenticating at the repo server
  1321  	Password string `json:"password,omitempty" protobuf:"bytes,3,opt,name=password"`
  1322  	// SSH private key data for authenticating at the repo server (only Git repos)
  1323  	SSHPrivateKey string `json:"sshPrivateKey,omitempty" protobuf:"bytes,4,opt,name=sshPrivateKey"`
  1324  	// TLS client cert data for authenticating at the repo server
  1325  	TLSClientCertData string `json:"tlsClientCertData,omitempty" protobuf:"bytes,5,opt,name=tlsClientCertData"`
  1326  	// TLS client cert key for authenticating at the repo server
  1327  	TLSClientCertKey string `json:"tlsClientCertKey,omitempty" protobuf:"bytes,6,opt,name=tlsClientCertKey"`
  1328  }
  1329  
  1330  // Repository is a repository holding application configurations
  1331  type Repository struct {
  1332  	// URL of the repo
  1333  	Repo string `json:"repo" protobuf:"bytes,1,opt,name=repo"`
  1334  	// Username for authenticating at the repo server
  1335  	Username string `json:"username,omitempty" protobuf:"bytes,2,opt,name=username"`
  1336  	// Password for authenticating at the repo server
  1337  	Password string `json:"password,omitempty" protobuf:"bytes,3,opt,name=password"`
  1338  	// SSH private key data for authenticating at the repo server
  1339  	// only for Git repos
  1340  	SSHPrivateKey string `json:"sshPrivateKey,omitempty" protobuf:"bytes,4,opt,name=sshPrivateKey"`
  1341  	// Current state of repository server connecting
  1342  	ConnectionState ConnectionState `json:"connectionState,omitempty" protobuf:"bytes,5,opt,name=connectionState"`
  1343  	// InsecureIgnoreHostKey should not be used anymore, Insecure is favoured
  1344  	// only for Git repos
  1345  	InsecureIgnoreHostKey bool `json:"insecureIgnoreHostKey,omitempty" protobuf:"bytes,6,opt,name=insecureIgnoreHostKey"`
  1346  	// Whether the repo is insecure
  1347  	Insecure bool `json:"insecure,omitempty" protobuf:"bytes,7,opt,name=insecure"`
  1348  	// Whether git-lfs support should be enabled for this repo
  1349  	EnableLFS bool `json:"enableLfs,omitempty" protobuf:"bytes,8,opt,name=enableLfs"`
  1350  	// TLS client cert data for authenticating at the repo server
  1351  	TLSClientCertData string `json:"tlsClientCertData,omitempty" protobuf:"bytes,9,opt,name=tlsClientCertData"`
  1352  	// TLS client cert key for authenticating at the repo server
  1353  	TLSClientCertKey string `json:"tlsClientCertKey,omitempty" protobuf:"bytes,10,opt,name=tlsClientCertKey"`
  1354  	// type of the repo, maybe "git or "helm, "git" is assumed if empty or absent
  1355  	Type string `json:"type,omitempty" protobuf:"bytes,11,opt,name=type"`
  1356  	// only for Helm repos
  1357  	Name string `json:"name,omitempty" protobuf:"bytes,12,opt,name=name"`
  1358  	// Whether credentials were inherited from a credential set
  1359  	InheritedCreds bool `json:"inheritedCreds,omitempty" protobuf:"bytes,13,opt,name=inheritedCreds"`
  1360  	// Whether helm-oci support should be enabled for this repo
  1361  	EnableOCI bool `json:"enableOCI,omitempty" protobuf:"bytes,14,opt,name=enableOCI"`
  1362  }
  1363  
  1364  // IsInsecure returns true if receiver has been configured to skip server verification
  1365  func (repo *Repository) IsInsecure() bool {
  1366  	return repo.InsecureIgnoreHostKey || repo.Insecure
  1367  }
  1368  
  1369  // IsLFSEnabled returns true if LFS support is enabled on receiver
  1370  func (repo *Repository) IsLFSEnabled() bool {
  1371  	return repo.EnableLFS
  1372  }
  1373  
  1374  // HasCredentials returns true when the receiver has been configured any credentials
  1375  func (m *Repository) HasCredentials() bool {
  1376  	return m.Username != "" || m.Password != "" || m.SSHPrivateKey != "" || m.TLSClientCertData != ""
  1377  }
  1378  
  1379  func (repo *Repository) CopyCredentialsFromRepo(source *Repository) {
  1380  	if source != nil {
  1381  		if repo.Username == "" {
  1382  			repo.Username = source.Username
  1383  		}
  1384  		if repo.Password == "" {
  1385  			repo.Password = source.Password
  1386  		}
  1387  		if repo.SSHPrivateKey == "" {
  1388  			repo.SSHPrivateKey = source.SSHPrivateKey
  1389  		}
  1390  		if repo.TLSClientCertData == "" {
  1391  			repo.TLSClientCertData = source.TLSClientCertData
  1392  		}
  1393  		if repo.TLSClientCertKey == "" {
  1394  			repo.TLSClientCertKey = source.TLSClientCertKey
  1395  		}
  1396  	}
  1397  }
  1398  
  1399  // CopyCredentialsFrom copies all credentials from source to receiver
  1400  func (repo *Repository) CopyCredentialsFrom(source *RepoCreds) {
  1401  	if source != nil {
  1402  		if repo.Username == "" {
  1403  			repo.Username = source.Username
  1404  		}
  1405  		if repo.Password == "" {
  1406  			repo.Password = source.Password
  1407  		}
  1408  		if repo.SSHPrivateKey == "" {
  1409  			repo.SSHPrivateKey = source.SSHPrivateKey
  1410  		}
  1411  		if repo.TLSClientCertData == "" {
  1412  			repo.TLSClientCertData = source.TLSClientCertData
  1413  		}
  1414  		if repo.TLSClientCertKey == "" {
  1415  			repo.TLSClientCertKey = source.TLSClientCertKey
  1416  		}
  1417  	}
  1418  }
  1419  
  1420  func (repo *Repository) GetGitCreds() git.Creds {
  1421  	if repo == nil {
  1422  		return git.NopCreds{}
  1423  	}
  1424  	if repo.Username != "" && repo.Password != "" {
  1425  		return git.NewHTTPSCreds(repo.Username, repo.Password, repo.TLSClientCertData, repo.TLSClientCertKey, repo.IsInsecure())
  1426  	}
  1427  	if repo.SSHPrivateKey != "" {
  1428  		return git.NewSSHCreds(repo.SSHPrivateKey, getCAPath(repo.Repo), repo.IsInsecure())
  1429  	}
  1430  	return git.NopCreds{}
  1431  }
  1432  
  1433  func (repo *Repository) GetHelmCreds() helm.Creds {
  1434  	return helm.Creds{
  1435  		Username:           repo.Username,
  1436  		Password:           repo.Password,
  1437  		CAPath:             getCAPath(repo.Repo),
  1438  		CertData:           []byte(repo.TLSClientCertData),
  1439  		KeyData:            []byte(repo.TLSClientCertKey),
  1440  		InsecureSkipVerify: repo.Insecure,
  1441  	}
  1442  }
  1443  
  1444  func getCAPath(repoURL string) string {
  1445  	if git.IsHTTPSURL(repoURL) {
  1446  		if parsedURL, err := url.Parse(repoURL); err == nil {
  1447  			if caPath, err := cert.GetCertBundlePathForRepository(parsedURL.Host); err == nil {
  1448  				return caPath
  1449  			} else {
  1450  				log.Warnf("Could not get cert bundle path for host '%s'", parsedURL.Host)
  1451  			}
  1452  		} else {
  1453  			// We don't fail if we cannot parse the URL, but log a warning in that
  1454  			// case. And we execute the command in a verbatim way.
  1455  			log.Warnf("Could not parse repo URL '%s'", repoURL)
  1456  		}
  1457  	}
  1458  	return ""
  1459  }
  1460  
  1461  // CopySettingsFrom copies all repository settings from source to receiver
  1462  func (m *Repository) CopySettingsFrom(source *Repository) {
  1463  	if source != nil {
  1464  		m.EnableLFS = source.EnableLFS
  1465  		m.InsecureIgnoreHostKey = source.InsecureIgnoreHostKey
  1466  		m.Insecure = source.Insecure
  1467  		m.InheritedCreds = source.InheritedCreds
  1468  	}
  1469  }
  1470  
  1471  type Repositories []*Repository
  1472  
  1473  func (r Repositories) Filter(predicate func(r *Repository) bool) Repositories {
  1474  	var res Repositories
  1475  	for i := range r {
  1476  		repo := r[i]
  1477  		if predicate(repo) {
  1478  			res = append(res, repo)
  1479  		}
  1480  	}
  1481  	return res
  1482  }
  1483  
  1484  // RepositoryList is a collection of Repositories.
  1485  type RepositoryList struct {
  1486  	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
  1487  	Items           Repositories `json:"items" protobuf:"bytes,2,rep,name=items"`
  1488  }
  1489  
  1490  // RepositoryList is a collection of Repositories.
  1491  type RepoCredsList struct {
  1492  	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
  1493  	Items           []RepoCreds `json:"items" protobuf:"bytes,2,rep,name=items"`
  1494  }
  1495  
  1496  // A RepositoryCertificate is either SSH known hosts entry or TLS certificate
  1497  type RepositoryCertificate struct {
  1498  	// Name of the server the certificate is intended for
  1499  	ServerName string `json:"serverName" protobuf:"bytes,1,opt,name=serverName"`
  1500  	// Type of certificate - currently "https" or "ssh"
  1501  	CertType string `json:"certType" protobuf:"bytes,2,opt,name=certType"`
  1502  	// The sub type of the cert, i.e. "ssh-rsa"
  1503  	CertSubType string `json:"certSubType" protobuf:"bytes,3,opt,name=certSubType"`
  1504  	// Actual certificate data, protocol dependent
  1505  	CertData []byte `json:"certData" protobuf:"bytes,4,opt,name=certData"`
  1506  	// Additional certificate info (e.g. SSH fingerprint, X509 CommonName)
  1507  	CertInfo string `json:"certInfo" protobuf:"bytes,5,opt,name=certInfo"`
  1508  }
  1509  
  1510  // RepositoryCertificateList is a collection of RepositoryCertificates
  1511  type RepositoryCertificateList struct {
  1512  	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
  1513  	// List of certificates to be processed
  1514  	Items []RepositoryCertificate `json:"items" protobuf:"bytes,2,rep,name=items"`
  1515  }
  1516  
  1517  // GnuPGPublicKey is a representation of a GnuPG public key
  1518  type GnuPGPublicKey struct {
  1519  	// KeyID in hexadecimal string format
  1520  	KeyID string `json:"keyID" protobuf:"bytes,1,opt,name=keyID"`
  1521  	// Fingerprint of the key
  1522  	Fingerprint string `json:"fingerprint,omitempty" protobuf:"bytes,2,opt,name=fingerprint"`
  1523  	// Owner identification
  1524  	Owner string `json:"owner,omitempty" protobuf:"bytes,3,opt,name=owner"`
  1525  	// Trust level
  1526  	Trust string `json:"trust,omitempty" protobuf:"bytes,4,opt,name=trust"`
  1527  	// Key sub type (e.g. rsa4096)
  1528  	SubType string `json:"subType,omitempty" protobuf:"bytes,5,opt,name=subType"`
  1529  	// Key data
  1530  	KeyData string `json:"keyData,omitempty" protobuf:"bytes,6,opt,name=keyData"`
  1531  }
  1532  
  1533  // GnuPGPublicKeyList is a collection of GnuPGPublicKey objects
  1534  type GnuPGPublicKeyList struct {
  1535  	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
  1536  	Items           []GnuPGPublicKey `json:"items" protobuf:"bytes,2,rep,name=items"`
  1537  }
  1538  
  1539  // AppProjectList is list of AppProject resources
  1540  // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
  1541  type AppProjectList struct {
  1542  	metav1.TypeMeta `json:",inline"`
  1543  	metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
  1544  	Items           []AppProject `json:"items" protobuf:"bytes,2,rep,name=items"`
  1545  }
  1546  
  1547  // AppProject provides a logical grouping of applications, providing controls for:
  1548  // * where the apps may deploy to (cluster whitelist)
  1549  // * what may be deployed (repository whitelist, resource whitelist/blacklist)
  1550  // * who can access these applications (roles, OIDC group claims bindings)
  1551  // * and what they can do (RBAC policies)
  1552  // * automation access to these roles (JWT tokens)
  1553  // +genclient
  1554  // +genclient:noStatus
  1555  // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
  1556  // +kubebuilder:resource:path=appprojects,shortName=appproj;appprojs
  1557  type AppProject struct {
  1558  	metav1.TypeMeta   `json:",inline"`
  1559  	metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
  1560  	Spec              AppProjectSpec   `json:"spec" protobuf:"bytes,2,opt,name=spec"`
  1561  	Status            AppProjectStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
  1562  }
  1563  
  1564  // GetRoleByName returns the role in a project by the name with its index
  1565  func (p *AppProject) GetRoleByName(name string) (*ProjectRole, int, error) {
  1566  	for i, role := range p.Spec.Roles {
  1567  		if name == role.Name {
  1568  			return &role, i, nil
  1569  		}
  1570  	}
  1571  	return nil, -1, fmt.Errorf("role '%s' does not exist in project '%s'", name, p.Name)
  1572  }
  1573  
  1574  // GetJWTToken looks up the index of a JWTToken in a project by id (new token), if not then by the issue at time (old token)
  1575  func (p *AppProject) GetJWTTokenFromSpec(roleName string, issuedAt int64, id string) (*JWTToken, int, error) {
  1576  	// This is for backward compatibility. In the oder version, JWTTokens are stored under spec.role
  1577  	role, _, err := p.GetRoleByName(roleName)
  1578  	if err != nil {
  1579  		return nil, -1, err
  1580  	}
  1581  
  1582  	if id != "" {
  1583  		for i, token := range role.JWTTokens {
  1584  			if id == token.ID {
  1585  				return &token, i, nil
  1586  			}
  1587  		}
  1588  	}
  1589  
  1590  	if issuedAt != -1 {
  1591  		for i, token := range role.JWTTokens {
  1592  			if issuedAt == token.IssuedAt {
  1593  				return &token, i, nil
  1594  			}
  1595  		}
  1596  	}
  1597  
  1598  	return nil, -1, fmt.Errorf("JWT token for role '%s' issued at '%d' does not exist in project '%s'", role.Name, issuedAt, p.Name)
  1599  }
  1600  
  1601  // GetJWTToken looks up the index of a JWTToken in a project by id (new token), if not then by the issue at time (old token)
  1602  func (p *AppProject) GetJWTToken(roleName string, issuedAt int64, id string) (*JWTToken, int, error) {
  1603  	// This is for newer version, JWTTokens are stored under status
  1604  	if id != "" {
  1605  		for i, token := range p.Status.JWTTokensByRole[roleName].Items {
  1606  			if id == token.ID {
  1607  				return &token, i, nil
  1608  			}
  1609  		}
  1610  
  1611  	}
  1612  
  1613  	if issuedAt != -1 {
  1614  		for i, token := range p.Status.JWTTokensByRole[roleName].Items {
  1615  			if issuedAt == token.IssuedAt {
  1616  				return &token, i, nil
  1617  			}
  1618  		}
  1619  	}
  1620  
  1621  	return nil, -1, fmt.Errorf("JWT token for role '%s' issued at '%d' does not exist in project '%s'", roleName, issuedAt, p.Name)
  1622  }
  1623  
  1624  func (p AppProject) RemoveJWTToken(roleIndex int, issuedAt int64, id string) error {
  1625  	roleName := p.Spec.Roles[roleIndex].Name
  1626  	// For backward compatibility
  1627  	_, jwtTokenIndex, err1 := p.GetJWTTokenFromSpec(roleName, issuedAt, id)
  1628  	if err1 == nil {
  1629  		p.Spec.Roles[roleIndex].JWTTokens[jwtTokenIndex] = p.Spec.Roles[roleIndex].JWTTokens[len(p.Spec.Roles[roleIndex].JWTTokens)-1]
  1630  		p.Spec.Roles[roleIndex].JWTTokens = p.Spec.Roles[roleIndex].JWTTokens[:len(p.Spec.Roles[roleIndex].JWTTokens)-1]
  1631  	}
  1632  
  1633  	// New location for storing JWTToken
  1634  	_, jwtTokenIndex, err2 := p.GetJWTToken(roleName, issuedAt, id)
  1635  	if err2 == nil {
  1636  		p.Status.JWTTokensByRole[roleName].Items[jwtTokenIndex] = p.Status.JWTTokensByRole[roleName].Items[len(p.Status.JWTTokensByRole[roleName].Items)-1]
  1637  		p.Status.JWTTokensByRole[roleName] = JWTTokens{Items: p.Status.JWTTokensByRole[roleName].Items[:len(p.Status.JWTTokensByRole[roleName].Items)-1]}
  1638  	}
  1639  
  1640  	if err1 == nil || err2 == nil {
  1641  		//If we find this token from either places, we can say there are no error
  1642  		return nil
  1643  	} else {
  1644  		//If we could not locate this taken from either places, we can return any of the errors
  1645  		return err2
  1646  	}
  1647  }
  1648  
  1649  func (p *AppProject) ValidateJWTTokenID(roleName string, id string) error {
  1650  	role, _, err := p.GetRoleByName(roleName)
  1651  	if err != nil {
  1652  		return err
  1653  	}
  1654  	if id == "" {
  1655  		return nil
  1656  	}
  1657  	for _, token := range role.JWTTokens {
  1658  		if id == token.ID {
  1659  			return status.Errorf(codes.InvalidArgument, "Token id '%s' has been used. ", id)
  1660  		}
  1661  	}
  1662  	return nil
  1663  }
  1664  
  1665  func (p *AppProject) ValidateProject() error {
  1666  	destKeys := make(map[string]bool)
  1667  	for _, dest := range p.Spec.Destinations {
  1668  		key := fmt.Sprintf("%s/%s", dest.Server, dest.Namespace)
  1669  		if _, ok := destKeys[key]; ok {
  1670  			return status.Errorf(codes.InvalidArgument, "destination '%s' already added", key)
  1671  		}
  1672  		destKeys[key] = true
  1673  	}
  1674  	srcRepos := make(map[string]bool)
  1675  	for _, src := range p.Spec.SourceRepos {
  1676  		if _, ok := srcRepos[src]; ok {
  1677  			return status.Errorf(codes.InvalidArgument, "source repository '%s' already added", src)
  1678  		}
  1679  		srcRepos[src] = true
  1680  	}
  1681  
  1682  	roleNames := make(map[string]bool)
  1683  	for _, role := range p.Spec.Roles {
  1684  		if _, ok := roleNames[role.Name]; ok {
  1685  			return status.Errorf(codes.AlreadyExists, "role '%s' already exists", role.Name)
  1686  		}
  1687  		if err := validateRoleName(role.Name); err != nil {
  1688  			return err
  1689  		}
  1690  		existingPolicies := make(map[string]bool)
  1691  		for _, policy := range role.Policies {
  1692  			if _, ok := existingPolicies[policy]; ok {
  1693  				return status.Errorf(codes.AlreadyExists, "policy '%s' already exists for role '%s'", policy, role.Name)
  1694  			}
  1695  			if err := validatePolicy(p.Name, role.Name, policy); err != nil {
  1696  				return err
  1697  			}
  1698  			existingPolicies[policy] = true
  1699  		}
  1700  		existingGroups := make(map[string]bool)
  1701  		for _, group := range role.Groups {
  1702  			if _, ok := existingGroups[group]; ok {
  1703  				return status.Errorf(codes.AlreadyExists, "group '%s' already exists for role '%s'", group, role.Name)
  1704  			}
  1705  			if err := validateGroupName(group); err != nil {
  1706  				return err
  1707  			}
  1708  			existingGroups[group] = true
  1709  		}
  1710  		roleNames[role.Name] = true
  1711  	}
  1712  
  1713  	if p.Spec.SyncWindows.HasWindows() {
  1714  		existingWindows := make(map[string]bool)
  1715  		for _, window := range p.Spec.SyncWindows {
  1716  			if _, ok := existingWindows[window.Kind+window.Schedule+window.Duration]; ok {
  1717  				return status.Errorf(codes.AlreadyExists, "window '%s':'%s':'%s' already exists, update or edit", window.Kind, window.Schedule, window.Duration)
  1718  			}
  1719  			err := window.Validate()
  1720  			if err != nil {
  1721  				return err
  1722  			}
  1723  			if len(window.Applications) == 0 && len(window.Namespaces) == 0 && len(window.Clusters) == 0 {
  1724  				return status.Errorf(codes.OutOfRange, "window '%s':'%s':'%s' requires one of application, cluster or namespace", window.Kind, window.Schedule, window.Duration)
  1725  			}
  1726  			existingWindows[window.Kind+window.Schedule+window.Duration] = true
  1727  		}
  1728  	}
  1729  
  1730  	return nil
  1731  }
  1732  
  1733  // TODO: refactor to use rbacpolicy.ActionGet, rbacpolicy.ActionCreate, without import cycle
  1734  var validActions = map[string]bool{
  1735  	"get":      true,
  1736  	"create":   true,
  1737  	"update":   true,
  1738  	"delete":   true,
  1739  	"sync":     true,
  1740  	"override": true,
  1741  	"*":        true,
  1742  }
  1743  
  1744  var validActionPatterns = []*regexp.Regexp{
  1745  	regexp.MustCompile("action/.*"),
  1746  }
  1747  
  1748  func isValidAction(action string) bool {
  1749  	if validActions[action] {
  1750  		return true
  1751  	}
  1752  	for i := range validActionPatterns {
  1753  		if validActionPatterns[i].MatchString(action) {
  1754  			return true
  1755  		}
  1756  	}
  1757  	return false
  1758  }
  1759  
  1760  func validatePolicy(proj string, role string, policy string) error {
  1761  	policyComponents := strings.Split(policy, ",")
  1762  	if len(policyComponents) != 6 || strings.Trim(policyComponents[0], " ") != "p" {
  1763  		return status.Errorf(codes.InvalidArgument, "invalid policy rule '%s': must be of the form: 'p, sub, res, act, obj, eft'", policy)
  1764  	}
  1765  	// subject
  1766  	subject := strings.Trim(policyComponents[1], " ")
  1767  	expectedSubject := fmt.Sprintf("proj:%s:%s", proj, role)
  1768  	if subject != expectedSubject {
  1769  		return status.Errorf(codes.InvalidArgument, "invalid policy rule '%s': policy subject must be: '%s', not '%s'", policy, expectedSubject, subject)
  1770  	}
  1771  	// resource
  1772  	resource := strings.Trim(policyComponents[2], " ")
  1773  	if resource != "applications" {
  1774  		return status.Errorf(codes.InvalidArgument, "invalid policy rule '%s': project resource must be: 'applications', not '%s'", policy, resource)
  1775  	}
  1776  	// action
  1777  	action := strings.Trim(policyComponents[3], " ")
  1778  	if !isValidAction(action) {
  1779  		return status.Errorf(codes.InvalidArgument, "invalid policy rule '%s': invalid action '%s'", policy, action)
  1780  	}
  1781  	// object
  1782  	object := strings.Trim(policyComponents[4], " ")
  1783  	objectRegexp, err := regexp.Compile(fmt.Sprintf(`^%s/[*\w-.]+$`, proj))
  1784  	if err != nil || !objectRegexp.MatchString(object) {
  1785  		return status.Errorf(codes.InvalidArgument, "invalid policy rule '%s': object must be of form '%s/*' or '%s/<APPNAME>', not '%s'", policy, proj, proj, object)
  1786  	}
  1787  	// effect
  1788  	effect := strings.Trim(policyComponents[5], " ")
  1789  	if effect != "allow" && effect != "deny" {
  1790  		return status.Errorf(codes.InvalidArgument, "invalid policy rule '%s': effect must be: 'allow' or 'deny'", policy)
  1791  	}
  1792  	return nil
  1793  }
  1794  
  1795  var roleNameRegexp = regexp.MustCompile(`^[a-zA-Z0-9]([-_a-zA-Z0-9]*[a-zA-Z0-9])?$`)
  1796  
  1797  func validateRoleName(name string) error {
  1798  	if !roleNameRegexp.MatchString(name) {
  1799  		return status.Errorf(codes.InvalidArgument, "invalid role name '%s'. Must consist of alphanumeric characters, '-' or '_', and must start and end with an alphanumeric character", name)
  1800  	}
  1801  	return nil
  1802  }
  1803  
  1804  var invalidChars = regexp.MustCompile("[,\n\r\t]")
  1805  
  1806  func validateGroupName(name string) error {
  1807  	if strings.TrimSpace(name) == "" {
  1808  		return status.Errorf(codes.InvalidArgument, "group '%s' is empty", name)
  1809  	}
  1810  	if invalidChars.MatchString(name) {
  1811  		return status.Errorf(codes.InvalidArgument, "group '%s' contains invalid characters", name)
  1812  	}
  1813  	return nil
  1814  }
  1815  
  1816  // AddGroupToRole adds an OIDC group to a role
  1817  func (p *AppProject) AddGroupToRole(roleName, group string) (bool, error) {
  1818  	role, roleIndex, err := p.GetRoleByName(roleName)
  1819  	if err != nil {
  1820  		return false, err
  1821  	}
  1822  	for _, roleGroup := range role.Groups {
  1823  		if group == roleGroup {
  1824  			return false, nil
  1825  		}
  1826  	}
  1827  	role.Groups = append(role.Groups, group)
  1828  	p.Spec.Roles[roleIndex] = *role
  1829  	return true, nil
  1830  }
  1831  
  1832  // RemoveGroupFromRole removes an OIDC group from a role
  1833  func (p *AppProject) RemoveGroupFromRole(roleName, group string) (bool, error) {
  1834  	role, roleIndex, err := p.GetRoleByName(roleName)
  1835  	if err != nil {
  1836  		return false, err
  1837  	}
  1838  	for i, roleGroup := range role.Groups {
  1839  		if group == roleGroup {
  1840  			role.Groups = append(role.Groups[:i], role.Groups[i+1:]...)
  1841  			p.Spec.Roles[roleIndex] = *role
  1842  			return true, nil
  1843  		}
  1844  	}
  1845  	return false, nil
  1846  }
  1847  
  1848  // NormalizePolicies normalizes the policies in the project
  1849  func (p *AppProject) NormalizePolicies() {
  1850  	for i, role := range p.Spec.Roles {
  1851  		var normalizedPolicies []string
  1852  		for _, policy := range role.Policies {
  1853  			normalizedPolicies = append(normalizedPolicies, p.normalizePolicy(policy))
  1854  		}
  1855  		p.Spec.Roles[i].Policies = normalizedPolicies
  1856  	}
  1857  }
  1858  
  1859  func (p *AppProject) normalizePolicy(policy string) string {
  1860  	policyComponents := strings.Split(policy, ",")
  1861  	normalizedPolicy := ""
  1862  	for _, component := range policyComponents {
  1863  		if normalizedPolicy == "" {
  1864  			normalizedPolicy = component
  1865  		} else {
  1866  			normalizedPolicy = fmt.Sprintf("%s, %s", normalizedPolicy, strings.Trim(component, " "))
  1867  		}
  1868  	}
  1869  	return normalizedPolicy
  1870  }
  1871  
  1872  // OrphanedResourcesMonitorSettings holds settings of orphaned resources monitoring
  1873  type OrphanedResourcesMonitorSettings struct {
  1874  	// Warn indicates if warning condition should be created for apps which have orphaned resources
  1875  	Warn   *bool                 `json:"warn,omitempty" protobuf:"bytes,1,name=warn"`
  1876  	Ignore []OrphanedResourceKey `json:"ignore,omitempty" protobuf:"bytes,2,opt,name=ignore"`
  1877  }
  1878  
  1879  type OrphanedResourceKey struct {
  1880  	Group string `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"`
  1881  	Kind  string `json:"kind,omitempty" protobuf:"bytes,2,opt,name=kind"`
  1882  	Name  string `json:"name,omitempty" protobuf:"bytes,3,opt,name=name"`
  1883  }
  1884  
  1885  func (s *OrphanedResourcesMonitorSettings) IsWarn() bool {
  1886  	return s.Warn == nil || *s.Warn
  1887  }
  1888  
  1889  // SignatureKey is the specification of a key required to verify commit signatures with
  1890  type SignatureKey struct {
  1891  	// The ID of the key in hexadecimal notation
  1892  	KeyID string `json:"keyID" protobuf:"bytes,1,name=keyID"`
  1893  }
  1894  
  1895  // AppProjectSpec is the specification of an AppProject
  1896  type AppProjectSpec struct {
  1897  	// SourceRepos contains list of repository URLs which can be used for deployment
  1898  	SourceRepos []string `json:"sourceRepos,omitempty" protobuf:"bytes,1,name=sourceRepos"`
  1899  	// Destinations contains list of destinations available for deployment
  1900  	Destinations []ApplicationDestination `json:"destinations,omitempty" protobuf:"bytes,2,name=destination"`
  1901  	// Description contains optional project description
  1902  	Description string `json:"description,omitempty" protobuf:"bytes,3,opt,name=description"`
  1903  	// Roles are user defined RBAC roles associated with this project
  1904  	Roles []ProjectRole `json:"roles,omitempty" protobuf:"bytes,4,rep,name=roles"`
  1905  	// ClusterResourceWhitelist contains list of whitelisted cluster level resources
  1906  	ClusterResourceWhitelist []metav1.GroupKind `json:"clusterResourceWhitelist,omitempty" protobuf:"bytes,5,opt,name=clusterResourceWhitelist"`
  1907  	// NamespaceResourceBlacklist contains list of blacklisted namespace level resources
  1908  	NamespaceResourceBlacklist []metav1.GroupKind `json:"namespaceResourceBlacklist,omitempty" protobuf:"bytes,6,opt,name=namespaceResourceBlacklist"`
  1909  	// OrphanedResources specifies if controller should monitor orphaned resources of apps in this project
  1910  	OrphanedResources *OrphanedResourcesMonitorSettings `json:"orphanedResources,omitempty" protobuf:"bytes,7,opt,name=orphanedResources"`
  1911  	// SyncWindows controls when syncs can be run for apps in this project
  1912  	SyncWindows SyncWindows `json:"syncWindows,omitempty" protobuf:"bytes,8,opt,name=syncWindows"`
  1913  	// NamespaceResourceWhitelist contains list of whitelisted namespace level resources
  1914  	NamespaceResourceWhitelist []metav1.GroupKind `json:"namespaceResourceWhitelist,omitempty" protobuf:"bytes,9,opt,name=namespaceResourceWhitelist"`
  1915  	// List of PGP key IDs that commits to be synced to must be signed with
  1916  	SignatureKeys []SignatureKey `json:"signatureKeys,omitempty" protobuf:"bytes,10,opt,name=signatureKeys"`
  1917  	// ClusterResourceBlacklist contains list of blacklisted cluster level resources
  1918  	ClusterResourceBlacklist []metav1.GroupKind `json:"clusterResourceBlacklist,omitempty" protobuf:"bytes,11,opt,name=clusterResourceBlacklist"`
  1919  }
  1920  
  1921  // SyncWindows is a collection of sync windows in this project
  1922  type SyncWindows []*SyncWindow
  1923  
  1924  // SyncWindow contains the kind, time, duration and attributes that are used to assign the syncWindows to apps
  1925  type SyncWindow struct {
  1926  	// Kind defines if the window allows or blocks syncs
  1927  	Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"`
  1928  	// Schedule is the time the window will begin, specified in cron format
  1929  	Schedule string `json:"schedule,omitempty" protobuf:"bytes,2,opt,name=schedule"`
  1930  	// Duration is the amount of time the sync window will be open
  1931  	Duration string `json:"duration,omitempty" protobuf:"bytes,3,opt,name=duration"`
  1932  	// Applications contains a list of applications that the window will apply to
  1933  	Applications []string `json:"applications,omitempty" protobuf:"bytes,4,opt,name=applications"`
  1934  	// Namespaces contains a list of namespaces that the window will apply to
  1935  	Namespaces []string `json:"namespaces,omitempty" protobuf:"bytes,5,opt,name=namespaces"`
  1936  	// Clusters contains a list of clusters that the window will apply to
  1937  	Clusters []string `json:"clusters,omitempty" protobuf:"bytes,6,opt,name=clusters"`
  1938  	// ManualSync enables manual syncs when they would otherwise be blocked
  1939  	ManualSync bool `json:"manualSync,omitempty" protobuf:"bytes,7,opt,name=manualSync"`
  1940  }
  1941  
  1942  func (s *SyncWindows) HasWindows() bool {
  1943  	return s != nil && len(*s) > 0
  1944  }
  1945  
  1946  func (s *SyncWindows) Active() *SyncWindows {
  1947  	return s.active(time.Now())
  1948  }
  1949  
  1950  func (s *SyncWindows) active(currentTime time.Time) *SyncWindows {
  1951  
  1952  	// If SyncWindows.Active() is called outside of a UTC locale, it should be
  1953  	// first converted to UTC before we scan through the SyncWindows.
  1954  	currentTime = currentTime.In(time.UTC)
  1955  
  1956  	if s.HasWindows() {
  1957  		var active SyncWindows
  1958  		specParser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
  1959  		for _, w := range *s {
  1960  			schedule, _ := specParser.Parse(w.Schedule)
  1961  			duration, _ := time.ParseDuration(w.Duration)
  1962  			nextWindow := schedule.Next(currentTime.Add(-duration))
  1963  			if nextWindow.Before(currentTime) {
  1964  				active = append(active, w)
  1965  			}
  1966  		}
  1967  		if len(active) > 0 {
  1968  			return &active
  1969  		}
  1970  	}
  1971  	return nil
  1972  }
  1973  
  1974  func (s *SyncWindows) InactiveAllows() *SyncWindows {
  1975  	return s.inactiveAllows(time.Now())
  1976  }
  1977  
  1978  func (s *SyncWindows) inactiveAllows(currentTime time.Time) *SyncWindows {
  1979  
  1980  	// If SyncWindows.InactiveAllows() is called outside of a UTC locale, it should be
  1981  	// first converted to UTC before we scan through the SyncWindows.
  1982  	currentTime = currentTime.In(time.UTC)
  1983  
  1984  	if s.HasWindows() {
  1985  		var inactive SyncWindows
  1986  		specParser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
  1987  		for _, w := range *s {
  1988  			if w.Kind == "allow" {
  1989  				schedule, sErr := specParser.Parse(w.Schedule)
  1990  				duration, dErr := time.ParseDuration(w.Duration)
  1991  				nextWindow := schedule.Next(currentTime.Add(-duration))
  1992  				if !nextWindow.Before(currentTime) && sErr == nil && dErr == nil {
  1993  					inactive = append(inactive, w)
  1994  				}
  1995  			}
  1996  		}
  1997  		if len(inactive) > 0 {
  1998  			return &inactive
  1999  		}
  2000  	}
  2001  	return nil
  2002  }
  2003  
  2004  func (s *AppProjectSpec) AddWindow(knd string, sch string, dur string, app []string, ns []string, cl []string, ms bool) error {
  2005  	if len(knd) == 0 || len(sch) == 0 || len(dur) == 0 {
  2006  		return fmt.Errorf("cannot create window: require kind, schedule, duration and one or more of applications, namespaces and clusters")
  2007  
  2008  	}
  2009  	window := &SyncWindow{
  2010  		Kind:       knd,
  2011  		Schedule:   sch,
  2012  		Duration:   dur,
  2013  		ManualSync: ms,
  2014  	}
  2015  
  2016  	if len(app) > 0 {
  2017  		window.Applications = app
  2018  	}
  2019  	if len(ns) > 0 {
  2020  		window.Namespaces = ns
  2021  	}
  2022  	if len(cl) > 0 {
  2023  		window.Clusters = cl
  2024  	}
  2025  
  2026  	err := window.Validate()
  2027  	if err != nil {
  2028  		return err
  2029  	}
  2030  
  2031  	s.SyncWindows = append(s.SyncWindows, window)
  2032  
  2033  	return nil
  2034  
  2035  }
  2036  
  2037  func (s *AppProjectSpec) DeleteWindow(id int) error {
  2038  	var exists bool
  2039  	for i := range s.SyncWindows {
  2040  		if i == id {
  2041  			exists = true
  2042  			s.SyncWindows = append(s.SyncWindows[:i], s.SyncWindows[i+1:]...)
  2043  			break
  2044  		}
  2045  	}
  2046  	if !exists {
  2047  		return fmt.Errorf("window with id '%s' not found", strconv.Itoa(id))
  2048  	}
  2049  	return nil
  2050  }
  2051  
  2052  func (w *SyncWindows) Matches(app *Application) *SyncWindows {
  2053  	if w.HasWindows() {
  2054  		var matchingWindows SyncWindows
  2055  		for _, w := range *w {
  2056  			if len(w.Applications) > 0 {
  2057  				for _, a := range w.Applications {
  2058  					if globMatch(a, app.Name) {
  2059  						matchingWindows = append(matchingWindows, w)
  2060  						break
  2061  					}
  2062  				}
  2063  			}
  2064  			if len(w.Clusters) > 0 {
  2065  				for _, c := range w.Clusters {
  2066  					if globMatch(c, app.Spec.Destination.Server) {
  2067  						matchingWindows = append(matchingWindows, w)
  2068  						break
  2069  					}
  2070  				}
  2071  			}
  2072  			if len(w.Namespaces) > 0 {
  2073  				for _, n := range w.Namespaces {
  2074  					if globMatch(n, app.Spec.Destination.Namespace) {
  2075  						matchingWindows = append(matchingWindows, w)
  2076  						break
  2077  					}
  2078  				}
  2079  			}
  2080  		}
  2081  		if len(matchingWindows) > 0 {
  2082  			return &matchingWindows
  2083  		}
  2084  	}
  2085  	return nil
  2086  }
  2087  
  2088  func (w *SyncWindows) CanSync(isManual bool) bool {
  2089  	if !w.HasWindows() {
  2090  		return true
  2091  	}
  2092  
  2093  	var allowActive, denyActive, manualEnabled bool
  2094  	active := w.Active()
  2095  	denyActive, manualEnabled = active.hasDeny()
  2096  	allowActive = active.hasAllow()
  2097  
  2098  	if !denyActive {
  2099  		if !allowActive {
  2100  			if isManual && w.InactiveAllows().manualEnabled() {
  2101  				return true
  2102  			}
  2103  		} else {
  2104  			return true
  2105  		}
  2106  	} else {
  2107  		if isManual && manualEnabled {
  2108  			return true
  2109  		}
  2110  	}
  2111  
  2112  	return false
  2113  }
  2114  
  2115  func (w *SyncWindows) hasDeny() (bool, bool) {
  2116  	if !w.HasWindows() {
  2117  		return false, false
  2118  	}
  2119  	var denyActive, manualEnabled bool
  2120  	for _, a := range *w {
  2121  		if a.Kind == "deny" {
  2122  			if !denyActive {
  2123  				manualEnabled = a.ManualSync
  2124  			} else {
  2125  				if manualEnabled {
  2126  					if !a.ManualSync {
  2127  						manualEnabled = a.ManualSync
  2128  					}
  2129  				}
  2130  			}
  2131  			denyActive = true
  2132  		}
  2133  	}
  2134  	return denyActive, manualEnabled
  2135  }
  2136  
  2137  func (w *SyncWindows) hasAllow() bool {
  2138  	if !w.HasWindows() {
  2139  		return false
  2140  	}
  2141  	for _, a := range *w {
  2142  		if a.Kind == "allow" {
  2143  			return true
  2144  		}
  2145  	}
  2146  	return false
  2147  }
  2148  
  2149  func (w *SyncWindows) manualEnabled() bool {
  2150  	if !w.HasWindows() {
  2151  		return false
  2152  	}
  2153  	for _, s := range *w {
  2154  		if s.ManualSync {
  2155  			return true
  2156  		}
  2157  	}
  2158  	return false
  2159  }
  2160  
  2161  func (w SyncWindow) Active() bool {
  2162  	return w.active(time.Now())
  2163  }
  2164  
  2165  func (w SyncWindow) active(currentTime time.Time) bool {
  2166  
  2167  	// If SyncWindow.Active() is called outside of a UTC locale, it should be
  2168  	// first converted to UTC before search
  2169  	currentTime = currentTime.UTC()
  2170  
  2171  	specParser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
  2172  	schedule, _ := specParser.Parse(w.Schedule)
  2173  	duration, _ := time.ParseDuration(w.Duration)
  2174  
  2175  	nextWindow := schedule.Next(currentTime.Add(-duration))
  2176  	return nextWindow.Before(currentTime)
  2177  }
  2178  
  2179  func (w *SyncWindow) Update(s string, d string, a []string, n []string, c []string) error {
  2180  	if len(s) == 0 && len(d) == 0 && len(a) == 0 && len(n) == 0 && len(c) == 0 {
  2181  		return fmt.Errorf("cannot update: require one or more of schedule, duration, application, namespace, or cluster")
  2182  	}
  2183  
  2184  	if len(s) > 0 {
  2185  		w.Schedule = s
  2186  	}
  2187  
  2188  	if len(d) > 0 {
  2189  		w.Duration = d
  2190  	}
  2191  
  2192  	if len(a) > 0 {
  2193  		w.Applications = a
  2194  	}
  2195  	if len(n) > 0 {
  2196  		w.Namespaces = n
  2197  	}
  2198  	if len(c) > 0 {
  2199  		w.Clusters = c
  2200  	}
  2201  
  2202  	return nil
  2203  }
  2204  
  2205  func (w *SyncWindow) Validate() error {
  2206  	if w.Kind != "allow" && w.Kind != "deny" {
  2207  		return fmt.Errorf("kind '%s' mismatch: can only be allow or deny", w.Kind)
  2208  	}
  2209  	specParser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
  2210  	_, err := specParser.Parse(w.Schedule)
  2211  	if err != nil {
  2212  		return fmt.Errorf("cannot parse schedule '%s': %s", w.Schedule, err)
  2213  	}
  2214  	_, err = time.ParseDuration(w.Duration)
  2215  	if err != nil {
  2216  		return fmt.Errorf("cannot parse duration '%s': %s", w.Duration, err)
  2217  	}
  2218  	return nil
  2219  }
  2220  
  2221  func (d AppProjectSpec) DestinationClusters() []string {
  2222  	servers := make([]string, 0)
  2223  
  2224  	for _, d := range d.Destinations {
  2225  		servers = append(servers, d.Server)
  2226  	}
  2227  
  2228  	return servers
  2229  }
  2230  
  2231  // ProjectRole represents a role that has access to a project
  2232  type ProjectRole struct {
  2233  	// Name is a name for this role
  2234  	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
  2235  	// Description is a description of the role
  2236  	Description string `json:"description,omitempty" protobuf:"bytes,2,opt,name=description"`
  2237  	// Policies Stores a list of casbin formated strings that define access policies for the role in the project
  2238  	Policies []string `json:"policies,omitempty" protobuf:"bytes,3,rep,name=policies"`
  2239  	// JWTTokens are a list of generated JWT tokens bound to this role
  2240  	JWTTokens []JWTToken `json:"jwtTokens,omitempty" protobuf:"bytes,4,rep,name=jwtTokens"`
  2241  	// Groups are a list of OIDC group claims bound to this role
  2242  	Groups []string `json:"groups,omitempty" protobuf:"bytes,5,rep,name=groups"`
  2243  }
  2244  
  2245  // JWTToken holds the issuedAt and expiresAt values of a token
  2246  type JWTToken struct {
  2247  	IssuedAt  int64  `json:"iat" protobuf:"int64,1,opt,name=iat"`
  2248  	ExpiresAt int64  `json:"exp,omitempty" protobuf:"int64,2,opt,name=exp"`
  2249  	ID        string `json:"id,omitempty" protobuf:"bytes,3,opt,name=id"`
  2250  }
  2251  
  2252  // Command holds binary path and arguments list
  2253  type Command struct {
  2254  	Command []string `json:"command,omitempty" protobuf:"bytes,1,name=command"`
  2255  	Args    []string `json:"args,omitempty" protobuf:"bytes,2,rep,name=args"`
  2256  }
  2257  
  2258  // ConfigManagementPlugin contains config management plugin configuration
  2259  type ConfigManagementPlugin struct {
  2260  	Name     string   `json:"name" protobuf:"bytes,1,name=name"`
  2261  	Init     *Command `json:"init,omitempty" protobuf:"bytes,2,name=init"`
  2262  	Generate Command  `json:"generate" protobuf:"bytes,3,name=generate"`
  2263  }
  2264  
  2265  // KustomizeOptions are options for kustomize to use when building manifests
  2266  type KustomizeOptions struct {
  2267  	// BuildOptions is a string of build parameters to use when calling `kustomize build`
  2268  	BuildOptions string `protobuf:"bytes,1,opt,name=buildOptions"`
  2269  	// BinaryPath holds optional path to kustomize binary
  2270  	BinaryPath string `protobuf:"bytes,2,opt,name=binaryPath"`
  2271  }
  2272  
  2273  // ProjectPoliciesString returns Casbin formated string of a project's policies for each role
  2274  func (proj *AppProject) ProjectPoliciesString() string {
  2275  	var policies []string
  2276  	for _, role := range proj.Spec.Roles {
  2277  		projectPolicy := fmt.Sprintf("p, proj:%s:%s, projects, get, %s, allow", proj.ObjectMeta.Name, role.Name, proj.ObjectMeta.Name)
  2278  		policies = append(policies, projectPolicy)
  2279  		policies = append(policies, role.Policies...)
  2280  		for _, groupName := range role.Groups {
  2281  			policies = append(policies, fmt.Sprintf("g, %s, proj:%s:%s", groupName, proj.ObjectMeta.Name, role.Name))
  2282  		}
  2283  	}
  2284  	return strings.Join(policies, "\n")
  2285  }
  2286  
  2287  // CascadedDeletion indicates if resources finalizer is set and controller should delete app resources before deleting app
  2288  func (app *Application) CascadedDeletion() bool {
  2289  	return getFinalizerIndex(app.ObjectMeta, common.ResourcesFinalizerName) > -1
  2290  }
  2291  
  2292  func (app *Application) IsRefreshRequested() (RefreshType, bool) {
  2293  	refreshType := RefreshTypeNormal
  2294  	annotations := app.GetAnnotations()
  2295  	if annotations == nil {
  2296  		return refreshType, false
  2297  	}
  2298  
  2299  	typeStr, ok := annotations[common.AnnotationKeyRefresh]
  2300  	if !ok {
  2301  		return refreshType, false
  2302  	}
  2303  
  2304  	if typeStr == string(RefreshTypeHard) {
  2305  		refreshType = RefreshTypeHard
  2306  	}
  2307  
  2308  	return refreshType, true
  2309  }
  2310  
  2311  // SetCascadedDeletion sets or remove resources finalizer
  2312  func (app *Application) SetCascadedDeletion(prune bool) {
  2313  	setFinalizer(&app.ObjectMeta, common.ResourcesFinalizerName, prune)
  2314  }
  2315  
  2316  // SetConditions updates the application status conditions for a subset of evaluated types.
  2317  // If the application has a pre-existing condition of a type that is not in the evaluated list,
  2318  // it will be preserved. If the application has a pre-existing condition of a type that
  2319  // is in the evaluated list, but not in the incoming conditions list, it will be removed.
  2320  func (status *ApplicationStatus) SetConditions(conditions []ApplicationCondition, evaluatedTypes map[ApplicationConditionType]bool) {
  2321  	appConditions := make([]ApplicationCondition, 0)
  2322  	now := metav1.Now()
  2323  	for i := 0; i < len(status.Conditions); i++ {
  2324  		condition := status.Conditions[i]
  2325  		if _, ok := evaluatedTypes[condition.Type]; !ok {
  2326  			if condition.LastTransitionTime == nil {
  2327  				condition.LastTransitionTime = &now
  2328  			}
  2329  			appConditions = append(appConditions, condition)
  2330  		}
  2331  	}
  2332  	for i := range conditions {
  2333  		condition := conditions[i]
  2334  		if condition.LastTransitionTime == nil {
  2335  			condition.LastTransitionTime = &now
  2336  		}
  2337  		eci := findConditionIndexByType(status.Conditions, condition.Type)
  2338  		if eci >= 0 && status.Conditions[eci].Message == condition.Message {
  2339  			// If we already have a condition of this type, only update the timestamp if something
  2340  			// has changed.
  2341  			appConditions = append(appConditions, status.Conditions[eci])
  2342  		} else {
  2343  			// Otherwise we use the new incoming condition with an updated timestamp:
  2344  			appConditions = append(appConditions, condition)
  2345  		}
  2346  	}
  2347  	sort.Slice(appConditions, func(i, j int) bool {
  2348  		left := appConditions[i]
  2349  		right := appConditions[j]
  2350  		return fmt.Sprintf("%s/%s/%v", left.Type, left.Message, left.LastTransitionTime) < fmt.Sprintf("%s/%s/%v", right.Type, right.Message, right.LastTransitionTime)
  2351  	})
  2352  	status.Conditions = appConditions
  2353  }
  2354  
  2355  func findConditionIndexByType(conditions []ApplicationCondition, t ApplicationConditionType) int {
  2356  	for i := range conditions {
  2357  		if conditions[i].Type == t {
  2358  			return i
  2359  		}
  2360  	}
  2361  	return -1
  2362  }
  2363  
  2364  // GetErrorConditions returns list of application error conditions
  2365  func (status *ApplicationStatus) GetConditions(conditionTypes map[ApplicationConditionType]bool) []ApplicationCondition {
  2366  	result := make([]ApplicationCondition, 0)
  2367  	for i := range status.Conditions {
  2368  		condition := status.Conditions[i]
  2369  		if ok := conditionTypes[condition.Type]; ok {
  2370  			result = append(result, condition)
  2371  		}
  2372  	}
  2373  	return result
  2374  }
  2375  
  2376  // IsError returns true if condition is error condition
  2377  func (condition *ApplicationCondition) IsError() bool {
  2378  	return strings.HasSuffix(condition.Type, "Error")
  2379  }
  2380  
  2381  // Equals compares two instances of ApplicationSource and return true if instances are equal.
  2382  func (source *ApplicationSource) Equals(other ApplicationSource) bool {
  2383  	return reflect.DeepEqual(*source, other)
  2384  }
  2385  
  2386  func (source *ApplicationSource) ExplicitType() (*ApplicationSourceType, error) {
  2387  	var appTypes []ApplicationSourceType
  2388  	if source.Kustomize != nil {
  2389  		appTypes = append(appTypes, ApplicationSourceTypeKustomize)
  2390  	}
  2391  	if source.Helm != nil {
  2392  		appTypes = append(appTypes, ApplicationSourceTypeHelm)
  2393  	}
  2394  	if source.Ksonnet != nil {
  2395  		appTypes = append(appTypes, ApplicationSourceTypeKsonnet)
  2396  	}
  2397  	if source.Directory != nil {
  2398  		appTypes = append(appTypes, ApplicationSourceTypeDirectory)
  2399  	}
  2400  	if source.Plugin != nil {
  2401  		appTypes = append(appTypes, ApplicationSourceTypePlugin)
  2402  	}
  2403  	if len(appTypes) == 0 {
  2404  		return nil, nil
  2405  	}
  2406  	if len(appTypes) > 1 {
  2407  		typeNames := make([]string, len(appTypes))
  2408  		for i := range appTypes {
  2409  			typeNames[i] = string(appTypes[i])
  2410  		}
  2411  		return nil, fmt.Errorf("multiple application sources defined: %s", strings.Join(typeNames, ","))
  2412  	}
  2413  	appType := appTypes[0]
  2414  	return &appType, nil
  2415  }
  2416  
  2417  // Equals compares two instances of ApplicationDestination and return true if instances are equal.
  2418  func (dest ApplicationDestination) Equals(other ApplicationDestination) bool {
  2419  	// ignore destination cluster name and isServerInferred fields during comparison
  2420  	// since server URL is inferred from cluster name
  2421  	if dest.isServerInferred {
  2422  		dest.Server = ""
  2423  		dest.isServerInferred = false
  2424  	}
  2425  
  2426  	if other.isServerInferred {
  2427  		other.Server = ""
  2428  		other.isServerInferred = false
  2429  	}
  2430  	return reflect.DeepEqual(dest, other)
  2431  }
  2432  
  2433  // GetProject returns the application's project. This is preferred over spec.Project which may be empty
  2434  func (spec ApplicationSpec) GetProject() string {
  2435  	if spec.Project == "" {
  2436  		return common.DefaultAppProjectName
  2437  	}
  2438  	return spec.Project
  2439  }
  2440  
  2441  func (spec ApplicationSpec) GetRevisionHistoryLimit() int {
  2442  	if spec.RevisionHistoryLimit != nil {
  2443  		return int(*spec.RevisionHistoryLimit)
  2444  	}
  2445  	return common.RevisionHistoryLimit
  2446  }
  2447  
  2448  func isResourceInList(res metav1.GroupKind, list []metav1.GroupKind) bool {
  2449  	for _, item := range list {
  2450  		ok, err := filepath.Match(item.Kind, res.Kind)
  2451  		if ok && err == nil {
  2452  			ok, err = filepath.Match(item.Group, res.Group)
  2453  			if ok && err == nil {
  2454  				return true
  2455  			}
  2456  		}
  2457  	}
  2458  	return false
  2459  }
  2460  
  2461  // IsGroupKindPermitted validates if the given resource group/kind is permitted to be deployed in the project
  2462  func (proj AppProject) IsGroupKindPermitted(gk schema.GroupKind, namespaced bool) bool {
  2463  	var isWhiteListed, isBlackListed bool
  2464  	res := metav1.GroupKind{Group: gk.Group, Kind: gk.Kind}
  2465  
  2466  	if namespaced {
  2467  		namespaceWhitelist := proj.Spec.NamespaceResourceWhitelist
  2468  		namespaceBlacklist := proj.Spec.NamespaceResourceBlacklist
  2469  
  2470  		isWhiteListed = namespaceWhitelist == nil || len(namespaceWhitelist) != 0 && isResourceInList(res, namespaceWhitelist)
  2471  		isBlackListed = len(namespaceBlacklist) != 0 && isResourceInList(res, namespaceBlacklist)
  2472  		return isWhiteListed && !isBlackListed
  2473  	}
  2474  
  2475  	clusterWhitelist := proj.Spec.ClusterResourceWhitelist
  2476  	clusterBlacklist := proj.Spec.ClusterResourceBlacklist
  2477  
  2478  	isWhiteListed = len(clusterWhitelist) != 0 && isResourceInList(res, clusterWhitelist)
  2479  	isBlackListed = len(clusterBlacklist) != 0 && isResourceInList(res, clusterBlacklist)
  2480  	return isWhiteListed && !isBlackListed
  2481  }
  2482  
  2483  func (proj AppProject) IsLiveResourcePermitted(un *unstructured.Unstructured, server string) bool {
  2484  	if !proj.IsGroupKindPermitted(un.GroupVersionKind().GroupKind(), un.GetNamespace() != "") {
  2485  		return false
  2486  	}
  2487  	if un.GetNamespace() != "" {
  2488  		return proj.IsDestinationPermitted(ApplicationDestination{Server: server, Namespace: un.GetNamespace()})
  2489  	}
  2490  	return true
  2491  }
  2492  
  2493  // getFinalizerIndex returns finalizer index in the list of object finalizers or -1 if finalizer does not exist
  2494  func getFinalizerIndex(meta metav1.ObjectMeta, name string) int {
  2495  	for i, finalizer := range meta.Finalizers {
  2496  		if finalizer == name {
  2497  			return i
  2498  		}
  2499  	}
  2500  	return -1
  2501  }
  2502  
  2503  // setFinalizer adds or removes finalizer with the specified name
  2504  func setFinalizer(meta *metav1.ObjectMeta, name string, exist bool) {
  2505  	index := getFinalizerIndex(*meta, name)
  2506  	if exist != (index > -1) {
  2507  		if index > -1 {
  2508  			meta.Finalizers[index] = meta.Finalizers[len(meta.Finalizers)-1]
  2509  			meta.Finalizers = meta.Finalizers[:len(meta.Finalizers)-1]
  2510  		} else {
  2511  			meta.Finalizers = append(meta.Finalizers, name)
  2512  		}
  2513  	}
  2514  }
  2515  
  2516  func (proj AppProject) HasFinalizer() bool {
  2517  	return getFinalizerIndex(proj.ObjectMeta, common.ResourcesFinalizerName) > -1
  2518  }
  2519  
  2520  func (proj *AppProject) RemoveFinalizer() {
  2521  	setFinalizer(&proj.ObjectMeta, common.ResourcesFinalizerName, false)
  2522  }
  2523  
  2524  func globMatch(pattern string, val string, separators ...rune) bool {
  2525  	if pattern == "*" {
  2526  		return true
  2527  	}
  2528  	return glob.Match(pattern, val, separators...)
  2529  }
  2530  
  2531  // IsSourcePermitted validates if the provided application's source is a one of the allowed sources for the project.
  2532  func (proj AppProject) IsSourcePermitted(src ApplicationSource) bool {
  2533  	srcNormalized := git.NormalizeGitURL(src.RepoURL)
  2534  	for _, repoURL := range proj.Spec.SourceRepos {
  2535  		normalized := git.NormalizeGitURL(repoURL)
  2536  		if globMatch(normalized, srcNormalized, '/') {
  2537  			return true
  2538  		}
  2539  	}
  2540  	return false
  2541  }
  2542  
  2543  // IsDestinationPermitted validates if the provided application's destination is one of the allowed destinations for the project
  2544  func (proj AppProject) IsDestinationPermitted(dst ApplicationDestination) bool {
  2545  	for _, item := range proj.Spec.Destinations {
  2546  		if globMatch(item.Server, dst.Server) && globMatch(item.Namespace, dst.Namespace) {
  2547  			return true
  2548  		}
  2549  	}
  2550  	return false
  2551  }
  2552  
  2553  // SetK8SConfigDefaults sets Kubernetes REST config default settings
  2554  func SetK8SConfigDefaults(config *rest.Config) error {
  2555  	config.QPS = common.K8sClientConfigQPS
  2556  	config.Burst = common.K8sClientConfigBurst
  2557  	tlsConfig, err := rest.TLSConfigFor(config)
  2558  	if err != nil {
  2559  		return err
  2560  	}
  2561  
  2562  	dial := (&net.Dialer{
  2563  		Timeout:   30 * time.Second,
  2564  		KeepAlive: 30 * time.Second,
  2565  	}).DialContext
  2566  	transport := utilnet.SetTransportDefaults(&http.Transport{
  2567  		Proxy:               http.ProxyFromEnvironment,
  2568  		TLSHandshakeTimeout: 10 * time.Second,
  2569  		TLSClientConfig:     tlsConfig,
  2570  		MaxIdleConns:        common.K8sMaxIdleConnections,
  2571  		MaxIdleConnsPerHost: common.K8sMaxIdleConnections,
  2572  		MaxConnsPerHost:     common.K8sMaxIdleConnections,
  2573  		DialContext:         dial,
  2574  		DisableCompression:  config.DisableCompression,
  2575  	})
  2576  	tr, err := rest.HTTPWrappersForConfig(config, transport)
  2577  	if err != nil {
  2578  		return err
  2579  	}
  2580  
  2581  	// set default tls config and remove auth/exec provides since we use it in a custom transport
  2582  	config.TLSClientConfig = rest.TLSClientConfig{}
  2583  	config.AuthProvider = nil
  2584  	config.ExecProvider = nil
  2585  
  2586  	config.Transport = tr
  2587  	return nil
  2588  }
  2589  
  2590  // RawRestConfig returns a go-client REST config from cluster that might be serialized into the file using kube.WriteKubeConfig method.
  2591  func (c *Cluster) RawRestConfig() *rest.Config {
  2592  	var config *rest.Config
  2593  	var err error
  2594  	if c.Server == common.KubernetesInternalAPIServerAddr && os.Getenv(common.EnvVarFakeInClusterConfig) == "true" {
  2595  		conf, exists := os.LookupEnv("KUBECONFIG")
  2596  		if exists {
  2597  			config, err = clientcmd.BuildConfigFromFlags("", conf)
  2598  		} else {
  2599  			config, err = clientcmd.BuildConfigFromFlags("", filepath.Join(os.Getenv("HOME"), ".kube", "config"))
  2600  		}
  2601  	} else if c.Server == common.KubernetesInternalAPIServerAddr && c.Config.Username == "" && c.Config.Password == "" && c.Config.BearerToken == "" {
  2602  		config, err = rest.InClusterConfig()
  2603  	} else {
  2604  		tlsClientConfig := rest.TLSClientConfig{
  2605  			Insecure:   c.Config.TLSClientConfig.Insecure,
  2606  			ServerName: c.Config.TLSClientConfig.ServerName,
  2607  			CertData:   c.Config.TLSClientConfig.CertData,
  2608  			KeyData:    c.Config.TLSClientConfig.KeyData,
  2609  			CAData:     c.Config.TLSClientConfig.CAData,
  2610  		}
  2611  		if c.Config.AWSAuthConfig != nil {
  2612  			args := []string{"eks", "get-token", "--cluster-name", c.Config.AWSAuthConfig.ClusterName}
  2613  			if c.Config.AWSAuthConfig.RoleARN != "" {
  2614  				args = append(args, "--role-arn", c.Config.AWSAuthConfig.RoleARN)
  2615  			}
  2616  			config = &rest.Config{
  2617  				Host:            c.Server,
  2618  				TLSClientConfig: tlsClientConfig,
  2619  				ExecProvider: &api.ExecConfig{
  2620  					APIVersion: "client.authentication.k8s.io/v1alpha1",
  2621  					Command:    "aws",
  2622  					Args:       args,
  2623  				},
  2624  			}
  2625  		} else if c.Config.ExecProviderConfig != nil {
  2626  			var env []api.ExecEnvVar
  2627  			if c.Config.ExecProviderConfig.Env != nil {
  2628  				for key, value := range c.Config.ExecProviderConfig.Env {
  2629  					env = append(env, api.ExecEnvVar{
  2630  						Name:  key,
  2631  						Value: value,
  2632  					})
  2633  				}
  2634  			}
  2635  			config = &rest.Config{
  2636  				Host:            c.Server,
  2637  				TLSClientConfig: tlsClientConfig,
  2638  				ExecProvider: &api.ExecConfig{
  2639  					APIVersion:  c.Config.ExecProviderConfig.APIVersion,
  2640  					Command:     c.Config.ExecProviderConfig.Command,
  2641  					Args:        c.Config.ExecProviderConfig.Args,
  2642  					Env:         env,
  2643  					InstallHint: c.Config.ExecProviderConfig.InstallHint,
  2644  				},
  2645  			}
  2646  		} else {
  2647  			config = &rest.Config{
  2648  				Host:            c.Server,
  2649  				Username:        c.Config.Username,
  2650  				Password:        c.Config.Password,
  2651  				BearerToken:     c.Config.BearerToken,
  2652  				TLSClientConfig: tlsClientConfig,
  2653  			}
  2654  		}
  2655  	}
  2656  	if err != nil {
  2657  		panic(fmt.Sprintf("Unable to create K8s REST config: %v", err))
  2658  	}
  2659  	return config
  2660  }
  2661  
  2662  // RESTConfig returns a go-client REST config from cluster with tuned throttling and HTTP client settings.
  2663  func (c *Cluster) RESTConfig() *rest.Config {
  2664  	config := c.RawRestConfig()
  2665  	err := SetK8SConfigDefaults(config)
  2666  	if err != nil {
  2667  		panic(fmt.Sprintf("Unable to apply K8s REST config defaults: %v", err))
  2668  	}
  2669  	return config
  2670  }
  2671  
  2672  func UnmarshalToUnstructured(resource string) (*unstructured.Unstructured, error) {
  2673  	if resource == "" || resource == "null" {
  2674  		return nil, nil
  2675  	}
  2676  	var obj unstructured.Unstructured
  2677  	err := json.Unmarshal([]byte(resource), &obj)
  2678  	if err != nil {
  2679  		return nil, err
  2680  	}
  2681  	return &obj, nil
  2682  }
  2683  
  2684  func (r ResourceDiff) LiveObject() (*unstructured.Unstructured, error) {
  2685  	return UnmarshalToUnstructured(r.LiveState)
  2686  }
  2687  
  2688  func (r ResourceDiff) TargetObject() (*unstructured.Unstructured, error) {
  2689  	return UnmarshalToUnstructured(r.TargetState)
  2690  }
  2691  
  2692  func (d *ApplicationDestination) SetInferredServer(server string) {
  2693  	d.isServerInferred = true
  2694  	d.Server = server
  2695  }
  2696  
  2697  func (d *ApplicationDestination) IsServerInferred() bool {
  2698  	return d.isServerInferred
  2699  }
  2700  
  2701  func (d *ApplicationDestination) MarshalJSON() ([]byte, error) {
  2702  	type Alias ApplicationDestination
  2703  	dest := d
  2704  	if d.isServerInferred {
  2705  		dest = dest.DeepCopy()
  2706  		dest.Server = ""
  2707  	}
  2708  	return json.Marshal(&struct{ *Alias }{Alias: (*Alias)(dest)})
  2709  }
  2710  
  2711  func (proj *AppProject) NormalizeJWTTokens() bool {
  2712  	needNormalize := false
  2713  	for i, role := range proj.Spec.Roles {
  2714  		for j, token := range role.JWTTokens {
  2715  			if token.ID == "" {
  2716  				token.ID = strconv.FormatInt(token.IssuedAt, 10)
  2717  				role.JWTTokens[j] = token
  2718  				needNormalize = true
  2719  			}
  2720  		}
  2721  		proj.Spec.Roles[i] = role
  2722  	}
  2723  	for _, roleTokenEntry := range proj.Status.JWTTokensByRole {
  2724  		for j, token := range roleTokenEntry.Items {
  2725  			if token.ID == "" {
  2726  				token.ID = strconv.FormatInt(token.IssuedAt, 10)
  2727  				roleTokenEntry.Items[j] = token
  2728  				needNormalize = true
  2729  			}
  2730  		}
  2731  	}
  2732  	needSync := syncJWTTokenBetweenStatusAndSpec(proj)
  2733  	return needNormalize || needSync
  2734  }
  2735  
  2736  func syncJWTTokenBetweenStatusAndSpec(proj *AppProject) bool {
  2737  	existingRole := map[string]bool{}
  2738  	needSync := false
  2739  	for roleIndex, role := range proj.Spec.Roles {
  2740  		existingRole[role.Name] = true
  2741  
  2742  		tokensInSpec := role.JWTTokens
  2743  		tokensInStatus := []JWTToken{}
  2744  		if proj.Status.JWTTokensByRole == nil {
  2745  			tokensByRole := make(map[string]JWTTokens)
  2746  			proj.Status.JWTTokensByRole = tokensByRole
  2747  		} else {
  2748  			tokensInStatus = proj.Status.JWTTokensByRole[role.Name].Items
  2749  		}
  2750  		tokens := jwtTokensCombine(tokensInStatus, tokensInSpec)
  2751  
  2752  		sort.Slice(proj.Spec.Roles[roleIndex].JWTTokens, func(i, j int) bool {
  2753  			return proj.Spec.Roles[roleIndex].JWTTokens[i].IssuedAt > proj.Spec.Roles[roleIndex].JWTTokens[j].IssuedAt
  2754  		})
  2755  		sort.Slice(proj.Status.JWTTokensByRole[role.Name].Items, func(i, j int) bool {
  2756  			return proj.Status.JWTTokensByRole[role.Name].Items[i].IssuedAt > proj.Status.JWTTokensByRole[role.Name].Items[j].IssuedAt
  2757  		})
  2758  		if !cmp.Equal(tokens, proj.Spec.Roles[roleIndex].JWTTokens) || !cmp.Equal(tokens, proj.Status.JWTTokensByRole[role.Name].Items) {
  2759  			needSync = true
  2760  		}
  2761  
  2762  		proj.Spec.Roles[roleIndex].JWTTokens = tokens
  2763  		proj.Status.JWTTokensByRole[role.Name] = JWTTokens{Items: tokens}
  2764  	}
  2765  	if proj.Status.JWTTokensByRole != nil {
  2766  		for role := range proj.Status.JWTTokensByRole {
  2767  			if !existingRole[role] {
  2768  				delete(proj.Status.JWTTokensByRole, role)
  2769  				needSync = true
  2770  			}
  2771  		}
  2772  	}
  2773  
  2774  	return needSync
  2775  }
  2776  
  2777  func jwtTokensCombine(tokens1 []JWTToken, tokens2 []JWTToken) []JWTToken {
  2778  	tokensMap := make(map[string]JWTToken)
  2779  	for _, token := range append(tokens1, tokens2...) {
  2780  		tokensMap[token.ID] = token
  2781  	}
  2782  
  2783  	var tokens []JWTToken
  2784  	for _, v := range tokensMap {
  2785  		tokens = append(tokens, v)
  2786  	}
  2787  
  2788  	sort.Slice(tokens, func(i, j int) bool {
  2789  		return tokens[i].IssuedAt > tokens[j].IssuedAt
  2790  	})
  2791  	return tokens
  2792  }