Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: feat(dev): dev environment #23

Merged
merged 10 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions dev/apps/crossplane.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: crossplane-system
namespace: argocd
spec:
project: default
sources:
- repoURL: https://charts.crossplane.io/stable
targetRevision: 1.15.0
chart: crossplane
helm:
values: |
args:
- --debug
- --enable-composition-functions
xfn.enabled: true
xfn.args: "{--debug}"
destination:
server: https://kubernetes.default.svc
namespace: crossplane-system
# Sync policy
syncPolicy:
automated: # automated sync by default retries failed attempts 5 times with following delays between attempts ( 5s, 10s, 20s, 40s, 80s ); retry controlled using `retry` field.
prune: true # Specifies if resources should be pruned during auto-syncing ( false by default ).
selfHeal: true # Specifies if partial app sync should be executed when resources are changed only in target Kubernetes cluster and no git change detected ( false by default ).
allowEmpty: false # Allows deleting all application resources during automatic syncing ( false by default ).
syncOptions: # Sync options which modifies sync behavior
- CreateNamespace=true # Namespace Auto-Creation ensures that namespace specified as the application destination exists in the destination cluster.
- PruneLast=true # Allow the ability for resource pruning to happen as a final, implicit wave of a sync operation
retry:
limit: 5 # number of failed sync attempt retries; unlimited number of attempts if less than 0
17 changes: 17 additions & 0 deletions dev/apps/keycloak-provider/keycloak-provider-secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
apiVersion: v1
kind: Secret
metadata:
name: keycloak-credentials
namespace: crossplane-system
type: Opaque
stringData:
credentials: |
{
"client_id":"admin-cli",
"username": "admin",
"password": "admin",
"url": "http://$KEYCLOAK_IP:$KEYCLOAK_PORT",
"base_path": "/auth",
"realm": "master"
}
41 changes: 41 additions & 0 deletions dev/apps/keycloak-provider/keycloak-provider.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: keycloak-provider
namespace: crossplane-system
annotations:
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-keycloak:v0.12.0
runtimeConfigRef:
name: enable-ess
---
apiVersion: pkg.crossplane.io/v1beta1
kind: DeploymentRuntimeConfig
metadata:
name: enable-ess
spec:
deploymentTemplate:
spec:
selector: {}
template:
spec:
containers:
- name: package-runtime
args:
- --enable-external-secret-stores
- --enable-management-policies
---
apiVersion: keycloak.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: keycloak-provider-config
namespace: dev
spec:
credentials:
source: Secret
secretRef:
name: keycloak-credentials
key: credentials
namespace: crossplane-system
45 changes: 45 additions & 0 deletions dev/apps/keycloak.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: keycloak
namespace: argocd
spec:
project: default
source:
repoURL: https://codecentric.github.io/helm-charts
targetRevision: 2.2.2
chart: keycloakx
helm:
values: |
command:
- "/opt/keycloak/bin/kc.sh"
- "start"
- "--http-enabled=true"
- "--http-port=8080"
- "--hostname-strict=false"
- "--hostname-strict-https=false"
extraEnv: |
- name: KEYCLOAK_ADMIN
value: admin
- name: KEYCLOAK_ADMIN_PASSWORD
value: admin
- name: JAVA_OPTS_APPEND
value: >-
-Djgroups.dns.query={{ include "keycloak.fullname" . }}-headless
service:
type: LoadBalancer
destination:
server: https://kubernetes.default.svc
namespace: keycloak
# Sync policy
syncPolicy:
automated: # automated sync by default retries failed attempts 5 times with following delays between attempts ( 5s, 10s, 20s, 40s, 80s ); retry controlled using `retry` field.
prune: true # Specifies if resources should be pruned during auto-syncing ( false by default ).
selfHeal: true # Specifies if partial app sync should be executed when resources are changed only in target Kubernetes cluster and no git change detected ( false by default ).
allowEmpty: false # Allows deleting all application resources during automatic syncing ( false by default ).
syncOptions: # Sync options which modifies sync behavior
- CreateNamespace=true # Namespace Auto-Creation ensures that namespace specified as the application destination exists in the destination cluster.
- PruneLast=true # Allow the ability for resource pruning to happen as a final, implicit wave of a sync operation
retry:
limit: 5 # number of failed sync attempt retries; unlimited number of attempts if less than 0
12 changes: 12 additions & 0 deletions dev/demos/basic/client.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
apiVersion: openidclient.keycloak.crossplane.io/v1alpha1
kind: Client
metadata:
annotations:
crossplane.io/external-name: "master/152d4b09-aaf8-414a-8649-63976cc01af2"
name: admin-cli
spec:
forProvider: {}
providerConfigRef:
name: "keycloak-provider-config"
managementPolicies: ["Observe"]
5 changes: 5 additions & 0 deletions dev/demos/basic/namespace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: dev
12 changes: 12 additions & 0 deletions dev/demos/basic/realm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Example 1: Basic Realm Configuration
# This example demonstrates the minimum required fields to create a realm.
apiVersion: realm.keycloak.crossplane.io/v1alpha1
kind: Realm
metadata:
name: dev # The name of the realm in Kubernetes
namespace: dev # The namespace in which the realm will be created
spec:
forProvider:
realm: "dev" # The name of the realm in Keycloak
providerConfigRef:
name: "keycloak-provider-config" # Reference to the ProviderConfig resource
13 changes: 13 additions & 0 deletions dev/demos/basic/users.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
# Example 1: Basic User
# This is a basic user with the minimum required fields.
apiVersion: user.keycloak.crossplane.io/v1alpha1
kind: User
metadata:
name: bree
spec:
forProvider:
realmId: "dev" # The realm to which this user belongs
username: "bree" # The username for this user
providerConfigRef:
name: "keycloak-provider-config" # Reference to the provider configuration
6 changes: 6 additions & 0 deletions dev/kind-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
16 changes: 16 additions & 0 deletions dev/metallb/pools.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: example
namespace: metallb-system
spec:
addresses:
#- 172.22.0.30-172.22.0.80
- ${IP_RANGE_START}-${IP_RANGE_END}
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: empty
namespace: metallb-system
1 change: 1 addition & 0 deletions dev/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python-keycloak==3.3.0
139 changes: 139 additions & 0 deletions dev/setup_dev_environment.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#!/bin/bash
set -eo pipefail

