Agents cicd-bitbucket-pipes-dev
Develop custom Bitbucket Pipes for reusable CI/CD components. Use when creating pipes for organization-wide use, building Docker-based automation steps, packaging pipes for the Atlassian Marketplace, or designing reusable pipeline components.
install
source · Clone the upstream repo
git clone https://github.com/aRustyDev/agents
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/skills/cicd-bitbucket-pipes-dev" ~/.claude/skills/arustydev-agents-cicd-bitbucket-pipes-dev && rm -rf "$T"
manifest:
content/skills/cicd-bitbucket-pipes-dev/SKILL.mdsource content
Bitbucket Pipes Development
Guide for developing custom Bitbucket Pipes - reusable, containerized CI/CD components for Bitbucket Pipelines.
When to Use This Skill
- Creating custom pipes for your organization
- Building Docker-based automation components
- Packaging pipes for distribution
- Designing reusable pipeline steps
- Understanding pipe structure and conventions
- Publishing pipes to the Atlassian Marketplace
What is a Pipe?
A pipe is a Docker container that runs a specific CI/CD task. Pipes encapsulate:
- Docker image with all dependencies
- Entry script that performs the task
- Input validation and error handling
- Consistent output formatting
Pipe Structure
Directory Layout
my-pipe/ ├── Dockerfile # Container definition ├── pipe.sh # Main entry script (bash) ├── pipe.yml # Pipe metadata ├── README.md # Documentation ├── CHANGELOG.md # Version history ├── LICENSE # License file ├── test/ # Test files │ ├── test-basic.bats # Bats tests │ └── fixtures/ # Test fixtures └── bitbucket-pipelines.yml # CI for the pipe itself
pipe.yml (Metadata)
name: My Custom Pipe image: myorg/my-pipe:1.0.0 description: A pipe that does something useful variables: - name: USERNAME description: The username for authentication required: true - name: PASSWORD description: The password for authentication required: true secret: true - name: DEBUG description: Enable debug mode default: 'false' required: false - name: ENVIRONMENT description: Target environment default: 'production' allowed_values: - development - staging - production repository: https://bitbucket.org/myorg/my-pipe maintainer: maintainer@example.com tags: - deployment - automation
Dockerfile
FROM python:3.11-slim # Install dependencies RUN pip install --no-cache-dir \ requests \ bitbucket-pipes-toolkit # Copy pipe script COPY pipe.sh /pipe.sh RUN chmod +x /pipe.sh # Set entrypoint ENTRYPOINT ["/pipe.sh"]
pipe.sh (Entry Script)
#!/usr/bin/env bash # Import common functions source "$(dirname "$0")/common.sh" # Enable strict mode set -euo pipefail # Validate required variables check_required_vars() { : "${USERNAME:?'USERNAME variable is required'}" : "${PASSWORD:?'PASSWORD variable is required'}" } # Main function main() { check_required_vars info "Starting pipe execution..." # Set defaults DEBUG="${DEBUG:-false}" ENVIRONMENT="${ENVIRONMENT:-production}" if [[ "${DEBUG}" == "true" ]]; then enable_debug fi info "Deploying to ${ENVIRONMENT}" # Perform the main task if deploy_application; then success "Deployment completed successfully" else fail "Deployment failed" fi } deploy_application() { # Your deployment logic here curl -X POST "https://api.example.com/deploy" \ -u "${USERNAME}:${PASSWORD}" \ -d "environment=${ENVIRONMENT}" } main
Common Functions
common.sh Template
#!/usr/bin/env bash # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Logging functions info() { echo -e "${BLUE}INFO:${NC} $*" } success() { echo -e "${GREEN}✔ SUCCESS:${NC} $*" } warning() { echo -e "${YELLOW}⚠ WARNING:${NC} $*" } fail() { echo -e "${RED}✖ ERROR:${NC} $*" exit 1 } debug() { if [[ "${DEBUG:-false}" == "true" ]]; then echo -e "${YELLOW}DEBUG:${NC} $*" fi } enable_debug() { set -x } # Variable validation require_var() { local var_name="$1" local var_value="${!var_name:-}" if [[ -z "${var_value}" ]]; then fail "Required variable '${var_name}' is not set" fi } # Check if command exists require_command() { local cmd="$1" if ! command -v "${cmd}" &> /dev/null; then fail "Required command '${cmd}' not found" fi }
Using the Pipes Toolkit
Python Toolkit
#!/usr/bin/env python3 from bitbucket_pipes_toolkit import Pipe class MyPipe(Pipe): def __init__(self): super().__init__( pipe_metadata='/pipe.yml', schema='/schema.json' # Optional JSON Schema ) def run(self): super().run() # Access validated variables username = self.get_variable('USERNAME') environment = self.get_variable('ENVIRONMENT') self.log_info(f"Deploying to {environment}") try: self.deploy(username, environment) self.success("Deployment completed") except Exception as e: self.fail(f"Deployment failed: {e}") def deploy(self, username, environment): # Deployment logic pass if __name__ == '__main__': pipe = MyPipe() pipe.run()
Install Toolkit
FROM python:3.11-slim RUN pip install bitbucket-pipes-toolkit COPY pipe.py /pipe.py RUN chmod +x /pipe.py ENTRYPOINT ["python3", "/pipe.py"]
Variable Handling
Required Variables
# pipe.yml variables: - name: API_TOKEN description: API authentication token required: true secret: true # Masked in logs
# pipe.sh require_var "API_TOKEN"
Optional with Defaults
variables: - name: TIMEOUT description: Request timeout in seconds default: '30' required: false
TIMEOUT="${TIMEOUT:-30}"
Enum/Allowed Values
variables: - name: LOG_LEVEL description: Logging verbosity default: 'INFO' allowed_values: - DEBUG - INFO - WARNING - ERROR
Multiple Values
# Accept comma-separated list IFS=',' read -ra TARGETS <<< "${DEPLOY_TARGETS}" for target in "${TARGETS[@]}"; do deploy_to "$target" done
Testing Pipes
Local Testing
# Build the pipe docker build -t my-pipe . # Run with environment variables docker run -it --rm \ -e USERNAME=myuser \ -e PASSWORD=mypass \ -e ENVIRONMENT=staging \ my-pipe
Bats Testing
#!/usr/bin/env bats # test/test-basic.bats setup() { export USERNAME="test-user" export PASSWORD="test-pass" } @test "pipe succeeds with valid inputs" { run ./pipe.sh [ "$status" -eq 0 ] [[ "$output" =~ "SUCCESS" ]] } @test "pipe fails without USERNAME" { unset USERNAME run ./pipe.sh [ "$status" -eq 1 ] [[ "$output" =~ "USERNAME" ]] } @test "pipe respects DEBUG flag" { export DEBUG="true" run ./pipe.sh [[ "$output" =~ "DEBUG" ]] }
CI for Pipes
# bitbucket-pipelines.yml image: docker:latest pipelines: default: - step: name: Build and Test services: - docker script: - docker build -t my-pipe . - docker run -e USERNAME=test -e PASSWORD=test my-pipe - step: name: Push to Registry services: - docker script: - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD - docker build -t myorg/my-pipe:$BITBUCKET_TAG . - docker push myorg/my-pipe:$BITBUCKET_TAG condition: changesets: includePaths: - "**" artifacts: - "**" trigger: manual definitions: services: docker: memory: 2048
Using Your Pipe
In Pipelines
# bitbucket-pipelines.yml pipelines: default: - step: name: Deploy script: - pipe: myorg/my-pipe:1.0.0 variables: USERNAME: $DEPLOY_USER PASSWORD: $DEPLOY_PASSWORD ENVIRONMENT: 'staging'
With Docker Hub
script: - pipe: docker://myorg/my-pipe:1.0.0 variables: USERNAME: $USERNAME
With Private Registry
script: - pipe: docker://registry.example.com/my-pipe:1.0.0 variables: USERNAME: $USERNAME
Publishing Pipes
Docker Hub
# Build with version tag docker build -t myorg/my-pipe:1.0.0 . docker build -t myorg/my-pipe:latest . # Push to registry docker push myorg/my-pipe:1.0.0 docker push myorg/my-pipe:latest
Bitbucket Pipelines Publishing
pipelines: tags: '*': - step: name: Publish Pipe services: - docker script: - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD - docker build -t myorg/my-pipe:$BITBUCKET_TAG . - docker build -t myorg/my-pipe:latest . - docker push myorg/my-pipe:$BITBUCKET_TAG - docker push myorg/my-pipe:latest
Semantic Versioning
# Major.Minor.Patch git tag 1.0.0 git push origin 1.0.0 # Users can pin to: # - Exact: myorg/my-pipe:1.0.0 # - Minor: myorg/my-pipe:1.0 # - Major: myorg/my-pipe:1
Advanced Patterns
Multi-Stage Docker Build
# Build stage FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN go build -o /pipe # Runtime stage FROM alpine:3.19 RUN apk add --no-cache ca-certificates COPY --from=builder /pipe /pipe COPY common.sh /common.sh ENTRYPOINT ["/pipe"]
Output Variables
Pipes can set output variables for subsequent steps:
# Set output variable echo "DEPLOY_URL=https://app.example.com" >> $BITBUCKET_PIPE_STORAGE_DIR/properties.env
# Access in subsequent step - step: script: - source $BITBUCKET_PIPE_STORAGE_DIR/properties.env - echo "Deployed to $DEPLOY_URL"
Artifact Handling
# Access workspace artifacts WORKSPACE="${BITBUCKET_CLONE_DIR}" ls -la "${WORKSPACE}/dist/" # Create artifacts for next step cp "${WORKSPACE}/dist/app.zip" "${BITBUCKET_PIPE_STORAGE_DIR}/"
Error Handling
# Trap errors trap 'fail "Script failed at line $LINENO"' ERR # Retry logic retry() { local max_attempts="$1" local cmd="${@:2}" local attempt=1 until $cmd; do if ((attempt >= max_attempts)); then fail "Command failed after $max_attempts attempts" fi warning "Attempt $attempt failed, retrying..." ((attempt++)) sleep $((attempt * 2)) done } retry 3 curl -f https://api.example.com/health
Documentation
README Template
# Pipe Name [](pipeline-url) Brief description of what this pipe does. ## YAML Definition \`\`\`yaml - pipe: myorg/my-pipe:1.0.0 variables: USERNAME: '<string>' PASSWORD: '<string>' # ENVIRONMENT: '<string>' # Optional \`\`\` ## Variables | Variable | Description | Required | Default | |----------|-------------|----------|---------| | USERNAME | Auth username | Yes | - | | PASSWORD | Auth password | Yes | - | | ENVIRONMENT | Target env | No | production | ## Examples ### Basic Usage \`\`\`yaml - pipe: myorg/my-pipe:1.0.0 variables: USERNAME: $MY_USER PASSWORD: $MY_PASS \`\`\` ### Deploy to Staging \`\`\`yaml - pipe: myorg/my-pipe:1.0.0 variables: USERNAME: $MY_USER PASSWORD: $MY_PASS ENVIRONMENT: 'staging' \`\`\` ## Support Report issues at: https://bitbucket.org/myorg/my-pipe/issues
Debugging Checklist
- Verify Dockerfile builds successfully
- Check all required variables are documented in pipe.yml
- Test pipe locally with docker run
- Verify secret variables are marked as
secret: true - Check exit codes (0 for success, non-zero for failure)
- Validate output messages use proper formatting
- Test error handling with invalid inputs
- Verify image is pushed to accessible registry