initial acm config

This commit is contained in:
2025-05-19 11:18:25 -05:00
parent caea2573e8
commit 631aa6f626
486 changed files with 106 additions and 11791 deletions

4
tmp/acm-hub-bootstrap-old/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
sealed-secrets-key.yaml
slack-token-key.yaml
*-private.yaml
eso-token-*.yaml

View File

@@ -0,0 +1,38 @@
### Introduction
This repository is used to bootstrap the RHACM Hub cluster as well as hold the ACM configuration
I use in my homelab environment. It is referenced by the [cluster-config](https://github.com/gnunn-gitops/cluster-config) repo for the `local.hub` cluster's ACM applications (Hub, Policies and Observerability).
From the perspective of bootstrapping the Hub there are a couple of different ways to go. You could
manually install OpenShift GitOps and have it bootstrap everything on the Hub including RHACM. However while
I did consider that approach I'm using ACM to bootstrap my managed clusters and I wanted to use the exact same workflow
if I could for the Hub. This is because I wanted to avoid having to create a tweaked workflow just for the Hub.
### Bootstrap Process
The bootstrap process, encapsulated in the `bootstrap.sh` script, is as follows:
1. Install the RHACM operator and wait for it to complete
2. Install the MultiClusterHub custom resource and wait for it to be in the Running phase
3. Deploy all of the RHACM policies including the policy that deploys the OpenShift
GitOps operator with the bootstrap application
4. Label the Hub cluster, `local-cluster`, with the label `gitops=local.hub`. This trigger the
GitOps policy we deployed in step #3 to install OpenShift GitOps on the Hub cluster and
deploy the bootstrap application pointing to the repo and path needed for this cluster, i.e.
`local.home`
At this point the GitOps operator deploys everything the Hub cluster requires including storage, operators, etc
as well as other ACM components. In particular, the components that the script deployed (Hub + Policies) have
Argo applications that will take over managing these.
With the script bootstrapping the Hub because a one command affair. Once the command completes successfully
I walk away for 20-30 minutes while everything gets sorted out.
### Side-note on LVM Operator
If you are running a SNO cluster like I am and using the LVM Operator make sure that whatever device you are
using for storage with it is free. For example, if you are doing a reinstall of the hub (or other cluster), after
the cluster is provisioned but before you provision Day 2 ops ssh to the cluster and run `lsblk` to make sure
the device is empty with no partitions.
A reminder for future me. :)

View File

@@ -0,0 +1,45 @@
#!/bin/bash
LANG=C
SLEEP_SECONDS=30
echo ""
echo "Installing RHACM Operator."
kustomize build github.com/redhat-cop/gitops-catalog/advanced-cluster-management/operator/overlays/release-2.9 | oc apply -f -
echo "Pause $SLEEP_SECONDS seconds for the creation of the rhacm-operator..."
sleep $SLEEP_SECONDS
echo "Waiting for operator to start"
until oc get deployment multiclusterhub-operator -n open-cluster-management
do
sleep 10;
done
echo "Installing the MultiClusterHub"
kustomize build github.com/redhat-cop/gitops-catalog/advanced-cluster-management/instance/base | oc apply -f -
echo "Waiting for hub to be installed"
until [[ $(oc get multiclusterhub multiclusterhub -n open-cluster-management -o jsonpath='{.status.phase}') == 'Running' ]]
do
echo "Waiting for hub, current status:"
oc get multiclusterhub multiclusterhub -n open-cluster-management
sleep 10
done
echo "Installing policies and initial secrets"
kustomize build bootstrap/secrets/base | oc apply -f -
kustomize build bootstrap/policies/overlays/default --enable-alpha-plugins | oc apply -f -
echo "Labeling cluster with 'gitops: local.home'"
oc label managedcluster local-cluster gitops=local.hub --overwrite=true
echo "Check policy compliance with the following command:"
echo " oc get policy -A"
echo "Once GitOps configuration is complete you may need to run the following to fix Unknown status\n"
echo " oc delete secret local-cluster-import -n local-cluster\n"

View File

@@ -0,0 +1 @@
Experimental, not used at the momentgit add

View File

@@ -0,0 +1,58 @@
#!/bin/bash
LANG=C
SLEEP_SECONDS=45
echo ""
echo "Installing GitOps Operator."
kustomize build ../../components/policies/gitops/base/manifests/gitops-subscription | oc apply -f -
echo "Pause $SLEEP_SECONDS seconds for the creation of the gitops-operator..."
sleep $SLEEP_SECONDS
echo "Waiting for operator to start"
until oc get deployment gitops-operator-controller-manager -n openshift-operators
do
sleep 5;
done
echo "Waiting for openshift-gitops namespace to be created"
until oc get ns openshift-gitops
do
sleep 5;
done
echo "Waiting for deployments to start"
until oc get deployment cluster -n openshift-gitops
do
sleep 5;
done
echo "Waiting for all pods to be created"
deployments=(cluster kam openshift-gitops-applicationset-controller openshift-gitops-redis openshift-gitops-repo-server openshift-gitops-server)
for i in "${deployments[@]}";
do
echo "Waiting for deployment $i";
oc rollout status deployment $i -n openshift-gitops
done
echo "Apply overlay to override default instance"
# echo "Create default instance of gitops operator"
kustomize build ../../components/policies/gitops/base/manifests/gitops-instance/base |
yq -y ".spec.repo.env |= map(select(.name == \"INFRASTRUCTURE_ID\").value=\"$INFRASTRUCTURE_ID\")" |
yq -y ".spec.repo.env |= map(select(.name == \"CLUSTER_ID\").value=\"$CLUSTER_ID\")" |
yq -y ".spec.repo.env |= map(select(.name == \"CLUSTER_NAME\").value=\"$CLUSTER_NAME\")" |
yq -y ".spec.repo.env |= map(select(.name == \"SUB_DOMAIN\").value=\"$SUB_DOMAIN\")" |
oc apply -f -
sleep 10
echo "Waiting for all pods to redeploy"
deployments=(cluster kam openshift-gitops-applicationset-controller openshift-gitops-redis openshift-gitops-repo-server openshift-gitops-server)
for i in "${deployments[@]}";
do
echo "Waiting for deployment $i";
oc rollout status deployment $i -n openshift-gitops
done
echo "GitOps Operator ready"

View File

@@ -0,0 +1,8 @@
namespace: acm-policies
namespace: acm-policies
resources:
# - ../../../../components/policies/compliance/base
- ../../../../components/policies/cert-expiration/base
- ../../../../components/policies/gitops/base

View File

@@ -0,0 +1 @@
Bootstrap tokens for ESO to access Doppler projects, note that these secrets are not in git.

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: hub-secrets

View File

@@ -0,0 +1,10 @@
resources:
- namespace.yaml
- hub-secrets-namespace.yaml
# Note these secrets are not stored in git and only applied during bootstrap process
# They are the tokens for accessing the Doppler secrets SaaS service where secrets are stored
- eso-token-cluster-push-secret.yaml
- eso-token-cluster-home-secret.yaml
- eso-token-cluster-hub-secret.yaml
- eso-token-cluster-rhdp-secret.yaml
- eso-token-cluster-microshift-secret.yaml

View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: Namespace
metadata:
annotations:
openshift.io/description: ACM Policies
openshift.io/display-name: ACM Policies
labels:
name: acm-policies

View File

@@ -0,0 +1,12 @@
resources:
- github.com/redhat-cop/gitops-catalog/advanced-cluster-management/operator/overlays/release-2.12
- github.com/redhat-cop/gitops-catalog/advanced-cluster-management/instance/base
patches:
- patch: |-
- op: replace
path: /spec/channel
value: 'release-2.13'
target:
kind: Subscription
name: advanced-cluster-management

View File

@@ -0,0 +1,31 @@
kind: ConfigMap
apiVersion: v1
metadata:
name: observability-metrics-custom-allowlist
namespace: open-cluster-management-observability
data:
metrics_list.yaml: |
names:
- argocd_cluster_info
- argocd-server-metrics
- argocd_app_info
- argocd_app_sync_total
- argocd_app_reconcile_count
- argocd_app_reconcile_bucket
- argocd_app_k8s_request_total
- argocd_kubectl_exec_pending
- argocd-metrics
- argocd_cluster_api_resource_objects
- argocd_cluster_api_resources
- argocd_cluster_events_total
- argocd_git_request_total
- argocd_git_request_duration_seconds_bucket
- argocd-repo-server
- argocd_redis_request_total
- process_start_time_seconds
- workqueue_depth
- go_memstats_heap_alloc_bytes
- process_cpu_seconds_total
- go_goroutines
- go_gc_duration_seconds
- grpc_server_handled_total

View File

@@ -0,0 +1,46 @@
namespace: open-cluster-management-observability
generatorOptions:
disableNameSuffixHash: true
labels:
grafana-custom-dashboard: "true"
resources:
- github.com/redhat-cop/gitops-catalog/advanced-cluster-management/instance/observability
- custom-metrics-allowlist.yaml
configMapGenerator:
- name: gitops-dashboard
files:
- gitops.json=gitops-dashboard.json
patches:
- patch: |-
- op: replace
path: /spec/storageConfig/storageClass
value: lvms-vg1
- op: replace
path: /spec/observabilityAddonSpec/interval
value: 60
- op: add
path: /spec/advanced
value:
alertmanager:
replicas: 1
grafana:
replicas: 1
observatoriumAPI:
replicas: 1
query:
replicas: 1
queryFrontend:
replicas: 1
queryFrontendMemcached:
replicas: 1
rbacQueryProxy:
replicas: 1
receive:
replicas: 1
target:
kind: MultiClusterObservability
name: observability

View File

@@ -0,0 +1,3 @@
resources:
- local-home.yaml
- local-hub.yaml

View File

@@ -0,0 +1,18 @@
apiVersion: cluster.open-cluster-management.io/v1
kind: ManagedCluster
metadata:
annotations:
open-cluster-management/created-via: other
labels:
cloud: Other
cluster.open-cluster-management.io/clusterset: dev
gitops: local.home
color: "0066CC"
name: home
vendor: OpenShift
name: home
spec:
hubAcceptsClient: true
leaseDurationSeconds: 60
managedClusterClientConfigs:
- url: https://api.home.ocplab.com:6443

View File

@@ -0,0 +1,20 @@
apiVersion: cluster.open-cluster-management.io/v1
kind: ManagedCluster
metadata:
annotations:
open-cluster-management/created-via: other
labels:
cloud: Other
cluster.open-cluster-management.io/clusterset: default
gitops: local.hub
color: "3E8635"
local-cluster: "true"
name: local-cluster
velero.io/exclude-from-backup: "true"
vendor: OpenShift
name: local-cluster
spec:
hubAcceptsClient: true
leaseDurationSeconds: 60
managedClusterClientConfigs:
- url: https://api.hub.ocplab.com:6443

View File

@@ -0,0 +1,2 @@
resources:
- policy-cert-expiration.yaml

View File

@@ -0,0 +1,50 @@
apiVersion: policy.open-cluster-management.io/v1
kind: Policy
metadata:
name: certificate-expiration
annotations:
policy.open-cluster-management.io/categories: SC System and Communications Protection
policy.open-cluster-management.io/standards: NIST SP 800-53
policy.open-cluster-management.io/controls: SC-8 Transmission Confidentiality and Integrity
spec:
disabled: false
remediationAction: inform
policy-templates:
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: CertificatePolicy
metadata:
name: policy-certificate
spec:
namespaceSelector:
include:
- default
exclude:
- kube-*
remediationAction: inform
severity: low
minimumDuration: 300h
---
apiVersion: apps.open-cluster-management.io/v1
kind: PlacementRule
metadata:
name: certificate-expiration-placement
spec:
clusterSelector:
matchExpressions: []
clusterConditions:
- status: "True"
type: ManagedClusterConditionAvailable
---
apiVersion: policy.open-cluster-management.io/v1
kind: PlacementBinding
metadata:
name: certificate-expiration-placement
placementRef:
name: certificate-expiration-placement
apiGroup: apps.open-cluster-management.io
kind: PlacementRule
subjects:
- name: certificate-expiration
apiGroup: policy.open-cluster-management.io
kind: Policy

View File

@@ -0,0 +1,2 @@
resources:
- scan-nist-800-53-policy.yaml

View File

@@ -0,0 +1,55 @@
apiVersion: policy.open-cluster-management.io/v1
kind: Policy
metadata:
name: scan-nist-800-53
annotations:
policy.open-cluster-management.io/standards: NIST-CSF
policy.open-cluster-management.io/categories: PR.IP Information Protection Processes and Procedures
policy.open-cluster-management.io/controls: PR.IP-1 Baseline Configuration
spec:
remediationAction: enforce
disabled: false
policy-templates:
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: scan-nist-800-53
spec:
remediationAction: inform
severity: high
object-templates:
- complianceType: mustnothave
objectDefinition:
apiVersion: compliance.openshift.io/v1alpha1
kind: ComplianceCheckResult
metadata:
namespace: openshift-compliance
labels:
compliance.openshift.io/check-status: FAIL
compliance.openshift.io/suite: cis-compliance
---
apiVersion: policy.open-cluster-management.io/v1
kind: PlacementBinding
metadata:
name: binding-scan-nist-800-53
placementRef:
name: placement-scan-nist-800-53
kind: PlacementRule
apiGroup: apps.open-cluster-management.io
subjects:
- name: scan-nist-800-53
kind: Policy
apiGroup: policy.open-cluster-management.io
---
apiVersion: apps.open-cluster-management.io/v1
kind: PlacementRule
metadata:
name: placement-scan-nist-800-53
spec:
clusterConditions:
- status: 'True'
type: ManagedClusterConditionAvailable
clusterSelector:
matchExpressions:
- {key: vendor, operator: In, values: ["OpenShift"]}

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
annotations:
openshift.io/description: ACM Configuration Policies
openshift.io/display-name: ACM Configuration Policies
name: acm-config-policies

View File

