Skip to main content
Early Access - The Dcycle CLI is currently available for enterprise customers. Contact us to learn more about access.

CI/CD Pipeline Integration

Integrate Dcycle uploads into your existing CI/CD pipelines for automated, reliable sustainability data management. This guide covers GitHub Actions, GitLab CI, and general best practices.

Why CI/CD for Sustainability Data?

Manual ProcessCI/CD Automated
Remember to upload monthlyRuns automatically on schedule
Human errors in data entryValidated before upload
No audit trailFull git history of changes
Single point of failureReliable, repeatable process

GitHub Actions

Basic Scheduled Upload

# .github/workflows/sustainability-upload.yml
name: Sustainability Data Upload

on:
  schedule:
    # Run every Monday at 6 AM UTC
    - cron: '0 6 * * 1'
  workflow_dispatch:  # Allow manual trigger

env:
  DCYCLE_API_KEY: ${{ secrets.DCYCLE_API_KEY }}
  DCYCLE_ORG_ID: ${{ secrets.DCYCLE_ORG_ID }}

jobs:
  upload:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install Dcycle CLI
        run: pip install dcycle-cli

      - name: Upload transport data
        run: |
          dc logistics upload data/viajes.csv --type requests --yes
          dc logistics upload data/consumos.csv --type recharges --yes

      - name: Verify upload
        run: |
          echo "Verifying recent uploads..."
          dc logistics requests list --from $(date -d "7 days ago" +%Y-%m-%d) --format json | jq length

With Data Validation

Add validation before uploading to catch errors early:
# .github/workflows/sustainability-validated.yml
name: Validated Sustainability Upload

on:
  schedule:
    - cron: '0 6 1 * *'  # Monthly on 1st at 6 AM
  pull_request:
    paths:
      - 'data/**'  # Run on PRs that modify data files

env:
  DCYCLE_API_KEY: ${{ secrets.DCYCLE_API_KEY }}
  DCYCLE_ORG_ID: ${{ secrets.DCYCLE_ORG_ID }}

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: |
          pip install dcycle-cli pandas

      - name: Validate CSV structure
        run: |
          python scripts/validate_data.py data/viajes.csv --type requests
          python scripts/validate_data.py data/consumos.csv --type recharges

      - name: Check for required columns
        run: |
          # Verify viajes.csv has required columns
          head -1 data/viajes.csv | grep -q "date" || exit 1
          head -1 data/viajes.csv | grep -q "vehicle_plate" || exit 1
          head -1 data/viajes.csv | grep -q "origin" || exit 1
          head -1 data/viajes.csv | grep -q "destination" || exit 1
          echo "✓ All required columns present"

  upload:
    needs: validate
    if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install Dcycle CLI
        run: pip install dcycle-cli

      - name: Upload data
        run: |
          dc logistics upload data/viajes.csv --type requests --yes
          dc logistics upload data/consumos.csv --type recharges --yes

      - name: Create upload summary
        run: |
          echo "## Upload Summary" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "- **Date:** $(date)" >> $GITHUB_STEP_SUMMARY
          echo "- **Requests uploaded:** $(wc -l < data/viajes.csv)" >> $GITHUB_STEP_SUMMARY
          echo "- **Recharges uploaded:** $(wc -l < data/consumos.csv)" >> $GITHUB_STEP_SUMMARY

With Slack Notifications

Get notified on success or failure:
# .github/workflows/sustainability-notified.yml
name: Sustainability Upload with Notifications

on:
  schedule:
    - cron: '0 6 * * 1'

env:
  DCYCLE_API_KEY: ${{ secrets.DCYCLE_API_KEY }}
  DCYCLE_ORG_ID: ${{ secrets.DCYCLE_ORG_ID }}