CLUSTER_NAME=$1

if [[ -n $CLUSTER_NAME ]]; then
echo "Cluster name provided: $CLUSTER_NAME"
else
echo "Default cluster name: fenrir-1"
CLUSTER_NAME="fenrir-1"
fi

echo "########### Checking dependencies ###########"
command -v docker >/dev/null 2>&1 || { echo >&2 "Docker is required but not installed. Aborting."; exit 1; }
command -v kind >/dev/null 2>&1 || { echo >&2 "Kind is required but not installed. Aborting."; exit 1; }
command -v kubectl >/dev/null 2>&1 || { echo >&2 "Kubectl is required but not installed. Aborting."; exit 1; }
command -v jq >/dev/null 2>&1 || { echo >&2 "jq is required but not installed. Aborting."; exit 1; }
command -v envsubst >/dev/null 2>&1 || { echo >&2 "envsubst is required but not installed. Aborting."; exit 1; }
command -v base64 >/dev/null 2>&1 || { echo >&2 "base64 is required but not installed. Aborting."; exit 1; }
command -v sed >/dev/null 2>&1 || { echo >&2 "sed is required but not installed. Aborting."; exit 1; }
echo "All dependencies are installed."

# check if user can run docker without sudo, if not create an alias for sudo docker for this session
if ! docker ps >/dev/null 2>&1; then
echo "Sudo required for docker."
sudo_prefix='sudo'
else
sudo_prefix=''
fi

echo "########### Setup Cluster ###########"
if $sudo_prefix kind get clusters | grep "$CLUSTER_NAME" >/dev/null 2>&1; then
echo "$CLUSTER_NAME cluster already exists"
else
echo "Creating cluster"
old_context=$(kubectl config current-context)
$sudo_prefix kind create cluster --name $CLUSTER_NAME --config kind-config.yaml --kubeconfig $HOME/.kube/$CLUSTER_NAME
$sudo_prefix chown $USER:$USER $HOME/.kube/$CLUSTER_NAME
echo "Restore old context $old_context"
kubectl config use-context $old_context
fi

echo "Running some commands to make sure the cluster is ready"
export KUBECONFIG=$HOME/.kube/$CLUSTER_NAME
kubectl_cmd="kubectl --context=kind-$CLUSTER_NAME"
$kubectl_cmd cluster-info
$kubectl_cmd get nodes

echo "########### Setup MetalLB ###########"
echo "* Installing MetalLB"
$kubectl_cmd apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.7/config/manifests/metallb-native.yaml
echo "* Waiting for MetalLB to be ready"
$kubectl_cmd wait --namespace metallb-system --for=condition=ready --all pod --selector=app=metallb --timeout=300s

