EKS as a service action (EKSaaS)
This guide demonstrates how to integrate Port and Upbound to provide EKS as a Service (EKSaaS). You'll use Port as a software catalog and self-service hub for developers, and Upbound as the platform management backend.
This guide includes one or more steps that require integration with GitHub.
Port supports two GitHub integrations:
- GitHub (Legacy) - uses a GitHub app, which is soon to be deprecated.
- GitHub (Ocean) - uses the Ocean framework, recommended for new integrations.
Both integration options are present in this guide via tabs, choose the one that fits your needs.
By the end of this guide, you will be able to:
- Request new EKS clusters through Port's self-service actions.
- Approve or deny cluster requests with a GitOps workflow.
- Create clusters directly (bypassing the approval process).
- Delete existing clusters.
- Track all cluster resources in Port's software catalog.
This guide assumes a clean starting point with an empty Upbound organization, an empty Git repository, and an empty Port environment.
Prerequisites
This guide assumes the following:
- You have a Port account and have completed the onboarding process.
- You have your Port
CLIENT_IDandCLIENT_SECRET(how to find your credentials). - You have set up an Upbound organization with:
- An EKSaaS configuration.
- At least one control plane deployed (save the identifiers).
- An API token (create one in My Account → API Tokens).
- Install the GitHub integration:
- GitHub (Legacy)
- GitHub (Ocean)
Install Port's GitHub app and ensure it has access to your repository.
Install the GitHub Ocean integration.
- Your GitHub repository has GitHub Actions enabled for creating and approving pull requests.
Set up data model
Create the following blueprints in Port. They must be created in this order due to dependencies.
Upbound Control Plane blueprint
- Go to your Builder page.
- Click on
+ Blueprint. - Click on the
{...}button in the top right corner, and choose Edit JSON. - Add this JSON schema:
Upbound Control Plane blueprint (click to expand)
{
"identifier": "upbound_control_plane",
"title": "Upbound Control Plane",
"icon": "Cluster",
"schema": {
"properties": {},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
}
- Click
Save.
EKS Cluster blueprint
- Go to your Builder page.
- Click on
+ Blueprint. - Click on the
{...}button in the top right corner, and choose Edit JSON. - Add this JSON schema:
EKS Cluster blueprint (click to expand)
{
"identifier": "eks_cluster",
"title": "EKS Cluster",
"icon": "Cluster",
"schema": {
"properties": {
"node_count": {
"icon": "Node",
"title": "Node Count",
"type": "number",
"description": "The cluster's node count"
},
"node_size": {
"icon": "Node",
"title": "Node Size",
"description": "The cluster's node size",
"type": "string",
"enum": ["small", "medium", "large"],
"enumColors": {
"small": "lightGray",
"medium": "lightGray",
"large": "lightGray"
}
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {
"claim_file_url": {
"title": "Claim file URL",
"icon": "Github",
"calculation": "\"https://github.com/port-demo/port-upbound-demo/blob/main/.up/clusters/\" + .identifier + \".yaml\"",
"type": "string",
"format": "url"
}
},
"relations": {
"upbound_control_plane": {
"title": "Upbound Control Plane",
"description": "The Upbound control plane for this cluster",
"target": "upbound_control_plane",
"required": false,
"many": false
}
}
}
- Click
Save.
EKS Cluster Request blueprint
- Go to your Builder page.
- Click on
+ Blueprint. - Click on the
{...}button in the top right corner, and choose Edit JSON. - Add this JSON schema:
EKS Cluster Request blueprint (click to expand)
{
"identifier": "eks_cluster_request",
"title": "EKS Cluster Request",
"icon": "Book",
"schema": {
"properties": {
"request_pr_url": {
"icon": "Github",
"title": "Request PR URL",
"type": "string",
"description": "The cluster request's PR URL",
"format": "url"
},
"request_pr_number": {
"icon": "Github",
"title": "Request PR Number",
"type": "number",
"minimum": 0
},
"node_count": {
"icon": "Node",
"title": "Node Count",
"type": "number",
"description": "Amount of nodes for this cluster"
},
"node_size": {
"icon": "Node",
"title": "Node Size",
"type": "string",
"description": "The node size for the cluster nodes",
"enum": ["small", "medium", "large"],
"enumColors": {
"small": "lightGray",
"medium": "lightGray",
"large": "lightGray"
}
},
"status": {
"icon": "BlankPage",
"title": "Status",
"description": "Status of the cluster request (Pending/Approved)",
"type": "string",
"default": "Pending",
"enum": ["Pending", "Approved", "Denied"],
"enumColors": {
"Pending": "yellow",
"Approved": "green",
"Denied": "red"
}
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {
"eks_cluster": {
"title": "EKS Cluster",
"description": "The cluster created for this request",
"target": "eks_cluster",
"required": false,
"many": false
},
"upbound_control_plane": {
"title": "Upbound Control Plane",
"description": "The control plane this cluster was requested for",
"target": "upbound_control_plane",
"required": false,
"many": false
}
}
}
- Click
Save.
Set up the backend
Create repository secrets
Follow GitHub's guide to add the following secrets to your repository:
| Secret | Description |
|---|---|
UPBOUND_TOKEN | Your Upbound API token |
PORT_CLIENT_ID | Your Port client ID |
PORT_CLIENT_SECRET | Your Port client secret |
Create the directory structure
In your repository, create the following structure:
.github/
workflows/
apply-clusters.yaml
approve-cluster-request.yaml
delete-cluster.yaml
deny-cluster-request.yaml
new-cluster-request.yaml
.up/
clusters/
examples/
cluster.yaml
Create the GitHub workflows
Create the following workflow files in .github/workflows/:
apply-clusters.yaml (click to expand)
name: Apply Cluster changes
on:
workflow_dispatch:
inputs:
port_context:
type: string
jobs:
apply-clusters:
runs-on: ubuntu-latest
env:
UPBOUND_ORG_ID: <ENTER_UPBOUND_ORG_ID>
steps:
- uses: actions/checkout@v4
with:
persist-credentials: true
ref: main
- name: Install Kubectl
uses: azure/setup-kubectl@v3
id: install-kubectl
- name: Install Upbound CLI
run: |
curl -sL "https://cli.upbound.io" | sh
sudo mv up /usr/local/bin/
- name: Connect to Upbound using CLI and apply manifests to all of the control planes
run: |
up login -t ${{ secrets.UPBOUND_TOKEN }}
cd .up/clusters
for CONTROL_PLANE in */ ; do
CONTROL_PLANE=${CONTROL_PLANE%/}
echo "Fetching kubeconfig for ${CONTROL_PLANE}"
up ctp kubeconfig get -a ${{ env.UPBOUND_ORG_ID }} ${CONTROL_PLANE} -f kubeconfig.yaml --token ${{ secrets.UPBOUND_TOKEN }}
echo "Applying manifests"
if find "$CONTROL_PLANE" -maxdepth 1 -type f -name "*.yaml" | read -r; then
kubectl --kubeconfig kubeconfig.yaml apply -f ./${CONTROL_PLANE}/ --recursive
else
echo "Control plane directory is empty"
# exit 1
fi
done
approve-cluster-request.yaml (click to expand)
name: Approve new cluster PR
on:
workflow_dispatch:
inputs:
port_context:
required: true
description: "Port's context"
type: string
jobs:
approve-cluster-request-call:
runs-on: ubuntu-latest
steps:
- name: Inform starting of approving EKS cluster request
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
logMessage: "Approving EKS cluster request: ${{ fromJson(inputs.port_context).entity.properties.title }}"
- uses: actions/checkout@v4
with:
persist-credentials: true
- name: Merge Pull Request
uses: juliangruber/merge-pull-request-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
number: ${{ fromJson(inputs.port_context).entity.properties.request_pr_number }}
method: squash
- name: "Report new EKS cluster to Port"
if: ${{ always() }}
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: UPSERT
identifier: ${{ fromJson(inputs.port_context).entity.properties.title }}
runId: ${{ fromJson(inputs.port_context).runId }}
title: ${{ fromJson(inputs.port_context).entity.properties.title }}
blueprint: eks_cluster
properties: |
{
"node_size": "${{ fromJson(inputs.port_context).entity.properties.node_size }}",
"node_count": "${{ fromJson(inputs.port_context).entity.properties.node_count }}"
}
relations: |
{
"upbound_control_plane": "${{ fromJson(inputs.port_context).entity.relations.control_plane }}"
}
- name: Inform that EKS cluster request has been approved
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
icon: GitHubActions
logMessage: "Approved EKS cluster request, and created new Port entity for the EKS cluster🚀 Applying Clusters to Upbound control plane..."
approve-cluster-request-port:
runs-on: ubuntu-latest
steps:
- uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).context.runId }}
icon: GitHubActions
logMessage: "Approving EKS cluster request: ${{ fromJson(inputs.port_context).entity.identifier }}"
- uses: actions/checkout@v4
with:
persist-credentials: true
- name: Merge Pull Request
uses: juliangruber/merge-pull-request-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
number: ${{ fromJson(inputs.port_context).entity.properties.request_pr_number }}
method: squash
- name: "Report new EKS cluster to Port"
if: ${{ always() }}
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
identifier: ${{ fromJson(inputs.port_context).payload.entity.identifier }}
title: ${{ fromJson(inputs.port_context).payload.entity.identifier }}
runId: ${{ fromJson(inputs.port_context).context.runId }}
blueprint: eks_cluster
properties: |
{
"node_size": "${{ fromJson(inputs.port_context).entity.properties.node_size }}",
"node_count": ${{ fromJson(inputs.port_context).entity.properties.node_count }}
}
relations: |
{
"upbound_control_plane": "${{ fromJson(inputs.port_context).entity.relations.upbound_control_plane }}"
}
- name: "Approve EKS cluster request"
if: ${{ always() }}
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
identifier: ${{ fromJson(inputs.port_context).payload.entity.identifier }}
blueprint: eks_cluster_request
runId: ${{ fromJson(inputs.port_context).context.runId }}
properties: |
{
"status": "Approved",
"eks_cluster": "${{ fromJson(inputs.port_context).payload.entity.identifier }}"
}
- uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).context.runId }}
icon: GitHubActions
logMessage: "Approved EKS cluster request, and created new Port entity for the EKS cluster🚀 Applying Clusters to Upbound control plane..."
apply-cluster-changes:
uses: ./.github/workflows/apply-clusters.yaml
if: ${{ always() }}
secrets: inherit
needs:
- approve-cluster-request-call
- approve-cluster-request-port
update-port:
runs-on: ubuntu-latest
needs:
- apply-cluster-changes
if: ${{ always() }}
steps:
- name: Inform cluster applied to Upbound
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
logMessage: "Applied cluster to Upbound successfully✅"
delete-cluster.yaml (click to expand)
name: Delete Cluster
on:
workflow_dispatch:
inputs:
port_context:
required: true
description: "Port's payload"
type: string
jobs:
delete-cluster:
runs-on: ubuntu-latest
env:
UPBOUND_ORG_ID: <ENTER_UPBOUND_ORG_ID>
steps:
- name: Inform starting of deleting EKS cluster
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
icon: GithubActions
logMessage: "Initiating deletion job 🏗️"
- uses: actions/checkout@v4
with:
persist-credentials: true
ref: main
- name: Install Kubectl
uses: azure/setup-kubectl@v3
id: install-kubectl
- name: Install Upbound CLI
run: |
curl -sL "https://cli.upbound.io" | sh
sudo mv up /usr/local/bin/
- uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
icon: GithubActions
logMessage: "Connecting to Upbound control plane 🛰️"
- name: Connect to Upbound using CLI + Fetch kubeconfig
run: |
up login -t ${{ secrets.UPBOUND_TOKEN }}
up ctp kubeconfig get -a ${{ env.UPBOUND_ORG_ID }} ${{ fromJson(inputs.port_context).entity.relations.upbound_control_plane }} -f kubeconfig.yaml --token ${{ secrets.UPBOUND_TOKEN }}
- uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
icon: GithubActions
logMessage: |
❌ Deleting CRDs from Upbound + claim files from the repository for:
Control plane: ${{ fromJson(inputs.port_context).entity.relations.upbound_control_plane }}
Cluster: ${{ fromJson(inputs.port_context).entity }} ❌
- name: Delete cluster from Upbound
run: |
kubectl --kubeconfig kubeconfig.yaml delete -f .up/clusters/${{ fromJson(inputs.port_context).entity.relations.upbound_control_plane }}/${{ fromJson(inputs.port_context).context.entity }}.yaml
- name: Delete cluster yaml file
run: |
git rm -f .up/clusters/${{ fromJson(inputs.port_context).entity.relations.upbound_control_plane }}/${{ fromJson(inputs.port_context).context.entity }}.yaml
git status
- name: Create Pull Request
id: create-pr
uses: peter-evans/create-pull-request@v4
with:
add-paths: .up/clusters
branch: "DELETE-CLUSTER-REQUEST-${{ fromJson(inputs.port_context).entity }}"
title: "Delete cluster request: ${{ fromJson(inputs.port_context).entity }}"
commit-message: "Delete cluster in upbound called ${{ fromJson(inputs.port_context).entity.identifier }}"
- name: Merge Pull Request
uses: juliangruber/merge-pull-request-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
number: ${{ steps.create-pr.outputs.pull-request-number }}
method: squash
- name: "Delete EKS cluster from Port"
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
identifier: ${{ fromJson(inputs.port_context).entity }}
runId: ${{ fromJson(inputs.port_context).runId }}
blueprint: eks_cluster
operation: DELETE
- uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
icon: GithubActions
status: "SUCCESS"
summary: "Deletion successful🚀"
logMessage: "Deletion successful ✅ Deleted EKS Cluster Port entity for: ${{ fromJson(inputs.port_context).entity }}"
deny-cluster-request.yaml (click to expand)
name: Deny cluster request
on:
workflow_dispatch:
inputs:
port_context:
required: true
description: "Port's payload"
type: string
jobs:
deny-cluster-request-port:
if: github.event.inputs.port_context != ''
runs-on: ubuntu-latest
steps:
- name: Inform starting of denying EKS cluster request
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).context.runId }}
logMessage: "Denying EKS cluster request: ${{ fromJson(inputs.port_context).entity.identifier }}"
- uses: actions/checkout@v4
with:
persist-credentials: true
- name: Close Pull Request
uses: peter-evans/close-pull@v3
with:
pull-request-number: ${{ fromJson(inputs.port_context).entity.properties.request_pr_number }}
comment: "Cluster request ${{ fromJson(inputs.port_context).entity.identifier }} was denied ❌. Pull request closed."
delete-branch: false
token: ${{ secrets.GITHUB_TOKEN }}
- name: "Deny EKS cluster request"
if: ${{ always() }}
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
identifier: ${{ fromJson(inputs.port_context).entity.identifier }}
blueprint: eks_cluster_request
runId: ${{ fromJson(inputs.port_context).context.runId }}
properties: |
{
"status": "Denied"
}
- name: Inform that EKS cluster request has been denied
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).context.runId }}
summary: "Request denied."
status: "SUCCESS"
logMessage: "Request updated - status 'denied' ❌"
new-cluster-request.yaml (click to expand)
name: Create new cluster PR
on:
workflow_dispatch:
inputs:
control-plane:
type: string
required: true
cluster-name:
type: string
required: true
description: The cluster name to request
node-count:
type: string
required: false
description: Number of nodes for the cluster
default: "1"
node-size:
required: false
description: "Node size"
type: choice
default: small
options:
- small
- medium
- large
port_context:
type: string
required: true
description: Port Payload
jobs:
create-cluster-request:
runs-on: ubuntu-latest
outputs:
pr-id: ${{ steps.create-pr.outputs.pull-request-number }}
steps:
- name: Inform starting of creating EKS cluster request
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
icon: GithubActions
logMessage: "Initiating EKS cluster request job 🏗️"
- uses: actions/checkout@v4
with:
persist-credentials: true
- name: Manipulate YAML file
run: |
if [[ ! -f ".up/clusters/${{ inputs.control-plane }}/${{ inputs.cluster-name }}.yaml" ]]; then
mkdir -p .up/clusters/${{ inputs.control-plane }}
cp .up/examples/cluster.yaml .up/clusters/${{ inputs.control-plane }}/${{ inputs.cluster-name }}.yaml
else
echo "This cluster already exists!"
exit 1
fi
yq -i e '.metadata.name = "${{ inputs.cluster-name }}"' .up/clusters/${{ inputs.control-plane }}/${{ inputs.cluster-name }}.yaml
yq -i e '.spec.id = "${{ inputs.cluster-name }}"' .up/clusters/${{ inputs.control-plane }}/${{ inputs.cluster-name }}.yaml
yq -i e '.spec.parameters.nodes.count = ${{ inputs.node-count }}' .up/clusters/${{ inputs.control-plane }}/${{ inputs.cluster-name }}.yaml
yq -i e '.spec.parameters.nodes.size = "${{ inputs.node-size }}"' .up/clusters/${{ inputs.control-plane }}/${{ inputs.cluster-name }}.yaml
yq -i e '.spec.writeConnectionSecretToRef.name = "${{ inputs.cluster-name }}-kubeconfig"' .up/clusters/${{ inputs.control-plane }}/${{ inputs.cluster-name }}.yaml
echo "New cluster's YAML:"
cat .up/clusters/${{ inputs.control-plane }}/${{ inputs.cluster-name }}.yaml
- uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
icon: GithubActions
logMessage: |
Cluster request information:
Upbound control plane name: ${{ inputs.control-plane }}
Cluster name: ${{ inputs.cluster-name }}
Node size: ${{ inputs.node-size }}
Node Count: ${{ inputs.node-count }}
Creating pull request for the new cluster⏳
- name: Create Pull Request
id: create-pr
uses: peter-evans/create-pull-request@v4
with:
branch: "CLUSTER-REQUEST-${{ inputs.cluster-name }}"
title: "New cluster request: ${{ inputs.cluster-name }}"
commit-message: "Create new cluster in upbound called ${{ inputs.cluster-name }}"
- name: "Report new EKS cluster request to Port"
if: ${{ fromJson(inputs.port_context).action == 'request_new_cluster' }}
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
identifier: ${{ inputs.cluster-name }}
title: ${{ inputs.cluster-name }}
blueprint: eks_cluster_request
runId: ${{ fromJson(inputs.port_context).runId }}
properties: |
{
"request_pr_url": "${{ steps.create-pr.outputs.pull-request-url }}",
"request_pr_number": ${{ steps.create-pr.outputs.pull-request-number }},
"node_size": "${{ inputs.node-size }}",
"node_count": "${{ inputs.node-count }}"
}
relations: |
{
"upbound_control_plane": "${{ inputs.control-plane }}"
}
- uses: port-labs/port-github-action@v1
if: ${{ fromJson(inputs.port_context).action == 'request_new_cluster' }}
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
link: ${{ steps.create-pr.outputs.pull-request-url }}
icon: GithubActions
logMessage: "Pull request created: ${{ steps.create-pr.outputs.pull-request-url }}"
- uses: port-labs/port-github-action@v1
if: ${{ fromJson(inputs.port_context).action == 'create_new_cluster' }}
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
icon: GithubActions
logMessage: "Creation job run, auto-approving cluster request..."
force-approve-request:
uses: ./.github/workflows/approve-cluster-request.yaml
if: ${{ fromJson(inputs.port_context).action == 'create_new_cluster' }}
secrets: inherit
needs: create-cluster-request
with:
pr-id: ${{ needs.create-cluster-request.outputs.pr-id }}
cluster-name: ${{ inputs.cluster-name }}
node-count: ${{ inputs.node-count }}
node-size: ${{ inputs.node-size }}
run-id: ${{ fromJson(inputs.port_context).runId }}
control-plane: ${{ inputs.control-plane }}
Create the cluster template
Create the file .up/examples/cluster.yaml with the following content:
cluster.yaml (click to expand)
apiVersion: k8s.starter.org/v1alpha1
kind: KubernetesCluster
metadata:
name: my-cluster
namespace: default
spec:
id: my-cluster
parameters:
nodes:
count: 3
size: small
services:
operators:
prometheus:
version: "34.5.1"
writeConnectionSecretToRef:
name: my-cluster-kubeconfig
Create the actions
Create the following self-service actions in Port. Remember to replace CHANGE_TO_YOUR_GITHUB_ORG_NAME and CHANGE_TO_YOUR_REPO_NAME with your actual values.
Make sure to replace <GITHUB_ORG> and <GITHUB_REPO> with your GitHub organization and repository names respectively.
Request new cluster action
- GitHub (Legacy)
- GitHub (Ocean)
Request new cluster action (click to expand)
{
"identifier": "eks_cluster_request_new_cluster",
"title": "Request new cluster",
"icon": "GithubActions",
"trigger": {
"type": "self-service",
"operation": "CREATE",
"userInputs": {
"properties": {
"cluster-name": {
"title": "Cluster Name",
"type": "string"
},
"node-size": {
"title": "Node Size",
"type": "string",
"default": "small",
"enum": ["small", "medium", "large"],
"enumColors": {
"small": "lightGray",
"medium": "lightGray",
"large": "lightGray"
}
},
"node-count": {
"icon": "DefaultProperty",
"title": "Node Count",
"type": "string",
"default": "1"
},
"control-plane": {
"icon": "DefaultProperty",
"title": "Upbound control plane",
"type": "string",
"blueprint": "upbound_control_plane",
"format": "entity"
}
},
"required": ["cluster-name", "control-plane"],
"order": ["control-plane", "cluster-name", "node-size", "node-count"]
},
"blueprintIdentifier": "eks_cluster"
},
"invocationMethod": {
"type": "GITHUB",
"org": "CHANGE_TO_YOUR_GITHUB_ORG_NAME",
"repo": "CHANGE_TO_YOUR_REPO_NAME",
"workflow": "new-cluster-request.yaml",
"workflowInputs": {
"cluster-name": "{{.inputs.\"cluster-name\"}}",
"node-size": "{{.inputs.\"node-size\"}}",
"node-count": "{{.inputs.\"node-count\"}}",
"control-plane": "{{.inputs.\"control-plane\" | if type == \"array\" then map(.identifier) else .identifier end}}",
"port_context": {
"action": "{{ .action.identifier[(\"eks_cluster_\" | length):] }}",
"runId": "{{.run.id}}"
}
},
"reportWorkflowStatus": true
},
"requiredApproval": false
}
Request new cluster action (click to expand)
{
"identifier": "eks_cluster_request_new_cluster",
"title": "Request new cluster",
"icon": "GithubActions",
"trigger": {
"type": "self-service",
"operation": "CREATE",
"userInputs": {
"properties": {
"cluster-name": {
"title": "Cluster Name",
"type": "string"
},
"node-size": {
"title": "Node Size",
"type": "string",
"default": "small",
"enum": ["small", "medium", "large"],
"enumColors": {
"small": "lightGray",
"medium": "lightGray",
"large": "lightGray"
}
},
"node-count": {
"icon": "DefaultProperty",
"title": "Node Count",
"type": "string",
"default": "1"
},
"control-plane": {
"icon": "DefaultProperty",
"title": "Upbound control plane",
"type": "string",
"blueprint": "upbound_control_plane",
"format": "entity"
}
},
"required": ["cluster-name", "control-plane"],
"order": ["control-plane", "cluster-name", "node-size", "node-count"]
},
"blueprintIdentifier": "eks_cluster"
},
"invocationMethod": {
"type": "INTEGRATION_ACTION",
"installationId": "<YOUR_GITHUB_OCEAN_INTEGRATION_ID>",
"integrationActionType": "dispatch_workflow",
"integrationActionExecutionProperties": {
"org": "CHANGE_TO_YOUR_GITHUB_ORG_NAME",
"repo": "CHANGE_TO_YOUR_REPO_NAME",
"workflow": "new-cluster-request.yaml",
"workflowInputs": {
"cluster-name": "{{.inputs.\"cluster-name\"}}",
"node-size": "{{.inputs.\"node-size\"}}",
"node-count": "{{.inputs.\"node-count\"}}",
"control-plane": "{{.inputs.\"control-plane\" | if type == \"array\" then map(.identifier) else .identifier end}}",
"port_context": {
"action": "{{ .action.identifier[(\"eks_cluster_\" | length):] }}",
"runId": "{{.run.id}}"
}
},
"reportWorkflowStatus": true
}
},
"requiredApproval": false
}
Approve cluster request action
- GitHub (Legacy)
- GitHub (Ocean)
Approve cluster request action (click to expand)
{
"identifier": "eks_cluster_request_approve_cluster_request",
"title": "Approve Cluster Request",
"icon": "GithubActions",
"trigger": {
"type": "self-service",
"operation": "DAY-2",
"userInputs": {
"properties": {},
"required": []
},
"blueprintIdentifier": "eks_cluster_request"
},
"invocationMethod": {
"type": "GITHUB",
"org": "CHANGE_TO_YOUR_GITHUB_ORG_NAME",
"repo": "CHANGE_TO_YOUR_REPO_NAME",
"workflow": "approve-cluster-request.yaml",
"workflowInputs": {
"port_context": {
"runId": "{{.run.id}}",
"entity": "{{.entity}}"
}
},
"reportWorkflowStatus": true
},
"requiredApproval": false
}
Approve cluster request action (click to expand)
{
"identifier": "eks_cluster_request_approve_cluster_request",
"title": "Approve Cluster Request",
"icon": "GithubActions",
"trigger": {
"type": "self-service",
"operation": "DAY-2",
"userInputs": {
"properties": {},
"required": []
},
"blueprintIdentifier": "eks_cluster_request"
},
"invocationMethod": {
"type": "INTEGRATION_ACTION",
"installationId": "<YOUR_GITHUB_OCEAN_INTEGRATION_ID>",
"integrationActionType": "dispatch_workflow",
"integrationActionExecutionProperties": {
"org": "CHANGE_TO_YOUR_GITHUB_ORG_NAME",
"repo": "CHANGE_TO_YOUR_REPO_NAME",
"workflow": "approve-cluster-request.yaml",
"workflowInputs": {
"port_context": {
"runId": "{{.run.id}}",
"entity": "{{.entity}}"
}
},
"reportWorkflowStatus": true
}
},
"requiredApproval": false
}
Deny cluster request action
- GitHub (Legacy)
- GitHub (Ocean)
Deny cluster request action (click to expand)
{
"identifier": "eks_cluster_request_deny_cluster_request",
"title": "Deny cluster request",
"icon": "Alert",
"description": "Deny this EKS cluster request",
"trigger": {
"type": "self-service",
"operation": "DAY-2",
"userInputs": {
"properties": {},
"required": []
},
"blueprintIdentifier": "eks_cluster_request"
},
"invocationMethod": {
"type": "GITHUB",
"org": "CHANGE_TO_YOUR_GITHUB_ORG_NAME",
"repo": "CHANGE_TO_YOUR_REPO_NAME",
"workflow": "deny-cluster-request.yaml",
"workflowInputs": {
"port_context": {
"entity": "{{.entity}}",
"runId": "{{.run.id}}"
}
},
"reportWorkflowStatus": true
},
"requiredApproval": false
}
Deny cluster request action (click to expand)
{
"identifier": "eks_cluster_request_deny_cluster_request",
"title": "Deny cluster request",
"icon": "Alert",
"description": "Deny this EKS cluster request",
"trigger": {
"type": "self-service",
"operation": "DAY-2",
"userInputs": {
"properties": {},
"required": []
},
"blueprintIdentifier": "eks_cluster_request"
},
"invocationMethod": {
"type": "INTEGRATION_ACTION",
"installationId": "<YOUR_GITHUB_OCEAN_INTEGRATION_ID>",
"integrationActionType": "dispatch_workflow",
"integrationActionExecutionProperties": {
"org": "CHANGE_TO_YOUR_GITHUB_ORG_NAME",
"repo": "CHANGE_TO_YOUR_REPO_NAME",
"workflow": "deny-cluster-request.yaml",
"workflowInputs": {
"port_context": {
"entity": "{{.entity}}",
"runId": "{{.run.id}}"
}
},
"reportWorkflowStatus": true
}
},
"requiredApproval": false
}
Create new cluster action
- GitHub (Legacy)
- GitHub (Ocean)
Create new cluster action (click to expand)
{
"identifier": "eks_cluster_create_new_cluster",
"title": "Create new cluster",
"icon": "GithubActions",
"trigger": {
"type": "self-service",
"operation": "CREATE",
"userInputs": {
"properties": {
"cluster-name": {
"title": "Cluster Name",
"type": "string"
},
"node-size": {
"title": "Node Size",
"type": "string",
"default": "small",
"enum": ["small", "medium", "large"],
"enumColors": {
"small": "lightGray",
"medium": "lightGray",
"large": "lightGray"
}
},
"node-count": {
"icon": "DefaultProperty",
"title": "Node Count",
"type": "string",
"default": "1"
},
"control-plane": {
"title": "Upbound control plane",
"type": "string",
"blueprint": "upbound_control_plane",
"format": "entity"
}
},
"required": ["cluster-name", "control-plane"],
"order": ["control-plane", "cluster-name", "node-size", "node-count"]
},
"blueprintIdentifier": "eks_cluster"
},
"invocationMethod": {
"type": "GITHUB",
"org": "CHANGE_TO_YOUR_GITHUB_ORG_NAME",
"repo": "CHANGE_TO_YOUR_REPO_NAME",
"workflow": "new-cluster-request.yaml",
"workflowInputs": {
"cluster-name": "{{.inputs.\"cluster-name\"}}",
"node-size": "{{.inputs.\"node-size\"}}",
"node-count": "{{.inputs.\"node-count\"}}",
"control-plane": "{{.inputs.\"control-plane\" | if type == \"array\" then map(.identifier) else .identifier end}}",
"port_context": {
"action": "{{ .action.identifier[(\"eks_cluster_\" | length):] }}",
"runId": "{{.run.id}}"
}
},
"reportWorkflowStatus": true
},
"requiredApproval": false
}
Create new cluster action (click to expand)
{
"identifier": "eks_cluster_create_new_cluster",
"title": "Create new cluster",
"icon": "GithubActions",
"trigger": {
"type": "self-service",
"operation": "CREATE",
"userInputs": {
"properties": {
"cluster-name": {
"title": "Cluster Name",
"type": "string"
},
"node-size": {
"title": "Node Size",
"type": "string",
"default": "small",
"enum": ["small", "medium", "large"],
"enumColors": {
"small": "lightGray",
"medium": "lightGray",
"large": "lightGray"
}
},
"node-count": {
"icon": "DefaultProperty",
"title": "Node Count",
"type": "string",
"default": "1"
},
"control-plane": {
"title": "Upbound control plane",
"type": "string",
"blueprint": "upbound_control_plane",
"format": "entity"
}
},
"required": ["cluster-name", "control-plane"],
"order": ["control-plane", "cluster-name", "node-size", "node-count"]
},
"blueprintIdentifier": "eks_cluster"
},
"invocationMethod": {
"type": "INTEGRATION_ACTION",
"installationId": "<YOUR_GITHUB_OCEAN_INTEGRATION_ID>",
"integrationActionType": "dispatch_workflow",
"integrationActionExecutionProperties": {
"org": "CHANGE_TO_YOUR_GITHUB_ORG_NAME",
"repo": "CHANGE_TO_YOUR_REPO_NAME",
"workflow": "new-cluster-request.yaml",
"workflowInputs": {
"cluster-name": "{{.inputs.\"cluster-name\"}}",
"node-size": "{{.inputs.\"node-size\"}}",
"node-count": "{{.inputs.\"node-count\"}}",
"control-plane": "{{.inputs.\"control-plane\" | if type == \"array\" then map(.identifier) else .identifier end}}",
"port_context": {
"action": "{{ .action.identifier[(\"eks_cluster_\" | length):] }}",
"runId": "{{.run.id}}"
}
},
"reportWorkflowStatus": true
}
},
"requiredApproval": false
}
Delete EKS cluster action
- GitHub (Legacy)
- GitHub (Ocean)
Delete EKS cluster action (click to expand)
{
"identifier": "eks_cluster_delete_eks_cluster",
"title": "Delete EKS Cluster",
"icon": "Alert",
"trigger": {
"type": "self-service",
"operation": "DELETE",
"userInputs": {
"properties": {},
"required": [],
"order": []
},
"blueprintIdentifier": "eks_cluster"
},
"invocationMethod": {
"type": "GITHUB",
"org": "CHANGE_TO_YOUR_GITHUB_ORG_NAME",
"repo": "CHANGE_TO_YOUR_REPO_NAME",
"workflow": "delete-cluster.yaml",
"workflowInputs": {
"port_context": {
"runId": "{{.run.id}}",
"entity": "{{.entity}}"
}
},
"reportWorkflowStatus": true
},
"requiredApproval": true,
"approvalNotification": {
"type": "email"
}
}
Delete EKS cluster action (click to expand)
{
"identifier": "eks_cluster_delete_eks_cluster",
"title": "Delete EKS Cluster",
"icon": "Alert",
"trigger": {
"type": "self-service",
"operation": "DELETE",
"userInputs": {
"properties": {},
"required": [],
"order": []
},
"blueprintIdentifier": "eks_cluster"
},
"invocationMethod": {
"type": "INTEGRATION_ACTION",
"installationId": "<YOUR_GITHUB_OCEAN_INTEGRATION_ID>",
"integrationActionType": "dispatch_workflow",
"integrationActionExecutionProperties": {
"org": "CHANGE_TO_YOUR_GITHUB_ORG_NAME",
"repo": "CHANGE_TO_YOUR_REPO_NAME",
"workflow": "delete-cluster.yaml",
"workflowInputs": {
"port_context": {
"runId": "{{.run.id}}",
"entity": "{{.entity}}"
}
},
"reportWorkflowStatus": true
}
},
"requiredApproval": true,
"approvalNotification": {
"type": "email"
}
}
Create Upbound control plane entities
After setting up the blueprints and actions, create entities for your Upbound control planes:
-
Navigate to the Upbound control planes catalog page.
-
Click Manually add Upbound Control Plane (or the + Upbound Control plane button).
- In the identifier field, enter the Upbound control plane identifier you saved earlier and click Create.
Repeat this step for each control plane in your Upbound organization.
Test the setup
Navigate to the Self-service page to see your actions:
You can now:
- Request a new cluster: Creates a PR for approval.
- Approve/Deny cluster requests: Manage pending requests.
- Create a new cluster directly: Bypasses the approval workflow.
- Delete an EKS cluster: Removes the cluster from Upbound and Port.