Scaffold Azure DevOps Repositories Using Cookiecutter
Overviewโ
This guide will help you implement a self-service action in Port that allows you to quickly scaffold Azure DevOps repositories using Cookiecutter templates. This streamlines project initialization and ensures consistent repository structure across your organization.
Prerequisitesโ
- Complete the onboarding process.
- Access to Azure DevOps with permissions to create repositories and pipelines.
Set up data modelโ
If you haven't installed the Azure DevOps integration, you'll need to create a blueprint for Azure DevOps repositories.
However, we highly recommend you install the Azure DevOps integration to have this automatically set up for you.
Create the Azure DevOps repository 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:
Azure DevOps Repository Blueprint (Click to expand)
{
"identifier": "azureDevopsRepository",
"title": "Azure DevOps Repository",
"icon": "Azure",
"schema": {
"properties": {
"url": {
"title": "URL",
"type": "string",
"format": "url",
"description": "The URL of the Azure DevOps repository"
},
"readme": {
"title": "README",
"type": "string",
"description": "The content of the repository's README file"
},
"defaultBranch": {
"title": "Default Branch",
"type": "string",
"description": "The default branch of the repository"
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
} -
Click "Save" to create the blueprint.
Implementationโ
Set up Azure DevOps environmentโ
-
Create an Azure DevOps Repository called
python_scaffolder
in your Azure DevOps Organization/Project: -
Configure a service connection to Azure DevOps:
Service connection configurationUse
port_trigger
for bothWebHook Name
andService connection name
when configuring your Service Connection
Create a self-service actionโ
-
Head to the self-service page in Port.
-
Click on
+ New Action
. -
Click on the
{...}
button in the top right corner, and choose "Edit JSON". -
Copy and paste the following JSON configuration:
Scaffolding new repository (Click to expand)
{
"identifier": "azure_scaffolder",
"title": "Azure Scaffolder",
"icon": "Azure",
"trigger": {
"type": "self-service",
"operation": "CREATE",
"userInputs": {
"properties": {
"service_name": {
"icon": "DefaultProperty",
"title": "Service Name",
"type": "string",
"description": "Name of the service to scaffold"
},
"azure_organization": {
"icon": "DefaultProperty",
"title": "Azure Organization",
"type": "string",
"description": "Your Azure DevOps organization name"
},
"azure_project": {
"icon": "DefaultProperty",
"title": "Azure Project",
"type": "string",
"description": "Your Azure DevOps project name"
},
"description": {
"icon": "DefaultProperty",
"title": "Description",
"type": "string",
"description": "Service description"
}
},
"required": [
"service_name"
],
"order": [
"service_name",
"azure_organization",
"azure_project",
"description"
]
},
"blueprintIdentifier": "azureDevopsRepository"
},
"invocationMethod": {
"type": "AZURE_DEVOPS",
"webhook": "port_trigger",
"org": "<AZURE_DEVOPS_ORGANIZATION_NAME>",
"payload": {
"properties": {
"service_name": "{{.inputs.\"service_name\"}}",
"azure_organization": "{{.inputs.\"azure_organization\"}}",
"azure_project": "{{.inputs.\"azure_project\"}}",
"description": "{{.inputs.\"description\"}}"
},
"port_context": {
"entity": "{{.entity}}",
"blueprint": "{{.action.blueprint}}",
"runId": "{{.run.id}}",
"trigger": "{{ .trigger }}"
}
}
},
"requiredApproval": false
}Replace the organization nameMake sure to replace
<AZURE_DEVOPS_ORGANIZATION_NAME>
with your actual Azure DevOps organization name. -
Click
Save
to create the action.
Create an Azure DevOps pipelineโ
-
In your
python_scaffolder
repository, create a file calledazure-pipelines.yml
in the root with the following content:Azure DevOps Pipeline YAML (Click to expand)
trigger: none
pool:
vmImage: "ubuntu-latest"
variables:
RUN_ID: "${{ parameters.port_trigger.port_context.runId }}"
BLUEPRINT_ID: "${{ parameters.port_trigger.port_context.blueprint }}"
SERVICE_NAME: "${{ parameters.port_trigger.properties.service_name }}"
DESCRIPTION: "${{ parameters.port_trigger.properties.description }}"
AZURE_ORGANIZATION: "${{ parameters.port_trigger.properties.azure_organization }}"
AZURE_PROJECT: "${{ parameters.port_trigger.properties.azure_project }}"
resources:
webhooks:
- webhook: port_trigger
connection: port_trigger
stages:
- stage: fetch_port_access_token
jobs:
- job: fetch_port_access_token
steps:
- script: |
sudo apt-get update
sudo apt-get install -y jq
- script: |
accessToken=$(curl -X POST \
-H 'Content-Type: application/json' \
-d '{"clientId": "$(PORT_CLIENT_ID)", "clientSecret": "$(PORT_CLIENT_SECRET)"}' \
-s 'https://api.getport.io/v1/auth/access_token' | jq -r '.accessToken')
echo "##vso[task.setvariable variable=accessToken;isOutput=true]$accessToken"
displayName: Fetch Access Token and Run ID
name: getToken
- stage: scaffold
dependsOn:
- fetch_port_access_token
jobs:
- job: scaffold
variables:
COOKIECUTTER_TEMPLATE_URL: "https://github.com/brettcannon/python-azure-web-app-cookiecutter"
steps:
- script: |
sudo apt-get update
sudo apt-get install -y jq
sudo pip install cookiecutter -q
- script: |
# Create the repository
PROJECT_ID=$(curl -X GET "https://dev.azure.com/${{ variables.AZURE_ORGANIZATION }}/_apis/projects/${{ variables.AZURE_PROJECT }}?api-version=7.0" \
-H "Authorization: Basic $(PERSONAL_ACCESS_TOKEN)" \
-H "Content-Type: application/json" \
-H "Content-Length: 0" | jq .id)
if [[ -z "$PROJECT_ID" ]]; then
echo "Failed to fetch Azure Devops Project ID."
exit 1
fi
PAYLOAD='{"name":"${{ variables.SERVICE_NAME }}","project":{"id":'$PROJECT_ID'}}'
CREATE_REPO_RESPONSE=$(curl -X POST "https://dev.azure.com/${{ variables.AZURE_ORGANIZATION }}/_apis/git/repositories?api-version=7.0" \
-H "Authorization: Basic $(PERSONAL_ACCESS_TOKEN)" \
-H "Content-Type: application/json" \
-d $PAYLOAD)
PROJECT_URL=$(echo $CREATE_REPO_RESPONSE | jq -r .webUrl)
if [[ -z "$PROJECT_URL" ]]; then
echo "Failed to create Azure Devops repository."
exit 1
fi
echo "##vso[task.setvariable variable=PROJECT_URL;isOutput=true]$PROJECT_URL"
cat <<EOF > cookiecutter.yaml
default_context:
site_name: "${{ variables.SERVICE_NAME }}"
python_version: "3.6.0"
EOF
cookiecutter $COOKIECUTTER_TEMPLATE_URL --no-input --config-file cookiecutter.yaml --output-dir scaffold_out
echo "Initializing new repository..."
git config --global user.email "scaffolder@email.com"
git config --global user.name "Mighty Scaffolder"
git config --global init.defaultBranch "main"
cd "scaffold_out/${{ variables.SERVICE_NAME }}"
git init
git add .
git commit -m "Initial commit"
decoded_pat=$(echo $(PERSONAL_ACCESS_TOKEN) | base64 -d)
git remote add origin https://$decoded_pat@dev.azure.com/${{ variables.AZURE_ORGANIZATION }}/${{ variables.AZURE_PROJECT }}/_git/${{ variables.SERVICE_NAME }}
git push -u origin --all
name: scaffold
- stage: upsert_entity
dependsOn:
- fetch_port_access_token
- scaffold
jobs:
- job: upsert_entity
variables:
accessToken: $[ stageDependencies.fetch_port_access_token.fetch_port_access_token.outputs['getToken.accessToken'] ]
PROJECT_URL: $[ stageDependencies.scaffold.scaffold.outputs['scaffold.PROJECT_URL'] ]
steps:
- script: |
sudo apt-get update
sudo apt-get install -y jq
- script: |
curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer $(accessToken)' \
-d '{
"identifier": "${{ variables.SERVICE_NAME }}",
"title": "${{ variables.SERVICE_NAME }}",
"properties": {"description":"${{ variables.DESCRIPTION }}","url":"$(PROJECT_URL)" },
"relations": {}
}' \
"https://api.getport.io/v1/blueprints/${{ variables.BLUEPRINT_ID }}/entities?upsert=true&run_id=${{ variables.RUN_ID }}&create_missing_related_entities=true"
- stage: update_run_status
dependsOn:
- upsert_entity
- fetch_port_access_token
- scaffold
jobs:
- job: update_run_status
variables:
accessToken: $[ stageDependencies.fetch_port_access_token.fetch_port_access_token.outputs['getToken.accessToken'] ]
PROJECT_URL: $[ stageDependencies.scaffold.scaffold.outputs['scaffold.PROJECT_URL'] ]
steps:
- script: |
sudo apt-get update
sudo apt-get install -y jq
- script: |
curl -X PATCH \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer $(accessToken)' \
-d '{"status":"SUCCESS", "message": {"run_status": "Scaffold ${{ variables.SERVICE_NAME }} finished successfully!\\n Project URL: $(PROJECT_URL)" }}' \
"https://api.getport.io/v1/actions/runs/${{ variables.RUN_ID }}"
- stage: update_run_status_failed
dependsOn:
- upsert_entity
- fetch_port_access_token
- scaffold
condition: failed()
jobs:
- job: update_run_status_failed
variables:
accessToken: $[ stageDependencies.fetch_port_access_token.fetch_port_access_token.outputs['getToken.accessToken'] ]
steps:
- script: |
curl -X PATCH \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer $(accessToken)' \
-d '{"status":"FAILURE", "message": {"run_status": "Scaffold ${{ variables.SERVICE_NAME }} failed" }}' \
"https://api.getport.io/v1/actions/runs/${{ variables.RUN_ID }}"Customize the templateYou can create your own Cookiecutter templates by following the official guide. Simply update the
COOKIECUTTER_TEMPLATE_URL
variable in the pipeline YAML to use your own template. -
Configure the pipeline in Azure DevOps:
- Go to Pipelines โ Create Pipeline โ Azure Repos Git and choose
python_scaffolder
. - Click Save (in the "Run" dropdown menu).
- Go to Pipelines โ Create Pipeline โ Azure Repos Git and choose
-
Create the following secret variables in your pipeline:
-
PERSONAL_ACCESS_TOKEN
- A base64-encoded Azure DevOps PAT with Code (Full) and Release (Read, write & execute) permissions.
To encode your Personal Access Token:MY_PAT=YourPAT
B64_PAT=$(printf ":%s" "$MY_PAT" | base64)
echo $B64_PAT -
PORT_CLIENT_ID
- Port Client ID learn more. -
PORT_CLIENT_SECRET
- Port Client Secret learn more.
-
Let's test it!โ
- Head to the self-service page of your portal.
- Click on the
Azure Scaffolder
action. - Fill in the required details:
- Service Name (required).
- Azure Organization (if different from default).
- Azure Project (if different from default).
- Description.
- Click on
Execute
. - Wait for the Azure DevOps pipeline to create your new repository with the Cookiecutter template.