@@ -0,0 +1,131 @@
# Sample policy to configure gatekeeper to exclude namespaces from certain processes for all constraints in the cluster
# See: https://github.com/open-policy-agent/gatekeeper/tree/release-3.3#exempting-namespaces-from-gatekeeper
apiVersion: policy.open-cluster-management.io/v1
kind: Policy
metadata:
name: policy-gatekeeper-config-exclude-namespaces
annotations:
policy.open-cluster-management.io/standards: NIST SP 800-53
policy.open-cluster-management.io/categories: CM Configuration Management
policy.open-cluster-management.io/controls: CM-2 Baseline Configuration
spec:
remediationAction: enforce
disabled: false
policy-templates:
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: policy-gatekeeper-config-exclude-namespaces
spec:
remediationAction: enforce # will be overridden by remediationAction in parent policy
severity: low
object-templates:
- complianceType: mustonlyhave
objectDefinition:
apiVersion: config.gatekeeper.sh/v1alpha1
kind: Config
metadata:
name: config
namespace: openshift-gatekeeper-system
spec:
match:
- excludedNamespaces:
- hive
- kube-node-lease
- kube-public
- kube-storage-version-migrator-operator
- kube-system
- open-cluster-management
- open-cluster-management-hub
- open-cluster-management-agent
- open-cluster-management-agent-addon
- openshift
- openshift-apiserver
- openshift-apiserver-operator
- openshift-authentication
- openshift-authentication-operator
- openshift-cloud-credential-operator
- openshift-cluster-csi-drivers
- openshift-cluster-machine-approver
- openshift-cluster-node-tuning-operator
- openshift-cluster-samples-operator
- openshift-cluster-storage-operator
- openshift-cluster-version
- openshift-compliance
- openshift-config
- openshift-config-managed
- openshift-config-operator
- openshift-console
- openshift-console-operator
- openshift-console-user-settings
- openshift-controller-manager
- openshift-controller-manager-operator
- openshift-dns
- openshift-dns-operator
- openshift-etcd
- openshift-etcd-operator
- openshift-gatekeeper-operator
- openshift-gatekeeper-system
- openshift-image-registry
- openshift-infra
- openshift-ingress
- openshift-ingress-canary
- openshift-ingress-operator
- openshift-insights
- openshift-kni-infra
- openshift-kube-apiserver
- openshift-kube-apiserver-operator
- openshift-kube-controller-manager
- openshift-kube-controller-manager-operator
- openshift-kube-scheduler
- openshift-kube-scheduler-operator
- openshift-kube-storage-version-migrator
- openshift-kube-storage-version-migrator-operator
- openshift-kubevirt-infra
- openshift-machine-api
- openshift-machine-config-operator
- openshift-marketplace
- openshift-monitoring
- openshift-multus
- openshift-network-diagnostics
- openshift-network-operator
- openshift-node
- openshift-oauth-apiserver
- openshift-openstack-infra
- openshift-operators
- openshift-operator-lifecycle-manager
- openshift-ovirt-infra
- openshift-ovn-kubernetes
- openshift-sdn
- openshift-service-ca
- openshift-service-ca-operator
- openshift-user-workload-monitoring
- openshift-vsphere-infra
processes:
- '*'
---
apiVersion: policy.open-cluster-management.io/v1
kind: PlacementBinding
metadata:
name: binding-policy-gatekeeper-config-exclude-namespaces
placementRef:
name: placement-policy-gatekeeper-config-exclude-namespaces
kind: PlacementRule
apiGroup: apps.open-cluster-management.io
subjects:
- name: policy-gatekeeper-config-exclude-namespaces
kind: Policy
apiGroup: policy.open-cluster-management.io
---
apiVersion: apps.open-cluster-management.io/v1
kind: PlacementRule
metadata:
name: placement-policy-gatekeeper-config-exclude-namespaces
spec:
clusterConditions:
- status: "True"
type: ManagedClusterConditionAvailable
clusterSelector:
matchExpressions:
- {key: policy, operator: In, values: ["opa"]}

View File

@@ -0,0 +1,139 @@
apiVersion: policy.open-cluster-management.io/v1
kind: Policy
metadata:
name: policy-gatekeeper-operator
annotations:
policy.open-cluster-management.io/standards: NIST SP 800-53
policy.open-cluster-management.io/categories: CM Configuration Management
policy.open-cluster-management.io/controls: CM-2 Baseline Configuration
spec:
remediationAction: enforce
disabled: false
policy-templates:
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: gatekeeper-operator-ns
spec:
remediationAction: enforce
severity: high
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: v1
kind: Namespace
metadata:
name: openshift-gatekeeper-operator
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: gatekeeper-operator-catalog-source
spec:
remediationAction: enforce
severity: high
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: operators.coreos.com/v1alpha1
kind: CatalogSource
metadata:
name: gatekeeper-operator
namespace: openshift-gatekeeper-operator
spec:
displayName: Gatekeeper Operator Upstream
publisher: github.com/font/gatekeeper-operator
sourceType: grpc
image: 'quay.io/gatekeeper/gatekeeper-operator-bundle-index:v0.2.1'
updateStrategy:
registryPoll:
interval: 45m
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: gatekeeper-operator-group
spec:
remediationAction: enforce
severity: high
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
name: gatekeeper-operator
namespace: openshift-gatekeeper-operator
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: gatekeeper-operator-subscription
spec:
remediationAction: enforce
severity: high
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: gatekeeper-operator-sub
namespace: openshift-gatekeeper-operator
spec:
channel: stable
name: gatekeeper-operator
source: gatekeeper-operator
sourceNamespace: openshift-gatekeeper-operator
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: gatekeeper
spec:
remediationAction: enforce
severity: high
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: operator.gatekeeper.sh/v1alpha1
kind: Gatekeeper
metadata:
name: gatekeeper
spec:
audit:
auditChunkSize: 500
logLevel: INFO
replicas: 1
validatingWebhook: Enabled
mutatingWebhook: Disabled
webhook:
emitAdmissionEvents: Enabled
logLevel: INFO
replicas: 2
---
apiVersion: policy.open-cluster-management.io/v1
kind: PlacementBinding
metadata:
name: binding-policy-gatekeeper-operator
placementRef:
name: placement-policy-gatekeeper-operator
kind: PlacementRule
apiGroup: apps.open-cluster-management.io
subjects:
- name: policy-gatekeeper-operator
kind: Policy
apiGroup: policy.open-cluster-management.io
---
apiVersion: apps.open-cluster-management.io/v1
kind: PlacementRule
metadata:
name: placement-policy-gatekeeper-operator
spec:
clusterConditions:
- status: "True"
type: ManagedClusterConditionAvailable
clusterSelector:
matchExpressions:
- {key: policy, operator: In, values: ["opa"]}

View File

@@ -0,0 +1,8 @@
namespace: acm-policies
resources:
- placement.yaml
- managedclustersetbinding.yaml
generators:
- policy-generator-config.yaml

View File

@@ -0,0 +1,7 @@
apiVersion: cluster.open-cluster-management.io/v1beta2
kind: ManagedClusterSetBinding
metadata:
name: global
namespace: acm-policies
spec:
clusterSet: global

View File

@@ -0,0 +1,39 @@
apiVersion: v1
kind: Secret
metadata:
name: eso-token-cluster
namespace: external-secrets
data:
dopplerToken: '{{hub (fromSecret "acm-policies" (printf "eso-token-cluster-%s" .ManagedClusterName) "dopplerToken") hub}}'
---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: doppler-cluster
spec:
provider:
doppler:
auth:
secretRef:
dopplerToken:
name: eso-token-cluster
key: dopplerToken
namespace: external-secrets
conditions:
- namespaceSelector:
matchLabels:
openshift.io/cluster-monitoring: "true"
- namespaces:
- "cert-manager"
- "dev-tools"
- "gitops"
- "openshift-config"
- "openshift-gitops"
- "openshift-monitoring"
- "openshift-pipelines"
- "stackrox"
- "stackrox-secured-cluster-service"
- "tenant-secrets"
- "trusted-profile-analyzer"

View File

@@ -0,0 +1,4 @@
namespace: openshift-gitops
resources:
- prometheus-rules.yaml

View File

@@ -0,0 +1,31 @@
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: argocd-health-alerts
annotations:
policy.open-cluster-management.io/disable-templates: "true"
spec:
groups:
- name: ArgoCD
rules:
- alert: ArgoCDHealthAlert
annotations:
message: ArgoCD application {{ $labels.name }} is not healthy
expr: argocd_app_info{namespace="openshift-gitops", health_status!~"Healthy|Suspended|Progressing|Degraded"} > 0
for: 5m
labels:
severity: warning
- alert: ArgoCDDegradedAlert
annotations:
message: ArgoCD application {{ $labels.name }} is degraded
expr: argocd_app_info{namespace="openshift-gitops", health_status="Degraded"} > 0
for: 5m
labels:
severity: critical
- alert: ArgoCDStuckAlert
annotations:
message: ArgoCD application {{ $labels.name }} is stuck in progressing for more than 10m
expr: argocd_app_info{namespace="openshift-gitops", health_status="Progressing"} > 0
for: 10m
labels:
severity: warning

View File

@@ -0,0 +1,27 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: cluster-config-bootstrap
namespace: openshift-gitops
labels:
gitops.ownedBy: cluster-config
spec:
destination:
namespace: openshift-gitops
server: https://kubernetes.default.svc
project: bootstrap
source:
path: bootstrap/overlays/{{ fromClusterClaim "gitops" }}
repoURL: https://github.com/gnunn-gitops/cluster-config-pins.git
targetRevision: 'HEAD'
syncPolicy:
automated:
prune: false
selfHeal: false
ignoreDifferences:
- group: argoproj.io
kind: Application
managedFieldsManagers:
- argocd-server
jsonPointers:
- /spec/syncPolicy/automated

View File

@@ -0,0 +1,16 @@
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: bootstrap
namespace: openshift-gitops
spec:
clusterResourceWhitelist:
- group: '*'
kind: '*'
description: Project for bootstrap cluster app
destinations:
- namespace: '*'
server: https://kubernetes.default.svc
sourceRepos:
- https://github.com/gnunn-gitops/cluster-config
- https://github.com/gnunn-gitops/cluster-config-pins

View File

@@ -0,0 +1,3 @@
resources:
- bootstrap-appproject.yaml
- bootstrap-app.yaml

View File

