diff --git a/common/defaults.go b/common/defaults.go index 1c6256729..85ae81701 100644 --- a/common/defaults.go +++ b/common/defaults.go @@ -104,6 +104,9 @@ const ( // ArgoCDDefaultRSAKeySize is the default RSA key size when not specified. ArgoCDDefaultRSAKeySize = 2048 + // ArgoCDDefaultServer is the default server address + ArgoCDDefaultServer = "https://kubernetes.default.svc" + // ArgoCDDefaultSSHKnownHosts is the default SSH Known hosts data. ArgoCDDefaultSSHKnownHosts = `[ssh.github.com]:443 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg= [ssh.github.com]:443 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl diff --git a/common/keys.go b/common/keys.go index d93ab55ee..55375fdf1 100644 --- a/common/keys.go +++ b/common/keys.go @@ -114,18 +114,18 @@ const ( // ArgoCDKeyTLSCACert is the key for TLS CA certificates. ArgoCDKeyTLSCACert = "ca.crt" - // ArgoCDPolicyMatcherMode is the key for matchers function for casbin. + // ArgoCDKeyPolicyMatcherMode is the key for matchers function for casbin. // There are two options for this, 'glob' for glob matcher or 'regex' for regex matcher. - ArgoCDPolicyMatcherMode = "policy.matchMode" + ArgoCDKeyPolicyMatcherMode = "policy.matchMode" // ArgoCDKeyUsersAnonymousEnabled is the configuration key for anonymous user access. ArgoCDKeyUsersAnonymousEnabled = "users.anonymous.enabled" - // ArgoCDDefaultServer is the default server address - ArgoCDDefaultServer = "https://kubernetes.default.svc" - // ArgoCDDexSecretKey is used to reference Dex secret from Argo CD secret into Argo CD configmap ArgoCDDexSecretKey = "oidc.dex.clientSecret" + + ArgoCDKeyKustomizeVersion = "kustomize.version." + ArgoCDKeyResourceCustomizations = "resource.customizations." ) // openshift.io keys diff --git a/controllers/argocd/argocdcommon/helper.go b/controllers/argocd/argocdcommon/helper.go index 6a998b3af..9b857ab2e 100644 --- a/controllers/argocd/argocdcommon/helper.go +++ b/controllers/argocd/argocdcommon/helper.go @@ -5,14 +5,43 @@ import ( "reflect" "github.com/argoproj-labs/argocd-operator/pkg/util" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + rbacv1 "k8s.io/api/rbac/v1" ) +// FieldToCompare contains a field from an existing resource, the same field in the desired state of the resource, and an action to be taken after comparison type FieldToCompare struct { Existing interface{} Desired interface{} ExtraAction func() } +// FieldCompFnCm is a function type for comparing fields of two ConfigMaps. +type FieldCompFnCm func(*corev1.ConfigMap, *corev1.ConfigMap) []FieldToCompare + +// FieldCompFnDeployment is a function type for comparing fields of two Deployments. +type FieldCompFnDeployment func(appsv1.Deployment, appsv1.Deployment) []FieldToCompare + +// FieldCompFnSecret is a function type for comparing fields of two Secrets. +type FieldCompFnSecret func(corev1.Secret, corev1.Secret) []FieldToCompare + +// FieldCompFnService is a function type for comparing fields of two Services. +type FieldCompFnService func(corev1.Service, corev1.Service) []FieldToCompare + +// FieldCompFnIngress is a function type for comparing fields of two Ingresses. +type FieldCompFnIngress func(networkingv1.Ingress, networkingv1.Ingress) []FieldToCompare + +// FieldCompFnRole is a function type for comparing fields of two Roles. +type FieldCompFnRole func(rbacv1.Role, rbacv1.Role) []FieldToCompare + +// FieldCompFnRoleBinding is a function type for comparing fields of two RoleBindings. +type FieldCompFnRoleBinding func(rbacv1.RoleBinding, rbacv1.RoleBinding) []FieldToCompare + +// FieldCompFnStatefulSet is a function type for comparing fields of two StatefulSets. +type FieldCompFnStatefulSet func(appsv1.StatefulSet, appsv1.StatefulSet) []FieldToCompare + // UpdateIfChanged accepts a slice of fields to be compared, along with a bool ptr. It compares all the provided fields, updating any fields and setting the bool ptr to true if a drift is detected func UpdateIfChanged(ftc []FieldToCompare, changed *bool) { for _, field := range ftc { @@ -51,3 +80,28 @@ func IsMergable(extraArgs []string, cmd []string) error { } return nil } + +// GetValueOrDefault returns the value if it's non-empty, otherwise returns the default value. +func GetValueOrDefault(value interface{}, defaultValue interface{}) interface{} { + if util.IsPtr(value) { + if reflect.ValueOf(value).IsNil() { + return defaultValue + } + return reflect.ValueOf(value).String() + } + + switch v := value.(type) { + case string: + if len(v) > 0 { + return v + } + return defaultValue + case map[string]string: + if len(v) > 0 { + return v + } + return defaultValue + } + + return defaultValue +} diff --git a/controllers/argocd/configmap.go b/controllers/argocd/configmap.go index 7843ac484..7e010743e 100644 --- a/controllers/argocd/configmap.go +++ b/controllers/argocd/configmap.go @@ -590,8 +590,8 @@ func (r *ReconcileArgoCD) reconcileRBACConfigMap(cm *corev1.ConfigMap, cr *argop } // Default Policy Matcher Mode - if cr.Spec.RBAC.PolicyMatcherMode != nil && cm.Data[common.ArgoCDPolicyMatcherMode] != *cr.Spec.RBAC.PolicyMatcherMode { - cm.Data[common.ArgoCDPolicyMatcherMode] = *cr.Spec.RBAC.PolicyMatcherMode + if cr.Spec.RBAC.PolicyMatcherMode != nil && cm.Data[common.ArgoCDKeyPolicyMatcherMode] != *cr.Spec.RBAC.PolicyMatcherMode { + cm.Data[common.ArgoCDKeyPolicyMatcherMode] = *cr.Spec.RBAC.PolicyMatcherMode changed = true } diff --git a/controllers/argocd/instance.go b/controllers/argocd/instance.go new file mode 100644 index 000000000..07a98155f --- /dev/null +++ b/controllers/argocd/instance.go @@ -0,0 +1,237 @@ +package argocd + +import ( + "fmt" + "reflect" + + argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/pkg/argoutil" + "github.com/argoproj-labs/argocd-operator/pkg/util" + "gopkg.in/yaml.v2" +) + +const ( + healthKey = "health" + ignoreDIffKey = "ignoreDifferences" + actionsKey = "actions" + allKey = "all" +) + +// getApplicationInstanceLabelKey returns the application instance label key for the given ArgoCD. +func (r *ArgoCDReconciler) getApplicationInstanceLabelKey() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.ApplicationInstanceLabelKey, common.ArgoCDDefaultApplicationInstanceLabelKey).(string) +} + +// getCAConfigMapName returns the CA ConfigMap name for the given ArgoCD. +func (r *ArgoCDReconciler) getCAConfigMapName() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.TLS.CA.ConfigMapName, argoutil.GenerateResourceName(r.Instance.Name, common.ArgoCDCASuffix)).(string) +} + +// getSCMRootCAConfigMapName returns the SCMRootCA ConfigMap name for the given ArgoCD ApplicationSet Controller. +func (r *ArgoCDReconciler) getSCMRootCAConfigMapName() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.ApplicationSet.SCMRootCAConfigMap, "").(string) +} + +// getConfigManagementPlugins returns the config management plugins for the given ArgoCD. +func (r *ArgoCDReconciler) getConfigManagementPlugins() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.ConfigManagementPlugins, common.ArgoCDDefaultConfigManagementPlugins).(string) +} + +// getGATrackingID returns the google analytics tracking ID for the given Argo CD. +func (r *ArgoCDReconciler) getGATrackingID() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.GATrackingID, common.ArgoCDDefaultGATrackingID).(string) +} + +// getHelpChatURL returns the help chat URL for the given Argo CD. +func (r *ArgoCDReconciler) getHelpChatURL() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.HelpChatURL, common.ArgoCDDefaultHelpChatURL).(string) +} + +// getHelpChatText returns the help chat text for the given Argo CD. +func (r *ArgoCDReconciler) getHelpChatText() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.HelpChatText, common.ArgoCDDefaultHelpChatText).(string) +} + +// getKustomizeBuildOptions returns the kuztomize build options for the given ArgoCD. +func (r *ArgoCDReconciler) getKustomizeBuildOptions() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.KustomizeBuildOptions, common.ArgoCDDefaultKustomizeBuildOptions).(string) +} + +// getOIDCConfig returns the OIDC configuration for the given instance. +func (r *ArgoCDReconciler) getOIDCConfig() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.OIDCConfig, common.ArgoCDDefaultOIDCConfig).(string) +} + +// getRBACPolicy will return the RBAC policy for the given ArgoCD instance. +func (r *ArgoCDReconciler) getRBACPolicy() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.RBAC.Policy, common.ArgoCDDefaultRBACPolicy).(string) +} + +// getRBACPolicyMatcherMode will return the RBAC policy matcher mode for the given ArgoCD instance. +func (r *ArgoCDReconciler) getRBACPolicyMatcherMode() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.RBAC.PolicyMatcherMode, nil).(string) +} + +// getRBACDefaultPolicy will return the RBAC default policy for the given ArgoCD instance. +func (r *ArgoCDReconciler) getRBACDefaultPolicy() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.RBAC.DefaultPolicy, common.ArgoCDDefaultRBACPolicy).(string) +} + +// getRBACScopes will return the RBAC scopes for the given ArgoCD instance. +func (r *ArgoCDReconciler) getRBACScopes() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.RBAC.Scopes, common.ArgoCDDefaultRBACScopes).(string) +} + +// getResourceExclusions will return the resource exclusions for the given ArgoCD instance. +func (r *ArgoCDReconciler) getResourceExclusions() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.ResourceExclusions, common.ArgoCDDefaultResourceExclusions).(string) +} + +// getResourceInclusions will return the resource inclusions for the given ArgoCD instance. +func (r *ArgoCDReconciler) getResourceInclusions() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.ResourceInclusions, common.ArgoCDDefaultResourceInclusions).(string) +} + +// getInitialRepositories will return the initial repositories for the given ArgoCD instance. +func (r *ArgoCDReconciler) getInitialRepositories() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.InitialRepositories, common.ArgoCDDefaultRepositories).(string) +} + +// getRepositoryCredentials will return the repository credentials for the given ArgoCD instance. +func (r *ArgoCDReconciler) getRepositoryCredentials() string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.RepositoryCredentials, common.ArgoCDDefaultRepositoryCredentials).(string) +} + +// getInitialTLSCerts will return the TLS certs for the given ArgoCD instance. +func (r *ArgoCDReconciler) getInitialTLSCerts() map[string]string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.TLS.InitialCerts, make(map[string]string)).(map[string]string) +} + +// getSSHKnownHosts will return the SSH Known Hosts data for the given ArgoCD instance. +func (r *ArgoCDReconciler) getInitialSSHKnownHosts() string { + skh := common.ArgoCDDefaultSSHKnownHosts + if r.Instance.Spec.InitialSSHKnownHosts.ExcludeDefaultHosts { + skh = "" + } + if len(r.Instance.Spec.InitialSSHKnownHosts.Keys) > 0 { + skh += r.Instance.Spec.InitialSSHKnownHosts.Keys + } + return skh +} + +func (r *ArgoCDReconciler) getDisableAdmin() string { + return fmt.Sprintf("%t", !r.Instance.Spec.DisableAdmin) +} + +func (r *ArgoCDReconciler) getGAAnonymizeUsers() string { + return fmt.Sprintf("%t", r.Instance.Spec.GAAnonymizeUsers) +} + +func (r *ArgoCDReconciler) getStatusBadgeEnabled() string { + return fmt.Sprintf("%t", r.Instance.Spec.StatusBadgeEnabled) +} + +func (r *ArgoCDReconciler) getUsersAnonymousEnabled() string { + return fmt.Sprintf("%t", r.Instance.Spec.UsersAnonymousEnabled) +} + +// getResourceTrackingMethod will return the resource tracking method for the given ArgoCD instance +func (r *ArgoCDReconciler) getResourceTrackingMethod() string { + rtm := argoproj.ParseResourceTrackingMethod(r.Instance.Spec.ResourceTrackingMethod) + if rtm == argoproj.ResourceTrackingMethodInvalid { + r.Logger.Debug(fmt.Sprintf("found invalid resource tracking method '%s'; defaulting to 'label' method", r.Instance.Spec.ResourceTrackingMethod)) + } else if r.Instance.Spec.ResourceTrackingMethod != "" { + r.Logger.Debug(fmt.Sprintf("found resource tracking method '%s'", r.Instance.Spec.ResourceTrackingMethod)) + } else { + r.Logger.Debug("using default resource tracking method 'label'") + } + return rtm.String() +} + +func (r *ArgoCDReconciler) getKustomizeVersions() map[string]string { + versions := make(map[string]string) + for _, kv := range r.Instance.Spec.KustomizeVersions { + versions[common.ArgoCDKeyKustomizeVersion+kv.Version] = kv.Path + } + return versions +} + +func (r *ArgoCDReconciler) getBanner() map[string]string { + banner := make(map[string]string) + if r.Instance.Spec.Banner != nil { + banner[common.ArgoCDKeyBannerContent] = argocdcommon.GetValueOrDefault(r.Instance.Spec.Banner.Content, "").(string) + banner[common.ArgoCDKeyBannerURL] = argocdcommon.GetValueOrDefault(r.Instance.Spec.Banner.URL, "").(string) + } + return banner +} + +func (r *ArgoCDReconciler) getExtraConfig() map[string]string { + return argocdcommon.GetValueOrDefault(r.Instance.Spec.ExtraConfig, make(map[string]string)).(map[string]string) +} + +// getResourceHealthChecks loads health customizations to `resource.customizations.health` from argocd-cm ConfigMap +func (r *ArgoCDReconciler) getResourceHealthChecks() map[string]string { + healthCheck := make(map[string]string) + + if r.Instance.Spec.ResourceHealthChecks != nil { + rhc := r.Instance.Spec.ResourceHealthChecks + for _, hc := range rhc { + subkey := util.ConstructString(util.DotSep, common.ArgoCDKeyResourceCustomizations, healthKey, util.ConstructString(util.UnderscoreSep, hc.Group, hc.Kind)) + subvalue := hc.Check + healthCheck[subkey] = subvalue + } + } + + return healthCheck +} + +// getResourceActions loads custom actions to `resource.customizations.actions` from argocd-cm ConfigMap +func (r *ArgoCDReconciler) getResourceActions() map[string]string { + actions := make(map[string]string) + + if r.Instance.Spec.ResourceActions != nil { + ra := r.Instance.Spec.ResourceActions + for _, a := range ra { + subkey := util.ConstructString(util.DotSep, common.ArgoCDKeyResourceCustomizations, actionsKey, util.ConstructString(util.UnderscoreSep, a.Group, a.Kind)) + subvalue := a.Action + actions[subkey] = subvalue + } + } + + return actions +} + +// getResourceIgnoreDifferences loads ignore differences customizations to `resource.customizations.ignoreDifferences` from argocd-cm ConfigMap +func (r *ArgoCDReconciler) getResourceIgnoreDifferences() map[string]string { + ignoreDiff := make(map[string]string) + + if r.Instance.Spec.ResourceIgnoreDifferences != nil { + rid := r.Instance.Spec.ResourceIgnoreDifferences + + if !reflect.DeepEqual(rid.All, &argoproj.IgnoreDifferenceCustomization{}) { + subkey := util.ConstructString(util.DotSep, common.ArgoCDKeyResourceCustomizations, ignoreDIffKey, allKey) + bytes, err := yaml.Marshal(rid.All) + if err != nil { + r.Logger.Error(err, "getResourceIgnoreDifferences") + return ignoreDiff + } + subvalue := string(bytes) + ignoreDiff[subkey] = subvalue + } + + for _, id := range rid.ResourceIdentifiers { + subkey := util.ConstructString(util.DotSep, common.ArgoCDKeyResourceCustomizations, ignoreDIffKey, util.ConstructString(util.UnderscoreSep, id.Group, id.Kind)) + bytes, err := yaml.Marshal(id.Customization) + if err != nil { + r.Logger.Error(err, "getResourceIgnoreDifferences") + return ignoreDiff + } + subvalue := string(bytes) + ignoreDiff[subkey] = subvalue + } + } + + return ignoreDiff +} diff --git a/pkg/util/string.go b/pkg/util/string.go index cc50bed6d..bb30d1ab1 100644 --- a/pkg/util/string.go +++ b/pkg/util/string.go @@ -6,6 +6,11 @@ import ( "strings" ) +const ( + DotSep = "." + UnderscoreSep = "_" +) + // SplitList accepts a string input containing a list of comma separated values, and returns a slice containing those values as separate elements func SplitList(s string) []string { if s == "" { @@ -73,3 +78,8 @@ func GenerateRandomString(s int) (string, error) { func StringPtr(val string) *string { return &val } + +// ConstructString concatenates the supplied parts by using the provided separator. Any empty strings are skipped +func ConstructString(separtor string, parts ...string) string { + return strings.Join(RemoveString(parts, ""), separtor) +}