Scaffold BitBucket Repositories Using Cookiecutter
This example demonstrates how to quickly scaffold BitBucket repositories using a Cookiecutter Template via Port Actions.
In addition, as cookiecutter is an open-source project you can make your own project template, learn more about it here.
Example - scaffolding golang template
Follow these steps to get started with the Golang template:
-
Create the following as Jenkins Credentials:
BITBUCKET_USERNAME
- a user with access to the BitBucket Workspace and Project.BITBUCKET_APP_PASSWORD
- an App Password with theRepositories:Read
andRepositories:Write
permissions permissions.PORT_CLIENT_ID
- Port Client ID learn more.PORT_CLIENT_SECRET
- Port Client Secret learn more.
-
Create a Port blueprint with the following properties:
Keep in mind this can be any blueprint you would like and this is just an example.
{
"identifier": "microservice",
"title": "Microservice",
"icon": "Microservice",
"schema": {
"properties": {
"description": {
"title": "Description",
"type": "string"
},
"url": {
"title": "URL",
"format": "url",
"type": "string"
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
}
- Create Port action using the following JSON definition:
Make sure to replace the placeholders for JENKINS_URL and JOB_TOKEN.
{
"identifier": "microservice_scaffold_bitbucket",
"title": "Scaffold Golang Microservice - BitBucket",
"icon": "Go",
"description": "Creates a repo for new golang Microservice on Bitbucket",
"trigger": {
"type": "self-service",
"operation": "CREATE",
"userInputs": {
"properties": {
"repo_name": {
"icon": "Microservice",
"title": "Repo Name",
"type": "string"
},
"bitbucket_workspace_name": {
"icon": "BitBucket",
"title": "Bitbucket Workspace Name",
"type": "string"
},
"bitbucket_project_key": {
"title": "Bitbucket Project Key",
"icon": "BitBucket",
"description": "Bitbucket project key symbol",
"type": "string"
}
},
"required": [
"repo_name",
"bitbucket_workspace_name",
"bitbucket_project_key"
]
},
"blueprintIdentifier": "microservice"
},
"invocationMethod": {
"type": "WEBHOOK",
"url": "https://<JENKINS_URL>/generic-webhook-trigger/invoke?token=<JOB_TOKEN>",
"agent": false,
"synchronized": false,
"method": "POST",
"body": {
"action": "{{ .action.identifier[(\"microservice_\" | length):] }}",
"resourceType": "run",
"status": "TRIGGERED",
"trigger": "{{ .trigger | {by, origin, at} }}",
"context": {
"entity": "{{.entity.identifier}}",
"blueprint": "{{.action.blueprint}}",
"runId": "{{.run.id}}"
},
"payload": {
"entity": "{{ (if .entity == {} then null else .entity end) }}",
"action": {
"invocationMethod": {
"type": "WEBHOOK",
"agent": false,
"url": "https://<JENKINS_URL>/generic-webhook-trigger/invoke?token=<JOB_TOKEN>",
"synchronized": false,
"method": "POST"
},
"trigger": "{{.trigger.operation}}"
},
"properties": {
"{{if (.inputs | has(\"repo_name\")) then \"repo_name\" else null end}}": "{{.inputs.\"repo_name\"}}",
"{{if (.inputs | has(\"bitbucket_workspace_name\")) then \"bitbucket_workspace_name\" else null end}}": "{{.inputs.\"bitbucket_workspace_name\"}}",
"{{if (.inputs | has(\"bitbucket_project_key\")) then \"bitbucket_project_key\" else null end}}": "{{.inputs.\"bitbucket_project_key\"}}"
},
"censoredProperties": "{{.action.encryptedProperties}}"
}
}
},
"publish": true
}
-
Create a Jenkins Pipeline with the following configuration:
-
Define variables for a pipeline: Define the REPO_NAME, BITBUCKET_WORKSPACE_NAME, BITBUCKET_PROJECT_KEY and RUN_ID variables.
-
Token Setup: Define the token to match
JOB_TOKEN
as configured in your Port Action.
-
Add the following content to the new pipeline:
Jenkins Pipeline Script
import groovy.json.JsonSlurper
pipeline {
agent any
environment {
COOKIECUTTER_TEMPLATE = 'https://github.com/lacion/cookiecutter-golang'
REPO_NAME = "${REPO_NAME}"
BITBUCKET_WORKSPACE_NAME = "${BITBUCKET_WORKSPACE_NAME}"
BITBUCKET_PROJECT_KEY = "${BITBUCKET_PROJECT_KEY}"
SCAFFOLD_DIR = "scaffold_${REPO_NAME}"
PORT_ACCESS_TOKEN = ""
PORT_BLUEPRINT_ID = "microservice"
PORT_RUN_ID = "${RUN_ID}"
}
stages {
stage('Get access token') {
steps {
script {
withCredentials([
string(credentialsId: 'PORT_CLIENT_ID', variable: 'PORT_CLIENT_ID'),
string(credentialsId: 'PORT_CLIENT_SECRET', variable: 'PORT_CLIENT_SECRET')
]) {
// Execute the curl command and capture the output
def result = sh(returnStdout: true, script: """
accessTokenPayload=\$(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")
echo \$accessTokenPayload
""")
// Parse the JSON response using JsonSlurper
def jsonSlurper = new JsonSlurper()
def payloadJson = jsonSlurper.parseText(result.trim())
// Access the desired data from the payload
PORT_ACCESS_TOKEN = payloadJson.accessToken
}
}
}
} // end of stage Get access token
stage('Create BitBucket Repository') {
steps {
script {
def logs_report_response = sh(script: """
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${PORT_ACCESS_TOKEN}" \
-d '{"message": "Creating BitBucket repository: ${REPO_NAME} in Workspace: ${BITBUCKET_WORKSPACE_NAME}, Project: ${BITBUCKET_PROJECT_KEY}..."}' \
"https://api.getport.io/v1/actions/runs/${PORT_RUN_ID}/logs"
""", returnStdout: true)
println(logs_report_response)
}
script {
withCredentials([
string(credentialsId: 'BITBUCKET_USERNAME', variable: 'BITBUCKET_USERNAME'),
string(credentialsId: 'BITBUCKET_APP_PASSWORD', variable: 'BITBUCKET_APP_PASSWORD')
]) {
sh """
curl -i -u ${BITBUCKET_USERNAME}:${BITBUCKET_APP_PASSWORD} \\
-d '{"is_private": true, "scm": "git", "project": {"key": "${BITBUCKET_PROJECT_KEY}"}}' \\
https://api.bitbucket.org/2.0/repositories/${BITBUCKET_WORKSPACE_NAME}/${REPO_NAME}
"""
}
}
}
} // end of stage Create BitBucket Repository
stage('Scaffold Cookiecutter Template') {
steps {
script {
def logs_report_response = sh(script: """
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${PORT_ACCESS_TOKEN}" \
-d '{"message": "Scaffolding ${REPO_NAME}..."}' \
"https://api.getport.io/v1/actions/runs/${PORT_RUN_ID}/logs"
""", returnStdout: true)
println(logs_report_response)
}
script {
withCredentials([
string(credentialsId: 'BITBUCKET_USERNAME', variable: 'BITBUCKET_USERNAME'),
string(credentialsId: 'BITBUCKET_APP_PASSWORD', variable: 'BITBUCKET_APP_PASSWORD')
]) {
def yamlContent = """
default_context:
full_name: "Full Name"
github_username: "bitbucketuser"
app_name: "${REPO_NAME}"
project_short_description": "A Golang project."
docker_hub_username: "dockerhubuser"
docker_image: "dockerhubuser/alpine-base-image:latest"
docker_build_image: "dockerhubuser/alpine-golang-buildimage"
"""
// Write the YAML content to a file
writeFile(file: 'cookiecutter.yaml', text: yamlContent)
sh("""
rm -rf ${SCAFFOLD_DIR} ${REPO_NAME}
git clone https://${BITBUCKET_USERNAME}:${BITBUCKET_APP_PASSWORD}@bitbucket.org/${BITBUCKET_WORKSPACE_NAME}/${REPO_NAME}.git
cookiecutter ${COOKIECUTTER_TEMPLATE} --output-dir ${SCAFFOLD_DIR} --no-input --config-file cookiecutter.yaml -f
rm -rf ${SCAFFOLD_DIR}/${REPO_NAME}/.git*
cp -r ${SCAFFOLD_DIR}/${REPO_NAME}/* "${REPO_NAME}/"
cd ${REPO_NAME}
git config user.name "Jenkins Pipeline Bot"
git config user.email "jenkins-pipeline[bot]@users.noreply.jenkins.com"
git add .
git commit -m "Scaffolded project ${REPO_NAME}"
git push -u origin master
cd ..
rm -rf ${SCAFFOLD_DIR} ${REPO_NAME}
""")
}
}
}
} // end of stage Clone Cookiecutter Template
stage('CREATE Microservice entity') {
steps {
script {
def logs_report_response = sh(script: """
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${PORT_ACCESS_TOKEN}" \
-d '{"message": "Creating ${REPO_NAME} Microservice Port entity..."}' \
"https://api.getport.io/v1/actions/runs/${PORT_RUN_ID}/logs"
""", returnStdout: true)
println(logs_report_response)
}
script {
def status_report_response = sh(script: """
curl --location --request POST "https://api.getport.io/v1/blueprints/$PORT_BLUEPRINT_ID/entities?upsert=true&run_id=$PORT_RUN_ID&create_missing_related_entities=true" \
--header "Authorization: Bearer $PORT_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data-raw '{
"identifier": "${REPO_NAME}",
"title": "${REPO_NAME}",
"properties": {"description":"${REPO_NAME} golang project","url":"https://bitbucket.org/${BITBUCKET_WORKSPACE_NAME}/${REPO_NAME}/src"},
"relations": {}
}'
""", returnStdout: true)
println(status_report_response)
}
}
} // end of stage CREATE Microservice entity
stage('Update Port Run Status') {
steps {
script {
def status_report_response = sh(script: """
curl -X PATCH \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${PORT_ACCESS_TOKEN}" \
-d '{"status":"SUCCESS", "message": {"run_status": "Scaffold Jenkins Pipeline completed successfully!"}}' \
"https://api.getport.io/v1/actions/runs/${PORT_RUN_ID}"
""", returnStdout: true)
println(status_report_response)
}
}
} // end of stage Update Port Run Status
}
post {
failure {
// Update Port Run failed.
script {
def status_report_response = sh(script: """
curl -X PATCH \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${PORT_ACCESS_TOKEN}" \
-d '{"status":"FAILURE", "message": {"run_status": "Failed to Scaffold ${REPO_NAME}"}}' \
"https://api.getport.io/v1/actions/runs/${PORT_RUN_ID}"
""", returnStdout: true)
println(status_report_response)
}
}
// Clean after build
always {
cleanWs(cleanWhenNotBuilt: false,
deleteDirs: true,
disableDeferredWipeout: false,
notFailBuild: true,
patterns: [[pattern: '.gitignore', type: 'INCLUDE'],
[pattern: '.propsfile', type: 'EXCLUDE']])
}
}
}
- Trigger the action from the Self-service tab of your Port application.