jobs:
  upload:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Install Dcycle CLI
        run: pip install dcycle-cli

      - name: Upload data
        id: upload
        run: |
          dc logistics upload data/viajes.csv --type requests --yes
          dc logistics upload data/consumos.csv --type recharges --yes

          # Capture counts for notification
          REQUESTS=$(wc -l < data/viajes.csv)
          RECHARGES=$(wc -l < data/consumos.csv)
          echo "requests=$REQUESTS" >> $GITHUB_OUTPUT
          echo "recharges=$RECHARGES" >> $GITHUB_OUTPUT

      - name: Notify success
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "✅ *Sustainability data uploaded successfully*"
                  }
                },
                {
                  "type": "section",
                  "fields": [
                    {"type": "mrkdwn", "text": "*Requests:* ${{ steps.upload.outputs.requests }}"},
                    {"type": "mrkdwn", "text": "*Recharges:* ${{ steps.upload.outputs.recharges }}"}
                  ]
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

      - name: Notify failure
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "❌ Sustainability data upload failed! Check: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

GitLab CI

# .gitlab-ci.yml
stages:
  - validate
  - upload
  - notify

variables:
  DCYCLE_API_KEY: $DCYCLE_API_KEY
  DCYCLE_ORG_ID: $DCYCLE_ORG_ID

validate_data:
  stage: validate
  image: python:3.11
  script:
    - pip install pandas
    - python scripts/validate_data.py data/viajes.csv
    - python scripts/validate_data.py data/consumos.csv
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
    - if: $CI_PIPELINE_SOURCE == "web"
    - changes:
        - data/**

upload_data:
  stage: upload
  image: python:3.11
  needs: [validate_data]
  script:
    - pip install dcycle-cli
    - dc logistics upload data/viajes.csv --type requests --yes
    - dc logistics upload data/consumos.csv --type recharges --yes
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
    - if: $CI_PIPELINE_SOURCE == "web"

notify_success:
  stage: notify
  needs: [upload_data]
  script:
    - |
      curl -X POST "$SLACK_WEBHOOK" \
        -H 'Content-Type: application/json' \
        -d '{"text": "✅ Sustainability data uploaded successfully"}'
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
      when: on_success

notify_failure:
  stage: notify
  needs: [upload_data]
  script:
    - |
      curl -X POST "$SLACK_WEBHOOK" \
        -H 'Content-Type: application/json' \
        -d '{"text": "❌ Sustainability upload failed: '$CI_PIPELINE_URL'"}'
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
      when: on_failure

Validation Script

A reusable Python script for data validation:
# scripts/validate_data.py
import argparse
import sys
import pandas as pd
from datetime import datetime

SCHEMAS = {
    'requests': {
        'required': ['date', 'vehicle_plate', 'origin', 'destination'],
        'optional': ['distance_km', 'weight_kg', 'toc', 'client'],
        'date_columns': ['date'],
    },
    'recharges': {
        'required': ['date', 'vehicle_plate', 'fuel_type', 'quantity'],
        'optional': ['odometer', 'station'],
        'date_columns': ['date'],
    }
}

def validate_csv(filepath: str, data_type: str) -> list[str]:
    """Validate CSV file against schema"""
    errors = []
    schema = SCHEMAS.get(data_type)

    if not schema:
        return [f"Unknown data type: {data_type}"]

    try:
        df = pd.read_csv(filepath)
    except Exception as e:
        return [f"Failed to read CSV: {e}"]

    # Check required columns
    missing = set(schema['required']) - set(df.columns)
    if missing:
        errors.append(f"Missing required columns: {missing}")

    # Check for empty required fields
    for col in schema['required']:
        if col in df.columns:
            empty_count = df[col].isna().sum()
            if empty_count > 0:
                errors.append(f"Column '{col}' has {empty_count} empty values")

    # Validate date formats
    for col in schema.get('date_columns', []):
        if col in df.columns:
            for idx, val in df[col].items():
                try:
                    datetime.strptime(str(val), '%Y-%m-%d')
                except ValueError:
                    errors.append(f"Row {idx + 2}: Invalid date format '{val}' in {col}")
                    if len(errors) > 10:
                        errors.append("... (truncated)")
                        return errors

    # Check for duplicates
    if df.duplicated().any():
        dup_count = df.duplicated().sum()
        errors.append(f"Found {dup_count} duplicate rows")

    return errors

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('filepath', help='CSV file to validate')
    parser.add_argument('--type', required=True, choices=['requests', 'recharges'])
    args = parser.parse_args()

    errors = validate_csv(args.filepath, args.type)

    if errors:
        print(f"❌ Validation failed for {args.filepath}:")
        for error in errors:
            print(f"  - {error}")
        sys.exit(1)
    else:
        print(f"✓ {args.filepath} is valid")
        sys.exit(0)

Setting Up Secrets

GitHub Actions

  1. Go to Repository → Settings → Secrets and variables → Actions
  2. Add these secrets:
    • DCYCLE_API_KEY: Your Dcycle API key
    • DCYCLE_ORG_ID: Your organization ID
    • SLACK_WEBHOOK (optional): For notifications

GitLab CI

  1. Go to Settings → CI/CD → Variables
  2. Add the same variables (mark as “Masked” for security)

Best Practices

Use --yes Flag

Always use --yes in CI/CD to skip interactive prompts that would hang the pipeline.

Validate First

Run validation in a separate job before upload. Fail fast on bad data.

Idempotent Uploads

Design pipelines to be safely re-runnable. Handle duplicates gracefully.

Monitor & Alert

Set up notifications for failures. Don’t let broken pipelines go unnoticed.

Next Steps