@@ -0,0 +1,389 @@
apiVersion: argoproj.io/v1beta1
kind: ArgoCD
metadata:
name: openshift-gitops
namespace: openshift-gitops
spec:
resourceTrackingMethod: annotation
applicationSet: {}
extraConfig:
exec.enabled: "true"
resource.respectRBAC: "normal"
resource.ignoreResourceUpdatesEnabled: 'true'
resource.compareoptions: |
ignoreAggregatedRoles: true
resource.customizations.ignoreResourceUpdates.external-secrets.io_ExternalSecret: |
jsonPointers:
- /status/refreshTime
resource.customizations.ignoreResourceUpdates.ocs.openshift.io_StorageCluster: |
jsonPointers:
- /status
- /metadata/resourceVersion
resource.customizations.ignoreResourceUpdates.ocs.openshift.io_StorageSystem: |
jsonPointers:
- /status
- /metadata/resourceVersion
resource.customizations.ignoreResourceUpdates.noobaa.io_Noobaa: |
jsonPointers:
- /status
- /metadata/resourceVersion
resource.customizations.ignoreResourceUpdates.noobaa.io_BackingStore: |
jsonPointers:
- /status
- /metadata/resourceVersion
accounts.admin: apiKey, login
ui.cssurl: 'https://gnunn-gitops.github.io/cluster-config/themes/{{ fromClusterClaim "gitops" }}/custom-cluster.css'
kustomizeBuildOptions: "--enable-helm --enable-alpha-plugins"
oidcConfig: |
name: Keycloak
issuer: https://sso.ocplab.com/realms/ocplab
clientID: argocd
clientSecret: $oidc.keycloak.clientSecret
requestedScopes: ["openid", "profile", "email", "groups"]
controller:
extraCommandArgs:
- '--persist-resource-health=false'
resources:
limits:
memory: 4Gi
requests:
cpu: 1000m
memory: 3Gi
monitoring:
enabled: true
repo:
sidecarContainers:
- name: setenv-cmp-plugin
command: [/var/run/argocd/argocd-cmp-server]
env:
- name: KUSTOMIZE_PLUGIN_HOME
value: /etc/kustomize/plugin
- name: INFRASTRUCTURE_ID
value: '{{ (lookup "config.openshift.io/v1" "Infrastructure" "" "cluster").status.infrastructureName }}'
- name: CLUSTER_ID
value: '{{ (lookup "config.openshift.io/v1" "ClusterVersion" "" "version").spec.clusterID }}'
- name: CLUSTER_GITOPS_NAME
value: '{{ fromClusterClaim "gitops" }}'
- name: CLUSTER_NAME
value: '{{ fromClusterClaim "name" }}'
- name: SUB_DOMAIN
value: '{{ (lookup "config.openshift.io/v1" "Ingress" "openshift-ingress" "cluster").spec.domain }}'
- name: BASE_DOMAIN
value: '{{ (lookup "config.openshift.io/v1" "DNS" "" "cluster").spec.baseDomain }}'
- name: COLOR
value: '{{ default "0066CC" (fromClusterClaim "color") }}'
image: quay.io/gnunn/tools:latest
imagePullPolicy: Always
securityContext:
runAsNonRoot: true
volumeMounts:
- mountPath: /var/run/argocd
name: var-files
- mountPath: /home/argocd/cmp-server/plugins
name: plugins
- mountPath: /tmp
name: tmp
- mountPath: /home/argocd/cmp-server/config/plugin.yaml
subPath: plugin.yaml
name: setenv-cmp-plugin
volumes:
- configMap:
name: setenv-cmp-plugin
name: setenv-cmp-plugin
resources:
limits:
cpu: '1'
memory: 1.5Gi
requests:
cpu: 250m
memory: 768Mi
redis:
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
server:
insecure: true
# host: 'openshift-gitops-server-openshift-gitops.{{ (lookup "config.openshift.io/v1" "Ingress" "openshift-ingress" "cluster").spec.domain }}'
route:
enabled: true
tls:
termination: edge
insecureEdgeTerminationPolicy: Redirect
notifications:
enabled: true
resourceActions:
- group: compliance.openshift.io
kind: ComplianceScan
action: |
discovery.lua: |
local actions = {}
local enabled = false
if obj.status ~= nil and obj.status.phase == "DONE" then
enabled = true
end
actions["rescan"] = {["disabled"] = not(enabled)}
return actions
definitions:
- name: rescan
action.lua: |
if obj.metadata.annotations == nil then
obj.metadata.annotations = {}
end
obj.metadata.annotations["compliance.openshift.io/rescan"] = ""
return obj
resourceHealthChecks:
- group: argoproj.io
kind: Application
check: |
hs = {}
hs.status = "Progressing"
hs.message = ""
if obj.status ~= nil then
if obj.status.health ~= nil then
hs.status = obj.status.health.status
hs.message = obj.status.health.message
end
end
return hs
- group: argoproj.io
kind: RolloutManager
check: |
hs = {}
if obj.status ~= nil then
if obj.status.conditions ~= nil then
for _, condition in ipairs(obj.status.conditions) do
hs.message = condition.message
break
end
end
if obj.status.phase ~= nil then
if obj.status.phase == "Failure" then
hs.status = "Degraded"
return hs
elseif obj.status.phase == "Available" then
hs.status = "Healthy"
return hs
elseif obj.status.phase == "Pending" then
hs.status = "Progressing"
return hs
end
end
hs.status = "Progressing"
hs.message = "Waiting for operator to update status"
return hs
end
- group: operators.coreos.com
kind: Subscription
check: |
health_status = {}
if obj.status ~= nil then
if obj.status.conditions ~= nil then
numDegraded = 0
numPending = 0
numSuspended = 0
msg = ""
for i, condition in pairs(obj.status.conditions) do
msg = msg .. i .. ": " .. condition.type .. " | " .. condition.status .. " | " .. (condition.reason and condition.reason or '') .. "\n"
if condition.type == "InstallPlanPending" and condition.status == "True" then
if condition.reason == "RequiresApproval" then
numSuspended = numSuspended + 1
else
numPending = numPending + 1
end
elseif (condition.type == "InstallPlanMissing" and condition.reason ~= "ReferencedInstallPlanNotFound") then
numDegraded = numDegraded + 1
elseif (condition.type == "CatalogSourcesUnhealthy" or condition.type == "InstallPlanFailed") and condition.status == "True" then
numDegraded = numDegraded + 1
elseif (condition.type == "ResolutionFailed" and condition.reason ~= "ConstraintsNotSatisfiable") then
numDegraded = numDegraded + 1
elseif (condition.type == 'ChannelDeprecated' and condition.status == "True") then
numDegraded = numDegraded + 1
end
end
if numDegraded > 0 then
health_status.status = "Degraded"
health_status.message = msg
return health_status
elseif numSuspended > 0 then
health_status.status = "Suspended"
health_status.message = msg
return health_status
elseif numPending > 0 then
health_status.status = "Progressing"
health_status.message = "An install plan for a subscription is pending installation"
return health_status
else
health_status.status = "Healthy"
health_status.message = msg
return health_status
end
end
end
health_status.status = "Progressing"
health_status.message = "An install plan for a subscription is pending installation"
return health_status
- group: operators.coreos.com
kind: InstallPlan
check: |
hs = {}
if obj.status ~= nil then
if obj.status.phase ~= nil then
if obj.status.phase == "Complete" then
hs.status = "Healthy"
hs.message = obj.status.phase
return hs
elseif obj.status.phase == "RequiresApproval" then
hs.status = "Suspended"
hs.message = obj.status.phase
return hs
else
hs.status = "Progressing"
hs.message = obj.status.phase
return hs
end
end
end
hs.status = "Progressing"
hs.message = "Waiting for InstallPlan to complete"
return hs
- group: platform.stackrox.io
kind: Central
check: |
hs = {}
if obj.status ~= nil and obj.status.conditions ~= nil then
for i, condition in ipairs(obj.status.conditions) do
if condition.status == "True" or condition.reason == "InstallSuccessful" or condition.reason == "UpgradeSuccessful" then
hs.status = "Healthy"
hs.message = "Install Successful"
return hs
end
end
end
hs.status = "Progressing"
hs.message = "Waiting for Central to deploy."
return hs
- group: image.openshift.io
kind: ImageStream
check: |
hs = {}
hs.status = "Progressing"
hs.message = ""
if obj.status ~= nil then
if obj.status.tags ~= nil then
numTags = 0
for _ , item in pairs(obj.status.tags) do
numTags = numTags + 1
numItems = 0
if item.tags ~= nil then
for _ , item in pairs(item.tags) do
numItems = numItems + 1
end
if numItems == 0 then
return hs
end
end
end
if numTags > 0 then
hs.status = "Healthy"
hs.message = "ImageStream has tags resolved"
return hs
end
end
end
return hs
- group: build.openshift.io
kind: Build
check: |
hs = {}
if obj.status ~= nil then
if obj.status.phase ~= nil then
if obj.status.phase == "Complete" then
hs.status = "Healthy"
hs.message = obj.status.phase
return hs
end
end
end
hs.status = "Progressing"
hs.message = "Waiting for Build to complete"
return hs
- kind: PersistentVolumeClaim
check: |
hs = {}
if obj.status ~= nil then
if obj.status.phase ~= nil then
if obj.status.phase == "Pending" then
hs.status = "Healthy"
hs.message = obj.status.phase
return hs
end
if obj.status.phase == "Bound" then
hs.status = "Healthy"
hs.message = obj.status.phase
return hs
end
end
end
hs.status = "Progressing"
hs.message = "Waiting for PVC"
return hs
resourceIgnoreDifferences:
resourceIdentifiers:
- group: route.openshift.io
kind: Route
customization:
jsonPointers:
- /status/ingress
- /metadata/annotations
- group: quay.redhat.com
kind: QuayRegistry
customization:
jsonPointers:
- /status/ingress
- group: cluster.open-cluster-management.io
kind: ManagedCluster
customization:
jsonPointers:
- /spec/managedClusterClientConfigs
resourceExclusions: |
- apiGroups:
- tekton.dev
clusters:
- '*'
kinds:
- TaskRun
- PipelineRun
- apiGroups:
- operator.tekton.dev
clusters:
- '*'
kinds:
- TektonAddon
- TektonInstallerSet
- apiGroups:
- compliance.openshift.io
kinds:
- ComplianceCheckResult
- ComplianceRemediation
- apiGroups:
- policy.open-cluster-management.io
kinds:
- ConfigurationPolicy
- apiGroups:
- noobaa.io
kinds:
- NooBaa
- BucketClass
ha:
enabled: false
rbac:
defaultPolicy: 'role:none'
policy: |
p, role:none, *, *, */*, deny
g, system:cluster-admins, role:admin
g, cluster-admins, role:admin
scopes: "[groups]"

View File

@@ -0,0 +1,3 @@
resources:
- setenv-cmp-plugin-cm.yaml
- argocd.yaml

View File

@@ -0,0 +1,19 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: setenv-cmp-plugin
namespace: openshift-gitops
data:
plugin.yaml: |
apiVersion: argoproj.io/v1alpha1
kind: ConfigManagementPlugin
metadata:
name: setenv-cmp-plugin
spec:
init:
command: [sh, -c, 'echo "Initializing setenv-plugin-cmp..."']
generate:
command:
- sh
- "-c"
- "set -o pipefail && kustomize build --enable-helm --enable-alpha-plugins . | envsub"

View File

@@ -0,0 +1,2 @@
resources:
- notifications-config.yaml

View File

@@ -0,0 +1,256 @@
# No longer needed with NotificationsConfig introduced in 1.12
apiVersion: v1
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/managed-by: openshift-gitops
app.kubernetes.io/name: argocd-notifications-cm
app.kubernetes.io/part-of: argocd
annotation:
policy.open-cluster-management.io/disable-templates: "true"
name: argocd-notifications-cm
namespace: openshift-gitops
data:
service.slack: |
token: $slack-token
template.app-deployed: |-
slack:
attachments: |
[{
"title": "{{ .app.metadata.name}}",
"title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#18be52",
"fields": [
{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
},
{
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
},
{
"title": "Revision",
"value": "{{.app.status.sync.revision}}",
"short": true
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"title": "{{$c.type}}",
"value": "{{$c.message}}",
"short": true
}
{{end}}
]
}]
deliveryPolicy: Post
groupingKey: ""
notifyBroadcast: false
template.app-health-degraded: |-
slack:
attachments: |
[{
"title": "{{ .app.metadata.name}}",
"title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#f4c030",
"fields": [
{
"title": "Health Status",
"value": "{{.app.status.health.status}}",
"short": true
},
{
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"title": "{{$c.type}}",
"value": "{{$c.message}}",
"short": true
}
{{end}}
]
}]
deliveryPolicy: Post
groupingKey: ""
notifyBroadcast: false
template.app-sync-failed: |-
slack:
attachments: |
[{
"title": "{{ .app.metadata.name}}",
"title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#E96D76",
"fields": [
{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
},
{
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"title": "{{$c.type}}",
"value": "{{$c.message}}",
"short": true
}
{{end}}
]
}]
deliveryPolicy: Post
groupingKey: ""
notifyBroadcast: false
template.app-sync-running: |-
slack:
attachments: |
[{
"title": "{{ .app.metadata.name}}",
"title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#0DADEA",
"fields": [
{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
},
{
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"title": "{{$c.type}}",
"value": "{{$c.message}}",
"short": true
}
{{end}}
]
}]
deliveryPolicy: Post
groupingKey: ""
notifyBroadcast: false
template.app-sync-status-unknown: |-
slack:
attachments: |
[{
"title": "{{ .app.metadata.name}}",
"title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#E96D76",
"fields": [
{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
},
{
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"title": "{{$c.type}}",
"value": "{{$c.message}}",
"short": true
}
{{end}}
]
}]
deliveryPolicy: Post
groupingKey: ""
notifyBroadcast: false
template.app-sync-succeeded: |-
slack:
attachments: |
[{
"title": "{{ .app.metadata.name}}",
"title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#18be52",
"fields": [
{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
},
{
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"title": "{{$c.type}}",
"value": "{{$c.message}}",
"short": true
}
{{end}}
]
}]
deliveryPolicy: Post
groupingKey: ""
notifyBroadcast: false
# trigger.on-created: |-
# - description: Application is created.
# oncePer: app.metadata.name
# send:
# - app-created
# when: "true"
# trigger.on-deleted: |-
# - description: Application is deleted.
# oncePer: app.metadata.name
# send:
# - app-deleted
# when: app.metadata.deletionTimestamp != nil
trigger.on-deployed: |-
- description: Application is synced and healthy. Triggered once per commit.
oncePer: app.status.operationState.syncResult.revision
send:
- app-deployed
when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status
== 'Healthy'
trigger.on-health-degraded: |-
- description: Application has degraded
send:
- app-health-degraded
when: app.status.health.status == 'Degraded'
trigger.on-sync-failed: |-
- description: Application syncing has failed
send:
- app-sync-failed
when: app.status.operationState.phase in ['Error', 'Failed']
trigger.on-sync-running: |-
- description: Application is being synced
send:
- app-sync-running
when: app.status.operationState.phase in ['Running']
trigger.on-sync-status-unknown: |-
- description: Application status is 'Unknown'
end:
- app-sync-status-unknown
when: app.status.sync.status == 'Unknown'
trigger.on-sync-succeeded: |-
- description: Application syncing has succeeded
send:
- app-sync-succeeded
when: app.status.operationState.phase in ['Succeeded']

View File

@@ -0,0 +1,540 @@
apiVersion: argoproj.io/v1alpha1
kind: NotificationsConfiguration
metadata:
name: default-notifications-configuration
namespace: openshift-gitops
spec:
services:
service.slack: |
token: $slack-token
templates:
template.app-created: |-
email:
subject: Application {{.app.metadata.name}} has been created.
message: Application {{.app.metadata.name}} has been created.
teams:
title: Application {{.app.metadata.name}} has been created.
template.app-deleted: |-
email:
subject: Application {{.app.metadata.name}} has been deleted.
message: Application {{.app.metadata.name}} has been deleted.
teams:
title: Application {{.app.metadata.name}} has been deleted.
template.app-deployed: |-
email:
subject: New version of an application {{.app.metadata.name}} is up and running.
message: |
{{if eq .serviceType "slack"}}:white_check_mark:{{end}} Application {{.app.metadata.name}} is now running new version of deployments manifests.
slack:
attachments: |
[{
"title": "{{ .app.metadata.name}}",
"title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#18be52",
"fields": [
{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
},
{
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
},
{
"title": "Revision",
"value": "{{.app.status.sync.revision}}",
"short": true
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"title": "{{$c.type}}",
"value": "{{$c.message}}",
"short": true
}
{{end}}
]
}]
deliveryPolicy: Post
groupingKey: ""
notifyBroadcast: false
teams:
facts: |
[{
"name": "Sync Status",
"value": "{{.app.status.sync.status}}"
},
{
"name": "Repository",
"value": "{{.app.spec.source.repoURL}}"
},
{
"name": "Revision",
"value": "{{.app.status.sync.revision}}"
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"name": "{{$c.type}}",
"value": "{{$c.message}}"
}
{{end}}
]
potentialAction: |-
[{
"@type":"OpenUri",
"name":"Operation Application",
"targets":[{
"os":"default",
"uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}"
}]
},
{
"@type":"OpenUri",
"name":"Open Repository",
"targets":[{
"os":"default",
"uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
}]
}]
themeColor: '#000080'
title: New version of an application {{.app.metadata.name}} is up and running.
template.app-health-degraded: |-
email:
subject: Application {{.app.metadata.name}} has degraded.
message: |
{{if eq .serviceType "slack"}}:exclamation:{{end}} Application {{.app.metadata.name}} has degraded.
Application details: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}.
slack:
attachments: |
[{
"title": "{{ .app.metadata.name}}",
"title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#f4c030",
"fields": [
{
"title": "Health Status",
"value": "{{.app.status.health.status}}",
"short": true
},
{
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"title": "{{$c.type}}",
"value": "{{$c.message}}",
"short": true
}
{{end}}
]
}]
deliveryPolicy: Post
groupingKey: ""
notifyBroadcast: false
teams:
facts: |
[{
"name": "Health Status",
"value": "{{.app.status.health.status}}"
},
{
"name": "Repository",
"value": "{{.app.spec.source.repoURL}}"
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"name": "{{$c.type}}",
"value": "{{$c.message}}"
}
{{end}}
]
potentialAction: |
[{
"@type":"OpenUri",
"name":"Open Application",
"targets":[{
"os":"default",
"uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}"
}]
},
{
"@type":"OpenUri",
"name":"Open Repository",
"targets":[{
"os":"default",
"uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
}]
}]
themeColor: '#FF0000'
title: Application {{.app.metadata.name}} has degraded.
template.app-sync-failed: |-
email:
subject: Failed to sync application {{.app.metadata.name}}.
message: |
{{if eq .serviceType "slack"}}:exclamation:{{end}} The sync operation of application {{.app.metadata.name}} has failed at {{.app.status.operationState.finishedAt}} with the following error: {{.app.status.operationState.message}}
Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true .
slack:
attachments: |
[{
"title": "{{ .app.metadata.name}}",
"title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#E96D76",
"fields": [
{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
},
{
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"title": "{{$c.type}}",
"value": "{{$c.message}}",
"short": true
}
{{end}}
]
}]
deliveryPolicy: Post
groupingKey: ""
notifyBroadcast: false
teams:
facts: |
[{
"name": "Sync Status",
"value": "{{.app.status.sync.status}}"
},
{
"name": "Failed at",
"value": "{{.app.status.operationState.finishedAt}}"
},
{
"name": "Repository",
"value": "{{.app.spec.source.repoURL}}"
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"name": "{{$c.type}}",
"value": "{{$c.message}}"
}
{{end}}
]
potentialAction: |-
[{
"@type":"OpenUri",
"name":"Open Operation",
"targets":[{
"os":"default",
"uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true"
}]
},
{
"@type":"OpenUri",
"name":"Open Repository",
"targets":[{
"os":"default",
"uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
}]
}]
themeColor: '#FF0000'
title: Failed to sync application {{.app.metadata.name}}.
template.app-sync-running: |-
email:
subject: Start syncing application {{.app.metadata.name}}.
message: |
The sync operation of application {{.app.metadata.name}} has started at {{.app.status.operationState.startedAt}}.
Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true .
slack:
attachments: |
[{
"title": "{{ .app.metadata.name}}",
"title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#0DADEA",
"fields": [
{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
},
{
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"title": "{{$c.type}}",
"value": "{{$c.message}}",
"short": true
}
{{end}}
]
}]
deliveryPolicy: Post
groupingKey: ""
notifyBroadcast: false
teams:
facts: |
[{
"name": "Sync Status",
"value": "{{.app.status.sync.status}}"
},
{
"name": "Started at",
"value": "{{.app.status.operationState.startedAt}}"
},
{
"name": "Repository",
"value": "{{.app.spec.source.repoURL}}"
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"name": "{{$c.type}}",
"value": "{{$c.message}}"
}
{{end}}
]
potentialAction: |-
[{
"@type":"OpenUri",
"name":"Open Operation",
"targets":[{
"os":"default",
"uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true"
}]
},
{
"@type":"OpenUri",
"name":"Open Repository",
"targets":[{
"os":"default",
"uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
}]
}]
title: Start syncing application {{.app.metadata.name}}.
template.app-sync-status-unknown: |-
email:
subject: Application {{.app.metadata.name}} sync status is 'Unknown'
message: |
{{if eq .serviceType "slack"}}:exclamation:{{end}} Application {{.app.metadata.name}} sync is 'Unknown'.
Application details: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}.
{{if ne .serviceType "slack"}}
{{range $c := .app.status.conditions}}
* {{$c.message}}
{{end}}
{{end}}
slack:
attachments: |
[{
"title": "{{ .app.metadata.name}}",
"title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#E96D76",
"fields": [
{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
},
{
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"title": "{{$c.type}}",
"value": "{{$c.message}}",
"short": true
}
{{end}}
]
}]
deliveryPolicy: Post
groupingKey: ""
notifyBroadcast: false
teams:
facts: |
[{
"name": "Sync Status",
"value": "{{.app.status.sync.status}}"
},
{
"name": "Repository",
"value": "{{.app.spec.source.repoURL}}"
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"name": "{{$c.type}}",
"value": "{{$c.message}}"
}
{{end}}
]
potentialAction: |-
[{
"@type":"OpenUri",
"name":"Open Application",
"targets":[{
"os":"default",
"uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}"
}]
},
{
"@type":"OpenUri",
"name":"Open Repository",
"targets":[{
"os":"default",
"uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
}]
}]
title: Application {{.app.metadata.name}} sync status is 'Unknown'
template.app-sync-succeeded: |-
email:
subject: Application {{.app.metadata.name}} has been successfully synced.
message: |
{{if eq .serviceType "slack"}}:white_check_mark:{{end}} Application {{.app.metadata.name}} has been successfully synced at {{.app.status.operationState.finishedAt}}.
Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true .
slack:
attachments: |
[{
"title": "{{ .app.metadata.name}}",
"title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#18be52",
"fields": [
{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
},
{
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"title": "{{$c.type}}",
"value": "{{$c.message}}",
"short": true
}
{{end}}
]
}]
deliveryPolicy: Post
groupingKey: ""
notifyBroadcast: false
teams:
facts: |
[{
"name": "Sync Status",
"value": "{{.app.status.sync.status}}"
},
{
"name": "Synced at",
"value": "{{.app.status.operationState.finishedAt}}"
},
{
"name": "Repository",
"value": "{{.app.spec.source.repoURL}}"
}
{{range $index, $c := .app.status.conditions}}
{{if not $index}},{{end}}
{{if $index}},{{end}}
{
"name": "{{$c.type}}",
"value": "{{$c.message}}"
}
{{end}}
]
potentialAction: |-
[{
"@type":"OpenUri",
"name":"Operation Details",
"targets":[{
"os":"default",
"uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true"
}]
},
{
"@type":"OpenUri",
"name":"Open Repository",
"targets":[{
"os":"default",
"uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
}]
}]
themeColor: '#000080'
title: Application {{.app.metadata.name}} has been successfully synced
triggers:
trigger.on-created: |-
- description: Application is created.
oncePer: app.metadata.name
send:
- app-created
when: "true"
trigger.on-deleted: |-
- description: Application is deleted.
oncePer: app.metadata.name
send:
- app-deleted
when: app.metadata.deletionTimestamp != nil
trigger.on-deployed: |-
- description: Application is synced and healthy. Triggered once per commit.
oncePer: app.status.operationState.syncResult.revision
send:
- app-deployed
when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status
== 'Healthy'
trigger.on-health-degraded: |-
- description: Application has degraded
send:
- app-health-degraded
when: app.status.health.status == 'Degraded'
trigger.on-sync-failed: |-
- description: Application syncing has failed
send:
- app-sync-failed
when: app.status.operationState.phase in ['Error', 'Failed']
trigger.on-sync-running: |-
- description: Application is being synced
send:
- app-sync-running
when: app.status.operationState.phase in ['Running']
trigger.on-sync-status-unknown: |-
- description: Application status is 'Unknown'
send:
- app-sync-status-unknown
when: app.status.sync.status == 'Unknown'
trigger.on-sync-succeeded: |-
- description: Application syncing has succeeded
send:
- app-sync-succeeded
when: app.status.operationState.phase in ['Succeeded']

View File

@@ -0,0 +1,20 @@
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: argocd-secret
namespace: openshift-gitops
spec:
data:
- remoteRef:
conversionStrategy: Default
decodingStrategy: None
key: OIDC_ARGOCD
secretKey: oidc.keycloak.clientSecret
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: doppler-cluster
target:
creationPolicy: Merge
deletionPolicy: Retain
name: argocd-secret

View File

@@ -0,0 +1,3 @@
resources:
- argocd-external-secret.yaml
- notifications-secret.yaml

View File

@@ -0,0 +1,17 @@
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: argocd-notifications-secret
namespace: openshift-gitops
spec:
secretStoreRef:
kind: ClusterSecretStore
name: doppler-cluster
target:
name: argocd-notifications-secret
creationPolicy: Merge
deletionPolicy: Retain
data:
- secretKey: slack-token
remoteRef:
key: GITOPS_SLACK_TOKEN

View File

@@ -0,0 +1,12 @@
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: argocd-application-controller-cluster-admin
subjects:
- kind: ServiceAccount
name: openshift-gitops-argocd-application-controller
namespace: openshift-gitops
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin

View File

@@ -0,0 +1,39 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
name: gitops-controller
aggregationRule:
clusterRoleSelectors:
- matchLabels:
gitops/aggregate-to-controller: "true"
rules: []
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gitops-controller-admin
labels:
gitops/aggregate-to-controller: "true"
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rules: []
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gitops-controller-view
labels:
gitops/aggregate-to-controller: "true"
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- get
- list
- watch

View File

@@ -0,0 +1,53 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gitops-server
rules:
- verbs:
- get
- patch
- delete
apiGroups:
- '*'
resources:
- '*'
- verbs:
- create
- get
- list
- watch
- update
- patch
- delete
apiGroups:
- ''
resources:
- secrets
- configmaps
- verbs:
- create
- get
- list
- watch
- update
- delete
- patch
apiGroups:
- argoproj.io
resources:
- applications
- appprojects
- applicationsets
- verbs:
- create
- list
apiGroups:
- ''
resources:
- events
- apiGroups:
- ""
resources:
- pods/exec
verbs:
- create

View File

@@ -0,0 +1,8 @@
resources:
- sub-namespace.yaml
- sub-operatorgroup.yaml
- subscription.yaml
- namespace.yaml
- gitops-controller-rbac.yaml
- gitops-server-rbac.yaml
- cluster-admin-rolebinding.yaml

View File

@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
labels:
openshift.io/cluster-monitoring: "true"
name: openshift-gitops

View File

@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
labels:
openshift.io/cluster-monitoring: "true"
name: openshift-gitops-operator

View File

@@ -0,0 +1,7 @@
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
name: gitops-operators
namespace: openshift-gitops-operator
spec:
upgradeStrategy: Default

View File

@@ -0,0 +1,19 @@
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: openshift-gitops-operator
namespace: openshift-gitops-operator
spec:
config:
env:
- name: ARGOCD_CLUSTER_CONFIG_NAMESPACES
value: openshift-gitops, gitops
- name: CONTROLLER_CLUSTER_ROLE
value: gitops-controller
- name: SERVER_CLUSTER_ROLE
value: gitops-server
channel: gitops-1.15
installPlanApproval: Automatic
name: openshift-gitops-operator
source: redhat-operators
sourceNamespace: openshift-marketplace

View File

@@ -0,0 +1,28 @@
apiVersion: cluster.open-cluster-management.io/v1beta1
kind: Placement
metadata:
name: placement-policy-gitops
namespace: acm-policies
spec:
clusterSets:
- global
predicates:
- requiredClusterSelector:
labelSelector:
matchExpressions:
- key: gitops
operator: Exists
---
apiVersion: policy.open-cluster-management.io/v1
kind: PlacementBinding
metadata:
name: binding-policy-gitops
namespace: acm-policies
placementRef:
apiGroup: cluster.open-cluster-management.io
kind: Placement
name: placement-policy-gitops
subjects:
- apiGroup: policy.open-cluster-management.io
kind: PolicySet
name: gitops

View File

@@ -0,0 +1,60 @@
apiVersion: policy.open-cluster-management.io/v1
kind: PolicyGenerator
metadata:
name: gitops-policy-generator
policyDefaults:
namespace: acm-policies
remediationAction: enforce
placementBindingDefaults:
name: "binding-policy-gitops"
policies:
- name: policy-eso-secret-store
configurationPolicyAnnotations:
apps.open-cluster-management.io/reconcile-option: replace
manifests:
- path: manifests/eso/base/
- name: policy-gitops-subscription
remediationAction: enforce
manifests:
- path: manifests/gitops-subscription/base/
- name: policy-gitops-instance
# Needed to fix issue with merging lists resulting in duplicates
configurationPolicyAnnotations:
apps.open-cluster-management.io/reconcile-option: replace
complianceType: "mustonlyhave"
manifests:
- path: manifests/gitops-instance/base/
- name: policy-gitops-bootstrap
manifests:
- path: manifests/gitops-bootstrap/base/
- name: policy-gitops-notifications
remediationAction: enforce
manifests:
- path: manifests/gitops-notifications/base/
configurationPolicyAnnotations:
policy.open-cluster-management.io/disable-templates: "true"
- name: policy-gitops-alerting
remediationAction: enforce
manifests:
- path: manifests/gitops-alerting/base/
configurationPolicyAnnotations:
policy.open-cluster-management.io/disable-templates: "true"
# Put ESO secrets into separate policy since it's chicken and egg
# Not being used since I have a global policy that does this but included for example
# - name: policy-gitops-secrets
# remediationAction: enforce
# manifests:
# - path: manifests/gitops-secrets/base/
policySets:
- name: "gitops"
description: "Policy for bootstrapping cluster with gitops"
policies:
- policy-eso-secret-store
- policy-gitops-subscription
- policy-gitops-instance
- policy-gitops-bootstrap
- policy-gitops-notifications
- policy-gitops-alerting
generatePolicySetPlacement: false
placement:
name: placement-policy-gitops

View File

@@ -0,0 +1,459 @@
apiVersion: policy.open-cluster-management.io/v1
kind: Policy
metadata:
name: policy-gatekeeper-livenessprobe
annotations:
policy.open-cluster-management.io/standards: NIST SP 800-53
policy.open-cluster-management.io/categories: CM Configuration Management
policy.open-cluster-management.io/controls: CM-2 Baseline Configuration
spec:
remediationAction: enforce
disabled: false
policy-templates:
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: policy-gatekeeper-containerlivenessprobenotset
spec:
remediationAction: enforce
severity: low
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
creationTimestamp: null
name: containerlivenessprobenotset
spec:
crd:
spec:
names:
kind: ContainerLivenessprobeNotset
targets:
- libs:
- |
package lib.konstraint
default is_gatekeeper = false
is_gatekeeper {
has_field(input, "review")
has_field(input.review, "object")
}
object = input {
not is_gatekeeper
}
object = input.review.object {
is_gatekeeper
}
format(msg) = gatekeeper_format {
is_gatekeeper
gatekeeper_format = {"msg": msg}
}
format(msg) = msg {
not is_gatekeeper
}
name = object.metadata.name
kind = object.kind
has_field(obj, field) {
obj[field]
}
missing_field(obj, field) = true {
obj[field] == ""
}
missing_field(obj, field) = true {
not has_field(obj, field)
}
is_service {
lower(kind) == "service"
}
is_statefulset {
lower(kind) == "statefulset"
}
is_daemonset {
lower(kind) == "daemonset"
}
is_deployment {
lower(kind) == "deployment"
}
is_pod {
lower(kind) == "pod"
}
is_namespace {
lower(kind) == "namespace"
}
is_workload {
containers[_]
}
pod_containers(pod) = all_containers {
keys = {"containers", "initContainers"}
all_containers = [c | keys[k]; c = pod.spec[k][_]]
}
containers[container] {
pods[pod]
all_containers = pod_containers(pod)
container = all_containers[_]
}
containers[container] {
all_containers = pod_containers(object)
container = all_containers[_]
}
container_images[image] {
containers[container]
image = container.image
}
container_images[image] {
image = object.spec.image
}
split_image(image) = [image, "latest"] {
not contains(image, ":")
}
split_image(image) = [image_name, tag] {
[image_name, tag] = split(image, ":")
}
pods[pod] {
is_statefulset
pod = object.spec.template
}
pods[pod] {
is_daemonset
pod = object.spec.template
}
pods[pod] {
is_deployment
pod = object.spec.template
}
pods[pod] {
is_pod
pod = object
}
volumes[volume] {
pods[pod]
volume = pod.spec.volumes[_]
}
mem_multiple("E") = 1000000000000000000000 { true }
mem_multiple("P") = 1000000000000000000 { true }
mem_multiple("T") = 1000000000000000 { true }
mem_multiple("G") = 1000000000000 { true }
mem_multiple("M") = 1000000000 { true }
mem_multiple("k") = 1000000 { true }
mem_multiple("") = 1000 { true }
mem_multiple("m") = 1 { true }
mem_multiple("Ki") = 1024000 { true }
mem_multiple("Mi") = 1048576000 { true }
mem_multiple("Gi") = 1073741824000 { true }
mem_multiple("Ti") = 1099511627776000 { true }
mem_multiple("Pi") = 1125899906842624000 { true }
mem_multiple("Ei") = 1152921504606846976000 { true }
get_suffix(mem) = suffix {
not is_string(mem)
suffix := ""
}
get_suffix(mem) = suffix {
is_string(mem)
count(mem) > 0
suffix := substring(mem, count(mem) - 1, -1)
mem_multiple(suffix)
}
get_suffix(mem) = suffix {
is_string(mem)
count(mem) > 1
suffix := substring(mem, count(mem) - 2, -1)
mem_multiple(suffix)
}
get_suffix(mem) = suffix {
is_string(mem)
count(mem) > 1
not mem_multiple(substring(mem, count(mem) - 1, -1))
not mem_multiple(substring(mem, count(mem) - 2, -1))
suffix := ""
}
get_suffix(mem) = suffix {
is_string(mem)
count(mem) == 1
not mem_multiple(substring(mem, count(mem) - 1, -1))
suffix := ""
}
get_suffix(mem) = suffix {
is_string(mem)
count(mem) == 0
suffix := ""
}
canonify_mem(orig) = new {
is_number(orig)
new := orig * 1000
}
canonify_mem(orig) = new {
not is_number(orig)
suffix := get_suffix(orig)
raw := replace(orig, suffix, "")
re_match("^[0-9]+$", raw)
new := to_number(raw) * mem_multiple(suffix)
}
canonify_storage(orig) = new {
is_number(orig)
new := orig
}
canonify_storage(orig) = new {
not is_number(orig)
suffix := get_suffix(orig)
raw := replace(orig, suffix, "")
re_match("^[0-9]+$", raw)
new := to_number(raw) * mem_multiple(suffix)
}
canonify_cpu(orig) = new {
is_number(orig)
new := orig * 1000
}
canonify_cpu(orig) = new {
not is_number(orig)
endswith(orig, "m")
new := to_number(replace(orig, "m", ""))
}
canonify_cpu(orig) = new {
not is_number(orig)
not endswith(orig, "m")
re_match("^[0-9]+$", orig)
new := to_number(orig) * 1000
}
dropped_capability(container, cap) {
container.securityContext.capabilities.drop[_] == cap
}
added_capability(container, cap) {
container.securityContext.capabilities.add[_] == cap
}
no_read_only_filesystem(c) {
not has_field(c, "securityContext")
}
no_read_only_filesystem(c) {
has_field(c, "securityContext")
not has_field(c.securityContext, "readOnlyRootFilesystem")
}
priviledge_escalation_allowed(c) {
not has_field(c, "securityContext")
}
priviledge_escalation_allowed(c) {
has_field(c, "securityContext")
has_field(c.securityContext, "allowPrivilegeEscalation")
}
- |-
package lib.openshift
import data.lib.konstraint
is_deploymentconfig {
lower(konstraint.object.apiVersion) == "apps.openshift.io/v1"
lower(konstraint.object.kind) == "deploymentconfig"
}
is_route {
lower(konstraint.object.apiVersion) == "route.openshift.io/v1"
lower(konstraint.object.kind) == "route"
}
is_workload_kind {
is_deploymentconfig
}
is_workload_kind {
konstraint.is_statefulset
}
is_workload_kind {
konstraint.is_daemonset
}
is_workload_kind {
konstraint.is_deployment
}
is_all_kind {
is_workload_kind
}
is_all_kind {
konstraint.is_service
}
is_all_kind {
is_route
}
pods[pod] {
is_deploymentconfig
pod = konstraint.object.spec.template
}
pods[pod] {
pod = konstraint.pods[_]
}
containers[container] {
pods[pod]
all_containers = konstraint.pod_containers(pod)
container = all_containers[_]
}
containers[container] {
container = konstraint.containers[_]
}
rego: |-
package ocp.bestpractices.container_livenessprobe_notset
import data.lib.konstraint
import data.lib.openshift
violation[msg] {
openshift.is_workload_kind
container := openshift.containers[_]
konstraint.missing_field(container, "livenessProbe")
obj := konstraint.object
msg := konstraint.format(sprintf("%s/%s: container '%s' has no livenessProbe. See: https://docs.openshift.com/container-platform/latest/applications/application-health.html", [obj.kind, obj.metadata.name, container.name]))
}
target: admission.k8s.gatekeeper.sh
- complianceType: musthave
objectDefinition:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: ContainerLivenessprobeNotset
metadata:
name: containerlivenessprobenotset
spec:
enforcementAction: dryrun
match:
kinds:
- apiGroups:
- apps.openshift.io
- apps
kinds:
- DeploymentConfig
- DaemonSet
- Deployment
- StatefulSet
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: policy-gatekeeper-audit-liveness
spec:
remediationAction: inform # will be overridden by remediationAction in parent policy
severity: low
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: ContainerLivenessprobeNotset
metadata:
name: containerlivenessprobenotset
status:
totalViolations: 0
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: policy-gatekeeper-admission-liveness
spec:
remediationAction: inform # will be overridden by remediationAction in parent policy
severity: low
object-templates:
- complianceType: mustnothave
objectDefinition:
apiVersion: v1
kind: Event
metadata:
namespace: openshift-gatekeeper-system # set it to the actual namespace where gatekeeper is running if different
annotations:
constraint_action: deny
constraint_kind: ContainerLivenessprobeNotset
constraint_name: containerlivenessprobenotset
event_type: violation
---
apiVersion: policy.open-cluster-management.io/v1
kind: PlacementBinding
metadata:
name: binding-policy-gatekeeper-containerlivenessprobenotset
placementRef:
name: placement-policy-gatekeeper-containerlivenessprobenotset
kind: PlacementRule
apiGroup: apps.open-cluster-management.io
subjects:
- name: policy-gatekeeper-livenessprobe
kind: Policy
apiGroup: policy.open-cluster-management.io
---
apiVersion: apps.open-cluster-management.io/v1
kind: PlacementRule
metadata:
name: placement-policy-gatekeeper-containerlivenessprobenotset
spec:
clusterConditions:
- status: "True"
type: ManagedClusterConditionAvailable
clusterSelector:
matchExpressions:
- { key: policy, operator: In, values: ["opa"] }

View File

@@ -0,0 +1,459 @@
apiVersion: policy.open-cluster-management.io/v1
kind: Policy
metadata:
name: policy-gatekeeper-readinessprobe
annotations:
policy.open-cluster-management.io/standards: NIST SP 800-53
policy.open-cluster-management.io/categories: CM Configuration Management
policy.open-cluster-management.io/controls: CM-2 Baseline Configuration
spec:
remediationAction: enforce
disabled: false
policy-templates:
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: policy-gatekeeper-containerreadinessprobenotset
spec:
remediationAction: enforce
severity: low
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
creationTimestamp: null
name: containerreadinessprobenotset
spec:
crd:
spec:
names:
kind: ContainerReadinessprobeNotset
targets:
- libs:
- |
package lib.konstraint
default is_gatekeeper = false
is_gatekeeper {
has_field(input, "review")
has_field(input.review, "object")
}
object = input {
not is_gatekeeper
}
object = input.review.object {
is_gatekeeper
}
format(msg) = gatekeeper_format {
is_gatekeeper
gatekeeper_format = {"msg": msg}
}
format(msg) = msg {
not is_gatekeeper
}
name = object.metadata.name
kind = object.kind
has_field(obj, field) {
obj[field]
}
missing_field(obj, field) = true {
obj[field] == ""
}
missing_field(obj, field) = true {
not has_field(obj, field)
}
is_service {
lower(kind) == "service"
}
is_statefulset {
lower(kind) == "statefulset"
}
is_daemonset {
lower(kind) == "daemonset"
}
is_deployment {
lower(kind) == "deployment"
}
is_pod {
lower(kind) == "pod"
}
is_namespace {
lower(kind) == "namespace"
}
is_workload {
containers[_]
}
pod_containers(pod) = all_containers {
keys = {"containers", "initContainers"}
all_containers = [c | keys[k]; c = pod.spec[k][_]]
}
containers[container] {
pods[pod]
all_containers = pod_containers(pod)
container = all_containers[_]
}
containers[container] {
all_containers = pod_containers(object)
container = all_containers[_]
}
container_images[image] {
containers[container]
image = container.image
}
container_images[image] {
image = object.spec.image
}
split_image(image) = [image, "latest"] {
not contains(image, ":")
}
split_image(image) = [image_name, tag] {
[image_name, tag] = split(image, ":")
}
pods[pod] {
is_statefulset
pod = object.spec.template
}
pods[pod] {
is_daemonset
pod = object.spec.template
}
pods[pod] {
is_deployment
pod = object.spec.template
}
pods[pod] {
is_pod
pod = object
}
volumes[volume] {
pods[pod]
volume = pod.spec.volumes[_]
}
mem_multiple("E") = 1000000000000000000000 { true }
mem_multiple("P") = 1000000000000000000 { true }
mem_multiple("T") = 1000000000000000 { true }
mem_multiple("G") = 1000000000000 { true }
mem_multiple("M") = 1000000000 { true }
mem_multiple("k") = 1000000 { true }
mem_multiple("") = 1000 { true }
mem_multiple("m") = 1 { true }
mem_multiple("Ki") = 1024000 { true }
mem_multiple("Mi") = 1048576000 { true }
mem_multiple("Gi") = 1073741824000 { true }
mem_multiple("Ti") = 1099511627776000 { true }
mem_multiple("Pi") = 1125899906842624000 { true }
mem_multiple("Ei") = 1152921504606846976000 { true }
get_suffix(mem) = suffix {
not is_string(mem)
suffix := ""
}
get_suffix(mem) = suffix {
is_string(mem)
count(mem) > 0
suffix := substring(mem, count(mem) - 1, -1)
mem_multiple(suffix)
}
get_suffix(mem) = suffix {
is_string(mem)
count(mem) > 1
suffix := substring(mem, count(mem) - 2, -1)
mem_multiple(suffix)
}
get_suffix(mem) = suffix {
is_string(mem)
count(mem) > 1
not mem_multiple(substring(mem, count(mem) - 1, -1))
not mem_multiple(substring(mem, count(mem) - 2, -1))
suffix := ""
}
get_suffix(mem) = suffix {
is_string(mem)
count(mem) == 1
not mem_multiple(substring(mem, count(mem) - 1, -1))
suffix := ""
}
get_suffix(mem) = suffix {
is_string(mem)
count(mem) == 0
suffix := ""
}
canonify_mem(orig) = new {
is_number(orig)
new := orig * 1000
}
canonify_mem(orig) = new {
not is_number(orig)
suffix := get_suffix(orig)
raw := replace(orig, suffix, "")
re_match("^[0-9]+$", raw)
new := to_number(raw) * mem_multiple(suffix)
}
canonify_storage(orig) = new {
is_number(orig)
new := orig
}
canonify_storage(orig) = new {
not is_number(orig)
suffix := get_suffix(orig)
raw := replace(orig, suffix, "")
re_match("^[0-9]+$", raw)
new := to_number(raw) * mem_multiple(suffix)
}
canonify_cpu(orig) = new {
is_number(orig)
new := orig * 1000
}
canonify_cpu(orig) = new {
not is_number(orig)
endswith(orig, "m")
new := to_number(replace(orig, "m", ""))
}
canonify_cpu(orig) = new {
not is_number(orig)
not endswith(orig, "m")
re_match("^[0-9]+$", orig)
new := to_number(orig) * 1000
}
dropped_capability(container, cap) {
container.securityContext.capabilities.drop[_] == cap
}
added_capability(container, cap) {
container.securityContext.capabilities.add[_] == cap
}
no_read_only_filesystem(c) {
not has_field(c, "securityContext")
}
no_read_only_filesystem(c) {
has_field(c, "securityContext")
not has_field(c.securityContext, "readOnlyRootFilesystem")
}
priviledge_escalation_allowed(c) {
not has_field(c, "securityContext")
}
priviledge_escalation_allowed(c) {
has_field(c, "securityContext")
has_field(c.securityContext, "allowPrivilegeEscalation")
}
- |-
package lib.openshift
import data.lib.konstraint
is_deploymentconfig {
lower(konstraint.object.apiVersion) == "apps.openshift.io/v1"
lower(konstraint.object.kind) == "deploymentconfig"
}
is_route {
lower(konstraint.object.apiVersion) == "route.openshift.io/v1"
lower(konstraint.object.kind) == "route"
}
is_workload_kind {
is_deploymentconfig
}
is_workload_kind {
konstraint.is_statefulset
}
is_workload_kind {
konstraint.is_daemonset
}
is_workload_kind {
konstraint.is_deployment
}
is_all_kind {
is_workload_kind
}
is_all_kind {
konstraint.is_service
}
is_all_kind {
is_route
}
pods[pod] {
is_deploymentconfig
pod = konstraint.object.spec.template
}
pods[pod] {
pod = konstraint.pods[_]
}
containers[container] {
pods[pod]
all_containers = konstraint.pod_containers(pod)
container = all_containers[_]
}
containers[container] {
container = konstraint.containers[_]
}
rego: |-
package ocp.bestpractices.container_readinessprobe_notset
import data.lib.konstraint
import data.lib.openshift
violation[msg] {
openshift.is_workload_kind
container := openshift.containers[_]
konstraint.missing_field(container, "readinessProbe")
obj := konstraint.object
msg := konstraint.format(sprintf("%s/%s: container '%s' has no readinessProbe. See: https://docs.openshift.com/container-platform/4.4/applications/application-health.html", [obj.kind, obj.metadata.name, container.name]))
}
target: admission.k8s.gatekeeper.sh
- complianceType: musthave
objectDefinition:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: ContainerReadinessprobeNotset
metadata:
name: containerreadinessprobenotset
spec:
enforcementAction: dryrun
match:
kinds:
- apiGroups:
- apps.openshift.io
- apps
kinds:
- DeploymentConfig
- DaemonSet
- Deployment
- StatefulSet
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: policy-gatekeeper-audit-readiness
spec:
remediationAction: inform # will be overridden by remediationAction in parent policy
severity: low
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: ContainerReadinessprobeNotset
metadata:
name: containerreadinessprobenotset
status:
totalViolations: 0
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: policy-gatekeeper-admission-readiness
spec:
remediationAction: inform # will be overridden by remediationAction in parent policy
severity: low
object-templates:
- complianceType: mustnothave
objectDefinition:
apiVersion: v1
kind: Event
metadata:
namespace: openshift-gatekeeper-system # set it to the actual namespace where gatekeeper is running if different
annotations:
constraint_action: deny
constraint_kind: ContainerReadinessprobeNotset
constraint_name: containerreadinessprobenotset
event_type: violation
---
apiVersion: policy.open-cluster-management.io/v1
kind: PlacementBinding
metadata:
name: binding-policy-gatekeeper-containerreadinessprobenotset
placementRef:
name: placement-policy-gatekeeper-containerreadinessprobenotset
kind: PlacementRule
apiGroup: apps.open-cluster-management.io
subjects:
- name: policy-gatekeeper-readinessprobe
kind: Policy
apiGroup: policy.open-cluster-management.io
---
apiVersion: apps.open-cluster-management.io/v1
kind: PlacementRule
metadata:
name: placement-policy-gatekeeper-containerreadinessprobenotset
spec:
clusterConditions:
- status: "True"
type: ManagedClusterConditionAvailable
clusterSelector:
matchExpressions:
- { key: policy, operator: In, values: ["opa"] }

View File

@@ -0,0 +1,18 @@
# Vim filetype=yaml
---
offline: false
#requirements: ansible/execution_environment/requirements.yml
exclude_paths:
- .cache/
- .github/
- charts/
- common/
- tests/
# warn_list:
# - yaml
# - schema
# - experimental
# - risky-file-permissions
# - var-spacing

View File

@@ -0,0 +1,9 @@
---
version: 2
updates:
# Check for updates to GitHub Actions every week
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -0,0 +1,9 @@
[whitelist]
# As of v4, gitleaks only matches against filename, not path in the
# files directive. Leaving content for backwards compatibility.
files = [
"ansible/plugins/modules/*.py",
"ansible/tests/unit/test_*.py",
"ansible/tests/unit/*.yaml",
"ansible/tests/unit/v2/*.yaml",
]

View File

@@ -0,0 +1,6 @@
{
"default": true,
"MD003": false,
"MD013": false,
"MD033": false
}

View File

@@ -0,0 +1,17 @@
name: Ansible Lint # feel free to pick your own name
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
# Important: This sets up your GITHUB_WORKSPACE environment variable
- uses: actions/checkout@v4
- name: Lint Ansible Playbook
uses: ansible/ansible-lint-action@v6
# Let's point it to the path
with:
path: "ansible/"

View File

@@ -0,0 +1,49 @@
---
name: Verify json schema
on: [push, pull_request]
jobs:
jsonschema_tests:
name: Json Schema tests
strategy:
matrix:
python-version: [3.11]
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install check-jsonschema
- name: Install yq
uses: chrisdickinson/setup-yq@latest
with:
yq-version: v4.30.7
- name: Verify secrets json schema against templates
run: |
cp ./values-secret.yaml.template ./values-secret.yaml
check-jsonschema --fill-defaults --schemafile https://raw.githubusercontent.com/validatedpatterns/rhvp.cluster_utils/refs/heads/main/roles/vault_utils/values-secrets.v2.schema.json values-secret.yaml
rm -f ./values-secret.yaml
- name: Verify ClusterGroup values.schema.json against values-*yaml files
run: |
set -e; for i in values-hub.yaml values-group-one.yaml; do
echo "$i"
# disable shellcheck of single quotes in yq
# shellcheck disable=2016
yq eval-all '. as $item ireduce ({}; . * $item )' values-global.yaml "$i" > tmp.yaml
check-jsonschema --fill-defaults --schemafile https://raw.githubusercontent.com/validatedpatterns/clustergroup-chart/refs/heads/main/values.schema.json tmp.yaml
rm -f tmp.yaml
done

View File

@@ -0,0 +1,44 @@
---
name: Super linter
on: [push, pull_request]
jobs:
build:
# Name the Job
name: Super linter
# Set the agent to run on
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
################################
# Run Linter against code base #
################################
- name: Lint Code Base
uses: super-linter/super-linter/slim@v7
env:
VALIDATE_ALL_CODEBASE: true
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# These are the validation we disable atm
VALIDATE_ANSIBLE: false
VALIDATE_BASH: false
VALIDATE_CHECKOV: false
VALIDATE_JSCPD: false
VALIDATE_JSON_PRETTIER: false
VALIDATE_MARKDOWN_PRETTIER: false
VALIDATE_KUBERNETES_KUBECONFORM: false
VALIDATE_PYTHON_PYLINT: false
VALIDATE_SHELL_SHFMT: false
VALIDATE_YAML: false
VALIDATE_YAML_PRETTIER: false
# VALIDATE_DOCKERFILE_HADOLINT: false
# VALIDATE_MARKDOWN: false
# VALIDATE_NATURAL_LANGUAGE: false
# VALIDATE_TEKTON: false

View File

@@ -0,0 +1,23 @@
# This job requires a secret called DOCS_TOKEN which should be a PAT token
# that has the permissions described in:
# validatedpatterns/docs/.github/workflows/metadata-docs.yml@main
---
name: Update docs pattern metadata
on:
push:
paths:
- "pattern-metadata.yaml"
- ".github/workflows/update-metadata.yml"
jobs:
update-metadata:
uses: validatedpatterns/docs/.github/workflows/metadata-docs.yml@main
permissions: # Workflow-level permissions
contents: read # Required for "read-all"
packages: write # Allows writing to packages
id-token: write # Allows creating OpenID Connect (OIDC) tokens
secrets: inherit
# For testing you can point to a different branch in the docs repository
# with:
# DOCS_BRANCH: "main"

9
tmp/acm-hub-bootstrap/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
*~
*.swp
*.swo
values-secret*
.*.expected.yaml
pattern-vault.init
vault.init
super-linter.log
common/pattern-vault.init

View File

@@ -0,0 +1 @@
.github/linters/.gitleaks.toml

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,25 @@
.PHONY: default
default: help
.PHONY: help
##@ Pattern tasks
# No need to add a comment here as help is described in common/
help:
@make -f common/Makefile MAKEFILE_LIST="Makefile common/Makefile" help
%:
make -f common/Makefile $*
.PHONY: install
install: operator-deploy post-install ## installs the pattern and loads the secrets
@echo "Installed"
.PHONY: post-install
post-install: ## Post-install tasks
make load-secrets
@echo "Done"
.PHONY: test
test:
@make -f common/Makefile PATTERN_OPTS="-f values-global.yaml -f values-hub.yaml" test

View File

@@ -0,0 +1,19 @@
# Multicloud Gitops
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[Live build status](https://validatedpatterns.io/ci/?pattern=mcgitops)
## Start Here
If you've followed a link to this repository, but are not really sure what it contains
or how to use it, head over to [Multicloud GitOps](https://validatedpatterns.io/patterns/multicloud-gitops/)
for additional context and installation instructions
## Rationale
The goal for this pattern is to:
* Use a GitOps approach to manage hybrid and multi-cloud deployments across both public and private clouds.
* Enable cross-cluster governance and application lifecycle management.
* Securely manage secrets across the deployment.

View File

@@ -0,0 +1,6 @@
[defaults]
localhost_warning=False
retry_files_enabled=False
library=~/.ansible/plugins/modules:./ansible/plugins/modules:./common/ansible/plugins/modules:/usr/share/ansible/plugins/modules
roles_path=~/.ansible/roles:./ansible/roles:./common/ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles
filter_plugins=~/.ansible/plugins/filter:./ansible/plugins/filter:./common/ansible/plugins/filter:/usr/share/ansible/plugins/filter

View File

@@ -0,0 +1,17 @@
# This is only needed for RHPDS
- name: MultiCloud-GitOps RHPDS bootstrap
hosts: localhost
connection: local
tasks:
# We cannot use .package or .dnf modules because python3 that is used comes
# from a virtualenv
- name: Launch the installation
ansible.builtin.command: ./pattern.sh make install
args:
chdir: "{{ lookup('env', 'PWD') }}"
register: output
changed_when: false
- name: Print output of installation
ansible.builtin.debug:
msg: "{{ output }}"

View File

@@ -0,0 +1,6 @@
apiVersion: v2
description: A Helm chart to build and deploy a use of remote configuration enabled by ACM and Vault
keywords:
- pattern
name: config-demo
version: 0.0.1

View File

@@ -0,0 +1,24 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: config-demo-configmap
labels:
app.kubernetes.io/instance: config-demo
data:
"index.html": |-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Config Demo</title>
</head>
<body>
<h1>
Hub Cluster domain is '{{ .Values.global.hubClusterDomain }}' <br>
Pod is running on Local Cluster Domain '{{ .Values.global.localClusterDomain }}' <br>
</h1>
<h2>
The secret is <a href="/secret/secret">secret</a>
</h2>
</body>
</html>

View File

@@ -0,0 +1,65 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
application: config-demo
name: config-demo
spec:
replicas: 2
revisionHistoryLimit: 3
selector:
matchLabels:
deploymentconfig: config-demo
template:
metadata:
creationTimestamp: null
labels:
app: config-demo
deploymentconfig: config-demo
name: config-demo
spec:
containers:
- name: apache
image: registry.access.redhat.com/ubi8/httpd-24:1-226
#imagePullPolicy: Always
ports:
- containerPort: 8080
name: http
protocol: TCP
volumeMounts:
- mountPath: /var/www/html
name: config-demo-configmap
- mountPath: /var/www/html/secret
readOnly: true
name: config-demo-secret
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
livenessProbe:
httpGet:
path: /index.html
port: 8080
scheme: HTTP
initialDelaySeconds: 5
timeoutSeconds: 1
periodSeconds: 10
successThreshold: 1
failureThreshold: 3
readinessProbe:
httpGet:
path: /index.html
port: 8080
scheme: HTTP
initialDelaySeconds: 5
timeoutSeconds: 1
periodSeconds: 10
successThreshold: 1
failureThreshold: 3
volumes:
- name: config-demo-configmap
configMap:
defaultMode: 438
name: config-demo-configmap
- name: config-demo-secret
secret:
secretName: config-demo-secret

View File

@@ -0,0 +1,18 @@
---
apiVersion: "external-secrets.io/v1beta1"
kind: ExternalSecret
metadata:
name: config-demo-secret
namespace: config-demo
spec:
refreshInterval: 15s
secretStoreRef:
name: {{ .Values.secretStore.name }}
kind: {{ .Values.secretStore.kind }}
target:
name: config-demo-secret
template:
type: Opaque
dataFrom:
- extract:
key: {{ .Values.configdemosecret.key }}

View File

@@ -0,0 +1,12 @@
apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
name: config-demo
spec:
lookupPolicy:
local: true
tags:
- name: registry.access.redhat.com/ubi8/httpd-24
importPolicy: {}
referencePolicy:
type: Local

View File

@@ -0,0 +1,14 @@
apiVersion: route.openshift.io/v1
kind: Route
metadata:
labels:
app: config-demo
name: config-demo
spec:
port:
targetPort: 8080-tcp
to:
kind: Service
name: config-demo
weight: 100
wildcardPolicy: None

View File

@@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: config-demo
name: config-demo
spec:
ports:
- name: 8080-tcp
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: config-demo
deploymentconfig: config-demo
sessionAffinity: None
type: ClusterIP

View File

@@ -0,0 +1,14 @@
---
secretStore:
name: vault-backend
kind: ClusterSecretStore
configdemosecret:
key: secret/data/global/config-demo
global:
hubClusterDomain: hub.example.com
localClusterDomain: region-one.example.com
clusterGroup:
isHubCluster: true

View File

@@ -0,0 +1,6 @@
apiVersion: v2
description: A Helm chart to show a webserver and with no other dependencies
keywords:
- pattern
name: hello-world
version: 0.0.1

View File

@@ -0,0 +1,23 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: hello-world-configmap
labels:
app.kubernetes.io/instance: hello-world
data:
"index.html": |-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello World</title>
</head>
<body>
<h1>Hello World!</h1>
<br/>
<h2>
Hub Cluster domain is '{{ .Values.global.hubClusterDomain }}' <br>
Pod is running on Local Cluster Domain '{{ .Values.global.localClusterDomain }}' <br>
</h2>
</body>
</html>

View File

@@ -0,0 +1,58 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
application: hello-world
name: hello-world
spec:
replicas: 1
revisionHistoryLimit: 3
selector:
matchLabels:
deploymentconfig: hello-world
template:
metadata:
labels:
app: hello-world
deploymentconfig: hello-world
name: hello-world
spec:
containers:
- name: apache
image: registry.access.redhat.com/ubi8/httpd-24:1-226
#imagePullPolicy: Always
ports:
- containerPort: 8080
name: http
protocol: TCP
volumeMounts:
- mountPath: /var/www/html
name: hello-world-configmap
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
livenessProbe:
httpGet:
path: /index.html
port: 8080
scheme: HTTP
initialDelaySeconds: 5
timeoutSeconds: 1
periodSeconds: 10
successThreshold: 1
failureThreshold: 3
readinessProbe:
httpGet:
path: /index.html
port: 8080
scheme: HTTP
initialDelaySeconds: 5
timeoutSeconds: 1
periodSeconds: 10
successThreshold: 1
failureThreshold: 3
volumes:
- name: hello-world-configmap
configMap:
defaultMode: 438
name: hello-world-configmap

View File

@@ -0,0 +1,14 @@
apiVersion: route.openshift.io/v1
kind: Route
metadata:
labels:
app: hello-world
name: hello-world
spec:
port:
targetPort: 8080-tcp
to:
kind: Service
name: hello-world
weight: 100
wildcardPolicy: None

View File

@@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: hello-world
name: hello-world
spec:
ports:
- name: 8080-tcp
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: hello-world
deploymentconfig: hello-world
sessionAffinity: None
type: ClusterIP

View File

@@ -0,0 +1,4 @@
---
global:
hubClusterDomain: hub.example.com
localCluster: local.example.com

View File

@@ -0,0 +1,21 @@
# Vim filetype=yaml
---
offline: false
skip_list:
- name[template] # Allow Jinja templating inside task and play names
- template-instead-of-copy # Templated files should use template instead of copy
- yaml[line-length] # too long lines
- yaml[indentation] # Forcing lists to be always indented by 2 chars is silly IMO
- var-naming[no-role-prefix] # This would be too much churn for very little gain
- no-changed-when
- var-naming[no-role-prefix] # There are too many changes now and it would be too risky
# ansible-lint gh workflow cannot find ansible.cfg hence fails to import vault_utils role
exclude_paths:
- ./ansible/playbooks/vault/vault.yaml
- ./ansible/playbooks/iib-ci/iib-ci.yaml
- ./ansible/playbooks/k8s_secrets/k8s_secrets.yml
- ./ansible/playbooks/process_secrets/process_secrets.yml
- ./ansible/playbooks/write-token-kubeconfig/write-token-kubeconfig.yml
- ./ansible/playbooks/process_secrets/display_secrets_info.yml
- ./ansible/roles/vault_utils/tests/test.yml

View File

@@ -0,0 +1,9 @@
---
version: 2
updates:
# Check for updates to GitHub Actions every week
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -0,0 +1,4 @@
[whitelist]
# As of v4, gitleaks only matches against filename, not path in the
# files directive. Leaving content for backwards compatibility.
files = [ ]

View File

@@ -0,0 +1,6 @@
{
"default": true,
"MD003": false,
"MD013": false,
"MD033": false
}

View File

@@ -0,0 +1,48 @@
name: Run Bash Script on Multiple Distributions
on:
push:
paths:
- "scripts/**"
- "Makefile"
branches:
- main
pull_request:
paths:
- "scripts/**"
- "Makefile"
jobs:
run-script:
name: Run Bash Script
strategy:
matrix:
# Fedora is not an option yet
os: [ubuntu-latest, ubuntu-22.04]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Podman on Ubuntu
if: contains(matrix.os, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y podman
# Currently we do not do MacOSX as it is not free, maybe in the future
# - name: Install Podman on macOS
# if: contains(matrix.os, 'macos')
# run: |
# brew install podman
# podman machine init
# podman machine start
- name: Verify Podman Installation
run: podman --version
- name: Run pattern.sh script
run: |
export TARGET_BRANCH=main
./scripts/pattern-util.sh make validate-origin

View File

@@ -0,0 +1,44 @@
---
name: Super linter
on: [push, pull_request]
jobs:
build:
# Name the Job
name: Super linter
# Set the agent to run on
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
################################
# Run Linter against code base #
################################
- name: Lint Code Base
uses: super-linter/super-linter/slim@v7
env:
VALIDATE_ALL_CODEBASE: true
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# These are the validation we disable atm
VALIDATE_ANSIBLE: false
VALIDATE_BASH: false
VALIDATE_CHECKOV: false
VALIDATE_JSCPD: false
VALIDATE_JSON_PRETTIER: false
VALIDATE_MARKDOWN_PRETTIER: false
VALIDATE_KUBERNETES_KUBECONFORM: false
VALIDATE_PYTHON_PYLINT: false
VALIDATE_SHELL_SHFMT: false
VALIDATE_YAML: false
VALIDATE_YAML_PRETTIER: false
# VALIDATE_DOCKERFILE_HADOLINT: false
# VALIDATE_MARKDOWN: false
# VALIDATE_NATURAL_LANGUAGE: false
# VALIDATE_TEKTON: false

13
tmp/acm-hub-bootstrap/common/.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
__pycache__/
*.py[cod]
*~
*.swp
*.swo
values-secret.yaml
.*.expected.yaml
.vscode
pattern-vault.init
pattern-vault.init.bak
super-linter.log
golang-external-secrets/Chart.lock
hashicorp-vault/Chart.lock

View File

@@ -0,0 +1 @@
.github/linters/.gitleaks.toml

View File

@@ -0,0 +1,153 @@
# Changes
## Sep 24, 2024
* Ansible has been moved out of the common code tree, you must use a clustergroup chart that is >= 0.9.1
## Sep 6, 2024
* Most charts have been removed from the tree. To get the charts you now have to point to them
## Sep 25, 2023
* Upgraded ESO to v0.9.5
## Aug 17, 2023
* Introduced support for multisource applications via .chart + .chartVersion
## Jul 8, 2023
* Introduced a default of 20 for sync failures retries in argo applications (global override via global.options.applicationRetryLimit
and per-app override via .syncPolicy)
## May 22, 2023
* Upgraded ESO to 0.8.2
* *Important* we now use the newly blessed sso config for argo. This means that gitops < 1.8 are *unsupported*
## May 18, 2023
* Introduce a EXTRA_HELM_OPTS env variable that will be passed to the helm invocations
## April 21, 2023
* Added labels and annotation support to namespaces.yaml template
## Apr 11, 2023
* Apply the ACM ocp-gitops-policy everywhere but the hub
## Apr 7, 2023
* Moved to gitops-1.8 channel by default (stable is unmaintained and will be dropped starting with ocp-4.13)
## March 20, 2023
* Upgraded ESO to 0.8.1
## February 9, 2023
* Add support for /values-<platform>.yaml and for /values-<platform>-<clusterversion>.yaml
## January 29, 2023
* Stop extracting the HUB's CA via an imperative job running on the imported cluster.
Just use ACM to push the HUB's CA out to the managed clusters.
## January 23, 2023
* Add initial support for running ESO on ACM-imported clusters
## January 18, 2023
* Add validate-schema target
## January 13, 2023
* Simplify the secrets paths when using argo hosted sites
## January 10, 2023
* vaultPrefixes is now optional in the v2 secret spec and defaults to ["hub"]
## December 9, 2022
* Dropped insecureUnsealVaultInsideCluster (and file_unseal) entirely. Now
vault is always unsealed via a cronjob in the cluster. It is recommended to
store the imperative/vaultkeys secret offline securely and then delete it.
## December 8, 2022
* Removed the legacy installation targets:
`deploy upgrade legacy-deploy legacy-upgrade`
Patterns must now use the operator-based installation
## November 29, 2022
* Upgraded vault-helm to 0.23.0
* Enable vault-ssl by default
## November 22, 2022
* Implemented a new format for the values-secret.yaml. Example can be found in examples/ folder
* Now the order of values-secret file lookup is the following:
1. ~/values-secret-<patternname>.yaml
2. ~/values-secret.yaml
3. <patterngitrepo>/values-secret.yaml.template
* Add support for ansible vault encrypted values-secret files. You can now encrypt your values-secret file
at rest with `ansible-vault encrypt ~/values-secret.yaml`. When running `make load-secrets` if an encrypted
file is encountered the user will be prompted automatically for the password to decrypt it.
## November 6, 2022
* Add support for /values-<CloudPlatform>-<clusterGroup>.yaml (e.g. /values-AWS-group-one.yaml)
## October 28, 2022
* Updated vault helm chart to v0.22.1 and vault containers to 1.12.0
## October 25, 2022
* Updated External Secrets Operator to v0.6.0
* Moved to -UBI based ESO containers
## October 13, 2022
* Added global.clusterVersion as a new helm variable which represents the OCP
Major.Minor cluster version. By default now a user can add a
values-<ocpversion>-<clustergroup>.yaml file to have specific cluster version
overrides (e.g. values-4.10-hub.yaml). Will need Validated Patterns Operator >= 0.0.6
when deploying with the operator. Note: When using the ArgoCD Hub and spoke model,
you cannot have spokes with a different version of OCP than the hub.
## October 4, 2022
* Extended the values-secret.yaml file to support multiple vault paths and re-wrote
the push_secrets feature as python module plugin. This requires the following line
in a pattern's ansible.cfg's '[defaults]' stanza:
`library=~/.ansible/plugins/modules:./ansible/plugins/modules:./common/ansible/plugins/modules:/usr/share/ansible/plugins/modules`
## October 3, 2022
* Restore the ability to install a non-default site: `make TARGET_SITE=mysite install`
* Revised tests (new output and filenames, requires adding new result files to Git)
* ACM 2.6 required for ACM-based managed sites
* Introduced global.clusterDomain template variable (without the `apps.` prefix)
* Removed the ability to send specific charts to another cluster, use hosted argo sites instead
* Added the ability to have the hub host `values-{site}.yaml` for spoke clusters.
The following example would deploy the namespaces, subscriptions, and
applications defined in `values-group-one.yaml` to the `perth` cluster
directly from ArgoCD on the hub.
```yaml
managedClusterGroups:
- name: group-one
hostedArgoSites:
- name: perth
domain: perth1.beekhof.net
bearerKeyPath: secret/data/hub/cluster_perth
caKeyPath: secret/data/hub/cluster_perth_ca
```

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,255 @@
NAME ?= $(shell basename "`pwd`")
ifneq ($(origin TARGET_SITE), undefined)
TARGET_SITE_OPT=--set main.clusterGroupName=$(TARGET_SITE)
endif
# This variable can be set in order to pass additional helm arguments from the
# the command line. I.e. we can set things without having to tweak values files
EXTRA_HELM_OPTS ?=
# This variable can be set in order to pass additional ansible-playbook arguments from the
# the command line. I.e. we can set -vvv for more verbose logging
EXTRA_PLAYBOOK_OPTS ?=
# INDEX_IMAGES=registry-proxy.engineering.redhat.com/rh-osbs/iib:394248
# or
# INDEX_IMAGES=registry-proxy.engineering.redhat.com/rh-osbs/iib:394248,registry-proxy.engineering.redhat.com/rh-osbs/iib:394249
INDEX_IMAGES ?=
TARGET_ORIGIN ?= origin
# This is to ensure that whether we start with a git@ or https:// URL, we end up with an https:// URL
# This is because we expect to use tokens for repo authentication as opposed to SSH keys
TARGET_REPO=$(shell git ls-remote --get-url --symref $(TARGET_ORIGIN) | sed -e 's/.*URL:[[:space:]]*//' -e 's%^git@%%' -e 's%^https://%%' -e 's%:%/%' -e 's%^%https://%')
# git branch --show-current is also available as of git 2.22, but we will use this for compatibility
TARGET_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
UUID_FILE ?= ~/.config/validated-patterns/pattern-uuid
UUID_HELM_OPTS ?=
# --set values always take precedence over the contents of -f
ifneq ("$(wildcard $(UUID_FILE))","")
UUID := $(shell cat $(UUID_FILE))
UUID_HELM_OPTS := --set main.analyticsUUID=$(UUID)
endif
# Set the secret name *and* its namespace when deploying from private repositories
# The format of said secret is documented here: https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/#repositories
TOKEN_SECRET ?=
TOKEN_NAMESPACE ?=
ifeq ($(TOKEN_SECRET),)
HELM_OPTS=-f values-global.yaml --set main.git.repoURL="$(TARGET_REPO)" --set main.git.revision=$(TARGET_BRANCH) $(TARGET_SITE_OPT) $(UUID_HELM_OPTS) $(EXTRA_HELM_OPTS)
else
# When we are working with a private repository we do not escape the git URL as it might be using an ssh secret which does not use https://
TARGET_CLEAN_REPO=$(shell git ls-remote --get-url --symref $(TARGET_ORIGIN))
HELM_OPTS=-f values-global.yaml --set main.tokenSecret=$(TOKEN_SECRET) --set main.tokenSecretNamespace=$(TOKEN_NAMESPACE) --set main.git.repoURL="$(TARGET_CLEAN_REPO)" --set main.git.revision=$(TARGET_BRANCH) $(TARGET_SITE_OPT) $(UUID_HELM_OPTS) $(EXTRA_HELM_OPTS)
endif
# Helm does the right thing and fetches all the tags and detects the newest one
PATTERN_INSTALL_CHART ?= oci://quay.io/hybridcloudpatterns/pattern-install
##@ Pattern Common Tasks
.PHONY: help
help: ## This help message
@echo "Pattern: $(NAME)"
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^(\s|[a-zA-Z_0-9-])+:.*?##/ { printf " \033[36m%-35s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
# Makefiles in the individual patterns should call these targets explicitly
# e.g. from industrial-edge: make -f common/Makefile show
.PHONY: show
show: ## show the starting template without installing it
helm template $(PATTERN_INSTALL_CHART) --name-template $(NAME) $(HELM_OPTS)
preview-all: ## (EXPERIMENTAL) Previews all applications on hub and managed clusters
@echo "NOTE: This is just a tentative approximation of rendering all hub and managed clusters templates"
@common/scripts/preview-all.sh $(TARGET_REPO) $(TARGET_BRANCH)
preview-%:
$(eval CLUSTERGROUP ?= $(shell yq ".main.clusterGroupName" values-global.yaml))
@common/scripts/preview.sh $(CLUSTERGROUP) $* $(TARGET_REPO) $(TARGET_BRANCH)
.PHONY: operator-deploy
operator-deploy operator-upgrade: validate-prereq validate-origin validate-cluster ## runs helm install
@common/scripts/deploy-pattern.sh $(NAME) $(PATTERN_INSTALL_CHART) $(HELM_OPTS)
.PHONY: uninstall
uninstall: ## runs helm uninstall
$(eval CSV := $(shell oc get subscriptions -n openshift-operators openshift-gitops-operator -ojsonpath={.status.currentCSV}))
helm uninstall $(NAME)
@oc delete csv -n openshift-operators $(CSV)
.PHONY: load-secrets
load-secrets: ## loads the secrets into the backend determined by values-global setting
common/scripts/process-secrets.sh $(NAME)
.PHONY: legacy-load-secrets
legacy-load-secrets: ## loads the secrets into vault (only)
common/scripts/vault-utils.sh push_secrets $(NAME)
.PHONY: secrets-backend-vault
secrets-backend-vault: ## Edits values files to use default Vault+ESO secrets config
common/scripts/set-secret-backend.sh vault
common/scripts/manage-secret-app.sh vault present
common/scripts/manage-secret-app.sh golang-external-secrets present
common/scripts/manage-secret-namespace.sh validated-patterns-secrets absent
@git diff --exit-code || echo "Secrets backend set to vault, please review changes, commit, and push to activate in the pattern"
.PHONY: secrets-backend-kubernetes
secrets-backend-kubernetes: ## Edits values file to use Kubernetes+ESO secrets config
common/scripts/set-secret-backend.sh kubernetes
common/scripts/manage-secret-namespace.sh validated-patterns-secrets present
common/scripts/manage-secret-app.sh vault absent
common/scripts/manage-secret-app.sh golang-external-secrets present
@git diff --exit-code || echo "Secrets backend set to kubernetes, please review changes, commit, and push to activate in the pattern"
.PHONY: secrets-backend-none
secrets-backend-none: ## Edits values files to remove secrets manager + ESO
common/scripts/set-secret-backend.sh none
common/scripts/manage-secret-app.sh vault absent
common/scripts/manage-secret-app.sh golang-external-secrets absent
common/scripts/manage-secret-namespace.sh validated-patterns-secrets absent
@git diff --exit-code || echo "Secrets backend set to none, please review changes, commit, and push to activate in the pattern"
.PHONY: load-iib
load-iib: ## CI target to install Index Image Bundles
@set -e; if [ x$(INDEX_IMAGES) != x ]; then \
ansible-playbook $(EXTRA_PLAYBOOK_OPTS) rhvp.cluster_utils.iib_ci; \
else \
echo "No INDEX_IMAGES defined. Bailing out"; \
exit 1; \
fi
.PHONY: token-kubeconfig
token-kubeconfig: ## Create a local ~/.kube/config with password (not usually needed)
common/scripts/write-token-kubeconfig.sh
##@ Validation Tasks
# We only check the remote ssh git branch's existance if we're not running inside a container
# as getting ssh auth working inside a container seems a bit brittle
# If the main repoUpstreamURL field is set, then we need to check against
# that and not target_repo
.PHONY: validate-origin
validate-origin: ## verify the git origin is available
@echo "Checking repository:"
$(eval UPSTREAMURL := $(shell yq -r '.main.git.repoUpstreamURL // (.main.git.repoUpstreamURL = "")' values-global.yaml))
@if [ -z "$(UPSTREAMURL)" ]; then\
echo -n " $(TARGET_REPO) - branch '$(TARGET_BRANCH)': ";\
git ls-remote --exit-code --heads $(TARGET_REPO) $(TARGET_BRANCH) >/dev/null &&\
echo "OK" || (echo "NOT FOUND"; exit 1);\
else\
echo "Upstream URL set to: $(UPSTREAMURL)";\
echo -n " $(UPSTREAMURL) - branch '$(TARGET_BRANCH)': ";\
git ls-remote --exit-code --heads $(UPSTREAMURL) $(TARGET_BRANCH) >/dev/null &&\
echo "OK" || (echo "NOT FOUND"; exit 1);\
fi
.PHONY: validate-cluster
validate-cluster: ## Do some cluster validations before installing
@echo "Checking cluster:"
@echo -n " cluster-info: "
@oc cluster-info >/dev/null && echo "OK" || (echo "Error"; exit 1)
@echo -n " storageclass: "
@if [ `oc get storageclass -o go-template='{{printf "%d\n" (len .items)}}'` -eq 0 ]; then\
echo "WARNING: No storageclass found";\
else\
echo "OK";\
fi
.PHONY: validate-schema
validate-schema: ## validates values files against schema in common/clustergroup
$(eval VAL_PARAMS := $(shell for i in ./values-*.yaml; do echo -n "$${i} "; done))
@echo -n "Validating clustergroup schema of: "
@set -e; for i in $(VAL_PARAMS); do echo -n " $$i"; helm template oci://quay.io/hybridcloudpatterns/clustergroup $(HELM_OPTS) -f "$${i}" >/dev/null; done
@echo
.PHONY: validate-prereq
validate-prereq: ## verify pre-requisites
$(eval GLOBAL_PATTERN := $(shell yq -r .global.pattern values-global.yaml))
@if [ $(NAME) != $(GLOBAL_PATTERN) ]; then\
echo "";\
echo "WARNING: folder directory is \"$(NAME)\" and global.pattern is set to \"$(GLOBAL_PATTERN)\"";\
echo "this can create problems. Please make sure they are the same!";\
echo "";\
fi
@if [ ! -f /run/.containerenv ]; then\
echo "Checking prerequisites:";\
echo -n " Check for python-kubernetes: ";\
if ! ansible -m ansible.builtin.command -a "{{ ansible_python_interpreter }} -c 'import kubernetes'" localhost > /dev/null 2>&1; then echo "Not found"; exit 1; fi;\
echo "OK";\
echo -n " Check for kubernetes.core collection: ";\
if ! ansible-galaxy collection list | grep kubernetes.core > /dev/null 2>&1; then echo "Not found"; exit 1; fi;\
echo "OK";\
else\
if [ -f values-global.yaml ]; then\
OUT=`yq -r '.main.multiSourceConfig.enabled // (.main.multiSourceConfig.enabled = "false")' values-global.yaml`;\
if [ "$${OUT,,}" = "false" ]; then\
echo "You must set \".main.multiSourceConfig.enabled: true\" in your 'values-global.yaml' file";\
echo "because your common subfolder is the slimmed down version with no helm charts in it";\
exit 1;\
fi;\
fi;\
fi
.PHONY: argo-healthcheck
argo-healthcheck: ## Checks if all argo applications are synced
@echo "Checking argo applications"
$(eval APPS := $(shell oc get applications.argoproj.io -A -o jsonpath='{range .items[*]}{@.metadata.namespace}{","}{@.metadata.name}{"\n"}{end}'))
@NOTOK=0; \
for i in $(APPS); do\
n=`echo "$${i}" | cut -f1 -d,`;\
a=`echo "$${i}" | cut -f2 -d,`;\
STATUS=`oc get -n "$${n}" applications.argoproj.io/"$${a}" -o jsonpath='{.status.sync.status}'`;\
if [[ $$STATUS != "Synced" ]]; then\
NOTOK=$$(( $${NOTOK} + 1));\
fi;\
HEALTH=`oc get -n "$${n}" applications.argoproj.io/"$${a}" -o jsonpath='{.status.health.status}'`;\
if [[ $$HEALTH != "Healthy" ]]; then\
NOTOK=$$(( $${NOTOK} + 1));\
fi;\
echo "$${n} $${a} -> Sync: $${STATUS} - Health: $${HEALTH}";\
done;\
if [ $${NOTOK} -gt 0 ]; then\
echo "Some applications are not synced or are unhealthy";\
exit 1;\
fi
##@ Test and Linters Tasks
.PHONY: qe-tests
qe-tests: ## Runs the tests that QE runs
@set -e; if [ -f ./tests/interop/run_tests.sh ]; then \
pushd ./tests/interop; ./run_tests.sh; popd; \
else \
echo "No ./tests/interop/run_tests.sh found skipping"; \
fi
.PHONY: super-linter
super-linter: ## Runs super linter locally
rm -rf .mypy_cache
podman run -e RUN_LOCAL=true -e USE_FIND_ALGORITHM=true \
-e VALIDATE_ANSIBLE=false \
-e VALIDATE_BASH=false \
-e VALIDATE_CHECKOV=false \
-e VALIDATE_DOCKERFILE_HADOLINT=false \
-e VALIDATE_JSCPD=false \
-e VALIDATE_JSON_PRETTIER=false \
-e VALIDATE_MARKDOWN_PRETTIER=false \
-e VALIDATE_KUBERNETES_KUBECONFORM=false \
-e VALIDATE_PYTHON_PYLINT=false \
-e VALIDATE_SHELL_SHFMT=false \
-e VALIDATE_TEKTON=false \
-e VALIDATE_YAML=false \
-e VALIDATE_YAML_PRETTIER=false \
$(DISABLE_LINTERS) \
-v $(PWD):/tmp/lint:rw,z \
-w /tmp/lint \
ghcr.io/super-linter/super-linter:slim-v7
.PHONY: deploy upgrade legacy-deploy legacy-upgrade
deploy upgrade legacy-deploy legacy-upgrade:
@echo "UNSUPPORTED TARGET: please switch to 'operator-deploy'"; exit 1