echo "* Get IPAM config: "
$sudo_prefix docker network inspect kind
echo "* Available subnets (IPv4 required): "
$sudo_prefix docker network inspect kind | jq -r .[].IPAM.Config[].Subnet

export IP_PREFIX=$($sudo_prefix docker network inspect kind | jq -r .[].IPAM.Config[].Subnet | grep -E "([0-9]+\.){3}0/[0-9]+" | sed -r 's|\.0/[0-9]+||g')
echo "* Found IP Prefix: $IP_PREFIX"
# if CLUSTER_NAME == "fenrir-1" then IP_PREFIX == "172.18.0" else IP_PREFIX == "172.19.0"
if [[ $CLUSTER_NAME == "fenrir-1" ]]; then
export IP_RANGE_START="$IP_PREFIX.30"
export IP_RANGE_END="$IP_PREFIX.55"
else
export IP_RANGE_START="$IP_PREFIX.56"
export IP_RANGE_END="$IP_PREFIX.80"
fi

echo "* Set IP Range: $IP_RANGE_START - $IP_RANGE_END"
while true; do
cat metallb/pools.yaml | envsubst | $kubectl_cmd apply -f - && break # Break the loop if command succeeds
echo "** still waiting for metallb resources to be ready"
sleep 1
done

echo "########### Installing ArgoCD ###########"
$kubectl_cmd create namespace argocd || true
$kubectl_cmd apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
echo "* Exposing ArgoCD"
$kubectl_cmd patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'

while [[ -z $($kubectl_cmd get svc -n argocd argocd-server -o jsonpath="{.status.loadBalancer.ingress}" 2>/dev/null) ]]; do
echo "** still waiting for argocd/argocd-server to get ingress"
sleep 1
done
echo "* argocd/argocd-server now has ingress."

export ARGOCD_IP=$($kubectl_cmd -n argocd get svc argocd-server -o json | jq -r .status.loadBalancer.ingress[0].ip)

echo "* Waiting for ArgoCD to be ready"
$kubectl_cmd wait pod --all --for=condition=Ready --namespace argocd --timeout=300s


echo "########### Installing Keycloak ###########"
if $kubectl_cmd diff -f apps/keycloak.yaml >/dev/null 2>&1; then
echo "Keycloak up-to-date."
else
$kubectl_cmd apply -f apps/keycloak.yaml
sleep 5
$kubectl_cmd wait pod --all --for=condition=Ready --namespace keycloak --timeout=300s
fi

while [[ -z $($kubectl_cmd get svc -n keycloak keycloak-keycloakx-http -o jsonpath="{.status.loadBalancer.ingress}" 2>/dev/null) ]]; do
echo "** still waiting for service keycloak/keycloak-keycloakx-http to get ingress"
sleep 5
done

export KEYCLOAK_IP=$($kubectl_cmd -n keycloak get svc keycloak-keycloakx-http -o json | jq -r .status.loadBalancer.ingress[0].ip)
export KEYCLOAK_PORT=$($kubectl_cmd -n keycloak get svc keycloak-keycloakx-http -o json | jq -r .spec.ports[0].port)
export KEYCLOAK_USER=admin
export KEYCLOAK_PASSWORD=admin


echo "########### Installing Crossplane ###########"
if $kubectl_cmd diff -f apps/crossplane.yaml >/dev/null 2>&1; then
echo "Crossplane up-to-date."
else
$kubectl_cmd apply -f apps/crossplane.yaml
sleep 10
$kubectl_cmd wait pod --all --for=condition=Ready --namespace crossplane-system --timeout=300s
sleep 10
$kubectl_cmd wait providers.pkg.crossplane.io/keycloak-provider --for=condition=Healthy --timeout=300s --namespace argocd
fi

echo "########### Installing Keycloak Provider secret ###########"
cat ./apps/keycloak-provider/keycloak-provider-secret.yaml | envsubst | $kubectl_cmd apply --namespace crossplane-system -f -
$kubectl_cmd apply -f ./apps/keycloak-provider/keycloak-provider.yaml


echo "#################################################"
echo "You're ready to go!"
echo "ArgoCD is ready at https://$ARGOCD_IP:443"
echo "ArgoCD login: admin / $($kubectl_cmd -n argocd get secrets argocd-initial-admin-secret -o json | jq -r .data.password | base64 -d)"
echo "-------------------------------------------------"
echo "Keycloak is ready at http://$KEYCLOAK_IP:$KEYCLOAK_PORT/auth"
echo "Keycloak login: admin / admin"
echo "#################################################"
Loading