View File

@@ -0,0 +1,51 @@
# Validated Patterns common/ repository
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
## Note
This is the `main` branch of common and it assumes that the pattern is fully
multisource (meaning that any used charts from VP is actually referenced from
either a helm chart repository or quay repository). I.e. there are no helm
charts contained in this branch of common and there is no ansible code neither.
The helm charts now live in separate repositories under the VP
[organization](https://github.com/validatedpatterns) on GitHub. The repositories are:
- clustergroup-chart
- pattern-install-chart
- hashicorp-vault-chart
- golang-external-secrets-chart
- acm-chart
- letsencrypt-chart
The ansible bits live in this [repository](https://github.com/validatedpatterns/rhvp.cluster_utils)
In order to be able to use this "slimmed-down" main branch of common you *must*
use a 0.9.* clustergroup-chart that. Add the following to your `values-global.yaml`:
```yaml
main:
multiSourceConfig:
enabled: true
clusterGroupChartVersion: 0.9.*
```
## Start Here
This repository is never used as standalone. It is usually imported in each pattern as a subtree.
In order to import the common subtree the very first time you can use the script
[make_common_subtree.sh](scripts/make-common-subtree.sh).
In order to update your common subtree inside your pattern repository you can either use
`https://github.com/validatedpatterns/utilities/blob/main/scripts/update-common-everywhere.sh` or
do it manually with the following commands:
```sh
git remote add -f common-upstream https://github.com/validatedpatterns/common.git
git merge -s subtree -Xtheirs -Xsubtree=common common-upstream/main
```
## Secrets
There are two different secret formats parsed by the ansible bits. Both are documented [here](https://github.com/validatedpatterns/common/tree/main/ansible/roles/vault_utils/README.md)

View File

@@ -0,0 +1,4 @@
---
# Define Ansible collection requirements here
collections:
- name: git+https://github.com/validatedpatterns/rhvp.cluster_utils.git,v1

View File

@@ -0,0 +1,27 @@
#!/bin/bash
set -o pipefail
RUNS=10
WAIT=15
# Retry five times because the CRD might not be fully installed yet
echo -n "Installing pattern: "
for i in $(seq 1 ${RUNS}); do \
exec 3>&1 4>&2
OUT=$( { helm template --include-crds --name-template $* 2>&4 | oc apply -f- 2>&4 1>&3; } 4>&1 3>&1)
ret=$?
exec 3>&- 4>&-
if [ ${ret} -eq 0 ]; then
break;
else
echo -n "."
sleep "${WAIT}"
fi
done
# All the runs failed
if [ ${i} -eq ${RUNS} ]; then
echo "Installation failed [${i}/${RUNS}]. Error:"
echo "${OUT}"
exit 1
fi
echo "Done"

View File

@@ -0,0 +1,16 @@
#!/bin/bash
PATTERN_DIR="$1"
if [ -z "$PATTERN_DIR" ]; then
PATTERN_DIR="."
fi
CGNAME=$(yq '.main.clusterGroupName' "$PATTERN_DIR/values-global.yaml")
if [ -z "$CGNAME" ] || [ "$CGNAME" == "null" ]; then
echo "Error - cannot detrmine clusterGroupName"
exit 1
fi
echo "$CGNAME"

View File

@@ -0,0 +1,15 @@
#!/bin/sh
PATTERN_DIR="$1"
if [ -z "$PATTERN_DIR" ]; then
PATTERN_DIR="."
fi
PATNAME=$(yq '.global.pattern' "$PATTERN_DIR/values-global.yaml" 2>/dev/null)
if [ -z "$PATNAME" ] || [ "$PATNAME" == "null" ]; then
PATNAME="$(basename "$PWD")"
fi
echo "$PATNAME"

View File

@@ -0,0 +1,15 @@
#!/bin/sh
PATTERN_DIR="$1"
if [ -z "$PATTERN_DIR" ]; then
PATTERN_DIR="."
fi
BACKEND=$(yq '.global.secretStore.backend' "$PATTERN_DIR/values-global.yaml" 2>/dev/null)
if [ -z "$BACKEND" -o "$BACKEND" == "null" ]; then
BACKEND="vault"
fi
echo "$BACKEND"

Some files were not shown because too many files have changed in this diff Show More