Kubesphere Devops Tenant logo

Kubesphere Devops Tenant

OrganizationPopular
kubesphere
kubesphere-devops-tenant

Use when operating KubeSphere DevOps as a namespace-scoped tenant with limited permissions, without cluster-admin access, or when accessing DevOps through KubeSphere APIs only

Overview

Publisherkubesphere
Repositorykubesphere
Skill namekubesphere-devops-tenant
Stars
17.1K
Forks
2.8K
Bundled files
Instructions only
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by kubesphere on GitHub. Read the source before you install it.

Installation

Install the Kubesphere Devops Tenant AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/kubesphere/kubesphere.git /tmp/kubesphere
mkdir -p .claude/skills
cp -r /tmp/kubesphere/skills/kubesphere-devops-tenant .claude/skills/kubesphere-devops-tenant
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Kubesphere Devops Tenant in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Kubesphere Devops Tenant on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Kubesphere Devops Tenant is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

KubeSphere DevOps Tenant Operations

Overview

This guide covers DevOps operations for namespace-scoped tenants who:

  • Have admin/operator permissions within their DevOpsProject namespace(s)
  • Cannot access kubesphere-devops-system (Jenkins secrets, tokens)
  • Cannot call Jenkins APIs directly
  • Must use KubeSphere APIs (/kapis/devops.kubesphere.io/) for all operations
  • Use KubeSphere authentication (OAuth tokens), not Jenkins tokens

Critical Distinction: DevOps projects are namespaces, not DevOpsProject CRs. To list accessible DevOps projects:

bash
# Correct - lists namespaces (DevOps projects) tenant can access
GET /clusters/{cluster}/kapis/devops.kubesphere.io/v1alpha3/workspaces/{workspace}/namespaces

# Wrong - requires cluster-admin, returns 403 for tenants
GET /clusters/{cluster}/apis/devops.kubesphere.io/v1alpha3/devopsprojects

When to Use

  • Operating as a project admin/operator (not cluster admin)
  • Working within tenant namespace boundaries
  • No access to Jenkins secrets in kubesphere-devops-system
  • Need to trigger pipelines via KubeSphere API
  • Building automation for namespace-scoped users
  • Developing tenant-facing tooling

Tenant vs Admin Permissions

CapabilityTenant (Namespace)Admin (Cluster)
Access DevOpsProject✅ Own namespace(s)✅ All namespaces
Create/Edit Pipelines✅ In own namespace✅ Any namespace
View PipelineRuns✅ In own namespace✅ Any namespace
Access Jenkins Secret❌ Nokubesphere-devops-system
Direct Jenkins API❌ No✅ Full access
View Jenkins Console❌ No✅ Via NodePort
KubeSphere API/kapis//kapis/

Authentication

Tenants authenticate via KubeSphere's OAuth, not Jenkins. See kubesphere-core for complete OAuth authentication details.

Quick Reference

bash
# Exchange credentials for OAuth token (see core skill for details)
export KUBESPHERE_API="https://kubesphere-api.example.com"
export USERNAME="tenant-user"
export PASSWORD="tenant-password"

# Get token
export API_TOKEN=$(curl -s -X POST "${KUBESPHERE_API}/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password&username=${USERNAME}&password=${PASSWORD}&client_id=kubesphere&client_secret=kubesphere" \
  | jq -r '.access_token')

# Use token
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha3/namespaces/demo-project/pipelines" \
  -H "Authorization: Bearer ${API_TOKEN}"

Key Points:

  • OAuth token expires in 7200 seconds (2 hours)
  • Use client_id=kubesphere and client_secret=kubesphere
  • Token contains user's RBAC permissions

See kubesphere-core for complete OAuth authentication details including token refresh and common use cases.

Get KubeSphere API Token

Tenants authenticate via KubeSphere's OAuth, not Jenkins:

bash
# Method 1: Using kubeconfig (if configured)
kubectl config view --raw -o jsonpath='{.users[?(@.name=="current-user")].user.token}'

# Method 2: Via KubeSphere OAuth API (Recommended)
export KUBESPHERE_URL="https://kubesphere-api.example.com"
export USERNAME="tenant-user"
export PASSWORD="tenant-password"

# Exchange credentials for token
TOKEN_RESPONSE=$(curl -s -X POST "${KUBESPHERE_URL}/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=password" \
  --data-urlencode "username=${USERNAME}" \
  --data-urlencode "password=${PASSWORD}" \
  --data-urlencode "client_id=kubesphere" \
  --data-urlencode "client_secret=kubesphere")

# Extract access token
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')

# Token expires in 7200 seconds (2 hours)
echo "Token obtained: ${ACCESS_TOKEN:0:50}..."

Using Token with API

bash
export API_TOKEN="<your-kubesphere-token>"
export DEVOPS_PROJECT="demo-project"
export KUBESPHERE_API="https://kubesphere-api.example.com"

# Verify access
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines" \
  -H "Authorization: Bearer ${API_TOKEN}"

Complete Working Example

Here's a verified workflow using tenant credentials (stoneshi / P@88w0rd):

Step 1: Authenticate

bash
export KUBESPHERE_API="http://kubesphere-apiserver.kubesphere-system.svc:80"
export USERNAME="stoneshi"
export PASSWORD='P@88w0rd'

# Get OAuth token
TOKEN_RESPONSE=$(curl -s -X POST "${KUBESPHERE_API}/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "username=${USERNAME}" \
  -d "password=${PASSWORD}" \
  -d "client_id=kubesphere" \
  -d "client_secret=kubesphere")

export API_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')
echo "Authenticated as: $(curl -s ${KUBESPHERE_API}/kapis/iam.kubesphere.io/v1beta1/users/stoneshi -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.metadata.name')"

Step 2: Access Workspace Resources

bash
# Verify workspace access (returns "stone")
curl -s "${KUBESPHERE_API}/kapis/tenant.kubesphere.io/v1beta1/workspaces/stone" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.metadata.name'

# Try accessing other workspace (returns 403 Forbidden - correct tenant isolation)
curl -s "${KUBESPHERE_API}/kapis/tenant.kubesphere.io/v1beta1/workspaces/demo" \
  -H "Authorization: Bearer ${API_TOKEN}"
# Output: {"message":"workspaces.tenant.kubesphere.io \"demo\" is forbidden..."}

Step 3: Create and List Pipelines

bash
export DEVOPS_PROJECT="stone-devops"  # Must be in "stone" workspace

# List pipelines in tenant namespace
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha3/namespaces/${DEVOPS_PROJECT}/pipelines" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.items[] | "✓ " + .metadata.name'

# Create pipeline via kubectl (as tenant with namespace permissions)
cat <<EOF | kubectl apply -f -
apiVersion: devops.kubesphere.io/v1alpha3
kind: Pipeline
metadata:
  name: stone-tenant-pipeline
  namespace: stone-devops
spec:
  type: pipeline
  pipeline:
    name: stone-tenant-pipeline
    description: "Test pipeline for tenant verification"
    jenkinsfile: |
      pipeline {
        agent { label "base" }
        stages {
          stage("Test") {
            steps {
              sh "echo 'Hello from tenant pipeline'"
            }
          }
        }
      }
EOF

# Verify via API
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha3/namespaces/${DEVOPS_PROJECT}/pipelines/stone-tenant-pipeline" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '{name: .metadata.name, type: .spec.type}'

Step 4: Trigger and Monitor Run

bash
export PIPELINE_NAME="stone-tenant-pipeline"

# Trigger run
curl -s -X POST "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{}' | jq -r '{runId: .id, state: .state}'

# List runs (Blue Ocean format)
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq '.items[] | {id: .id, state: .state, result: .result}'

# Check specific run status
export RUN_ID="1"
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '{state: .state, result: .result, duration: .durationInMillis}'
# Output: {"state":"FINISHED","result":"SUCCESS","duration":15110}

Step 5: Get Logs

bash
# Get console log (tenant accessible, no Jenkins token needed)
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}/log" \
  -H "Authorization: Bearer ${API_TOKEN}" | tail -20

# Expected output includes:
# + echo Hello from tenant pipeline
# Hello from tenant pipeline
# Finished: SUCCESS

Key Findings

AspectTenant Behavior
AuthenticationOAuth with client_id/client_secret = "kubesphere"
Token Expiry7200 seconds (2 hours)
API Versionv1alpha3 for pipelines, v1alpha2 for runs
Response FormatBlue Ocean JSON (not Kubernetes resources)
Status Fields.state (QUEUED/RUNNING/FINISHED), .result (SUCCESS/FAILURE)
Namespace Isolation403 Forbidden for other workspaces
Logs Access✅ Available via KubeSphere API
Artifacts✅ Available via /artifacts endpoint

Pipeline Operations

List Pipelines (Tenant View)

bash
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/search?q=type:pipeline" \
  -H "Authorization: Bearer ${API_TOKEN}"

# Or list in specific namespace
# Via API (v1alpha3 for pipelines)
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha3/namespaces/${DEVOPS_PROJECT}/pipelines" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.items[].metadata.name'
  -H "Authorization: Bearer ${API_TOKEN}" | jq '.items[].metadata.name'

Get Pipeline Details

bash
export PIPELINE_NAME="my-pipeline"

curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha3/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq .

Create Pipeline (Tenant)

Regular Pipeline:

bash
curl -s -X POST "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "devops.kubesphere.io/v1alpha3",
    "kind": "Pipeline",
    "metadata": {
      "name": "my-tenant-pipeline",
      "namespace": "'${DEVOPS_PROJECT}'"
    },
    "spec": {
      "type": "pipeline",
      "pipeline": {
        "name": "my-tenant-pipeline",
        "description": "Pipeline created by tenant",
        "jenkinsfile": "pipeline {\n  agent { label \"base\" }\n  stages {\n    stage(\"Build\") {\n      steps {\n        sh \"echo Building...\"\n      }\n    }\n  }\n}"
      }
    }
  }'

Multi-Branch Pipeline:

bash
curl -s -X POST "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "devops.kubesphere.io/v1alpha3",
    "kind": "Pipeline",
    "metadata": {
      "name": "my-multibranch-pipeline",
      "namespace": "'${DEVOPS_PROJECT}'"
    },
    "spec": {
      "type": "multi-branch-pipeline",
      "multi_branch_pipeline": {
        "name": "my-multibranch-pipeline",
        "description": "Multi-branch pipeline from tenant",
        "source_type": "git",
        "git_source": {
          "url": "https://github.com/example/repo",
          "credential_id": "my-git-credential",
          "discover_branches": true,
          "discover_tags": false
        },
        "script_path": "Jenkinsfile"
      }
    }
  }'

Create Multi-Branch Pipeline from Private Repository

⚠️ CRITICAL: Always Check Repository Type First

Before creating any multi-branch pipeline, you MUST ask the user:

"Is this a private repository?"

If YES (Private Repo):

  1. Ask if they want to use an existing credential or create a new one
  2. Create a DevOps credential (basic-auth type with GitHub PAT) - see Step 1 below
  3. Reference the credential in git_source.credential_id when creating the pipeline

If NO (Public Repo):

  • Set credential_id: "" (empty string)

Never assume repository type - always confirm with the user first. Never use GITHUB_ env vars directly in pipeline specs.

Complete workflow for private GitHub repository:

Prerequisites:

  • GitHub Personal Access Token (PAT) with repo access
  • DevOps project namespace (e.g., devopstestc2nj7)

Step 1: Create Credential for GitHub Access

bash
export GITHUB_PAT="ghp_xxxxxxxxxxxxxxxxxxxx"
export TENANT_NAME="stone-ns-admin"

curl -s -X POST "${KUBESPHERE_API}/clusters/member-1/kapis/devops.kubesphere.io/v1alpha3/namespaces/devopstestc2nj7/credentials" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "v1",
    "kind": "Secret",
    "metadata": {
      "name": "github-token",
      "namespace": "devopstestc2nj7",
      "annotations": {
        "credential.devops.kubesphere.io/type": "basic-auth"
      }
    },
    "stringData": {
      "username": "git",
      "password": "'${GITHUB_PAT}'"
    },
    "type": "credential.devops.kubesphere.io/basic-auth"
  }'

Step 2: Create GitRepository

bash
curl -s -X POST "${KUBESPHERE_API}/clusters/member-1/kapis/devops.kubesphere.io/v1alpha3/namespaces/devopstestc2nj7/gitrepositories" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "devops.kubesphere.io/v1alpha3",
    "kind": "GitRepository",
    "metadata": {
      "name": "my-private-repo",
      "namespace": "devopstestc2nj7"
    },
    "spec": {
      "url": "https://github.com/stoneshi-yunify/jenkinsfiles.git",
      "provider": "github",
      "secret": {
        "name": "github-token",
        "namespace": "devopstestc2nj7"
      },
      "description": "Private repository with Jenkinsfile"
    }
  }'

Step 3: Create Multi-Branch Pipeline

bash
curl -s -X POST "${KUBESPHERE_API}/clusters/member-1/kapis/devops.kubesphere.io/v1alpha3/namespaces/devopstestc2nj7/pipelines" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "devops.kubesphere.io/v1alpha3",
    "kind": "Pipeline",
    "metadata": {
      "name": "echo-pipeline",
      "namespace": "devopstestc2nj7",
      "annotations": {
        "kubesphere.io/creator": "'${TENANT_NAME}'"
      }
    },
    "spec": {
      "type": "multi-branch-pipeline",
      "multi_branch_pipeline": {
        "name": "echo-pipeline",
        "description": "Multi-branch pipeline from private repo",
        "source_type": "git",
        "git_source": {
          "url": "https://github.com/stoneshi-yunify/jenkinsfiles.git",
          "credential_id": "github-token",
          "discover_branches": true,
          "discover_tags": false
        },
        "script_path": "echo/Jenkinsfile"
      }
    }
  }'

Key Points:

  • GitRepository requires spec.provider (e.g., github) and spec.secret fields
  • Pipeline MUST have kubesphere.io/creator annotation when created by tenant
  • Multi-branch pipelines auto-discover branches from the repository

Pipeline Runs (The Tenant Way)

⚠️ API Version Notice: The /kapis/devops.kubesphere.io/v1alpha2/ APIs are deprecated. Always prefer v1alpha3 APIs when available.

Trigger a Pipeline Run (Multi-Branch)

For Multi-Branch Pipelines - Three-Step Procedure:

Step 1: List Available Branches

bash
curl -s "${KUBESPHERE_API}/clusters/${CLUSTER}/kapis/devops.kubesphere.io/v1alpha3/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/branches?filter=origin&page=1&limit=10" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" | jq -r '.items[] | "- Branch: \(.name) | Latest: \(.latestRun.id // "N/A") | Status: \(.latestRun.result // "N/A")"'

Step 2: Ask User Which Branch

"Which branch would you like to build?"

Step 3: Trigger Build with Branch Parameter

bash
export BRANCH="main"  # User's selection

curl -s -X POST "${KUBESPHERE_API}/clusters/${CLUSTER}/kapis/devops.kubesphere.io/v1alpha3/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/pipelineruns?branch=${BRANCH}" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"parameters":[]}' | jq -r '.metadata.name'

Key Points:

  • Use v1alpha3 endpoint with ?branch=${BRANCH} query parameter
  • Returns Kubernetes PipelineRun resource (not Blue Ocean format)
  • For multi-branch pipelines, the branch parameter is required

Trigger Repository Scanning (Multi-Branch)

Exception to v1alpha3 rule: Repository scanning uses v1alpha2 API. This endpoint is not available in v1alpha3.

When to use:

  • Force immediate repository re-scan to discover new branches
  • Troubleshoot branch detection issues
  • Manually trigger branch indexing after credential changes

Step 1: Trigger Scan (v1alpha2)

bash
curl -X POST "${KUBESPHERE_API}/clusters/${CLUSTER}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/scan" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{}'

Step 2: Fetch Scanning Log (v1alpha2)

bash
curl -s "${KUBESPHERE_API}/clusters/${CLUSTER}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/consolelog" \
  -H "Authorization: Bearer ${API_TOKEN}"

Example scanning output:

Started by user stone-ns-admin
Starting branch indexing...
 > git ls-remote --symref -- https://github.com/org/repo.git
Fetching & pruning origin...
Checking branches:
  Checking branch main ✓
      'Jenkinsfile' found
    Met criteria
  Checking branch feature-branch
      'Jenkinsfile' found
    Met criteria
  Checking branch old-branch
      'Jenkinsfile' not found
    Does not meet criteria
Processed 3 branches
Finished branch indexing. Indexing took 3 sec
Finished: SUCCESS

Via PipelineRun CR (kubectl) - Alternative:

bash
cat <<EOF | kubectl apply -f -
apiVersion: devops.kubesphere.io/v1alpha3
kind: PipelineRun
metadata:
  name: my-run-$(date +%s)
  namespace: ${DEVOPS_PROJECT}
spec:
  pipelineRef:
    name: ${PIPELINE_NAME}
  scm:
    refName: "main"    # Branch name for multi-branch pipelines
    refType: "branch"
EOF

List Pipeline Runs

bash
# Via kubectl (preferred - returns Kubernetes PipelineRun resources)
kubectl get pipelineruns -n ${DEVOPS_PROJECT} --sort-by=.metadata.creationTimestamp

# Via API (v1alpha3 - returns Kubernetes resources)
curl -s "${KUBESPHERE_API}/clusters/${CLUSTER}/kapis/devops.kubesphere.io/v1alpha3/namespaces/${DEVOPS_PROJECT}/pipelineruns?labelSelector=devops.kubesphere.io/pipeline=${PIPELINE_NAME}" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq '.items[] | {name: .metadata.name, phase: .status.phase, creationTime: .metadata.creationTimestamp}'

Get Run Status

bash
# Via kubectl (preferred)
kubectl get pipelinerun ${RUN_NAME} -n ${DEVOPS_PROJECT} -o jsonpath='{.status.phase}'

# Via API (v1alpha3)
curl -s "${KUBESPHERE_API}/clusters/${CLUSTER}/kapis/devops.kubesphere.io/v1alpha3/namespaces/${DEVOPS_PROJECT}/pipelineruns/${RUN_NAME}" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '{name: .metadata.name, phase: .status.phase, startTime: .status.startTime, completionTime: .status.completionTime}'

Deprecated v1alpha2 APIs

⚠️ Deprecated: These v1alpha2 endpoints return Blue Ocean format and are deprecated. Use v1alpha3 APIs shown above.

bash
# List runs (v1alpha2 - deprecated)
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq '.items[] | {id: .id, state: .state, result: .result}'

# Get run status (v1alpha2 - deprecated)
export RUN_ID="1"
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq '{id: .id, state: .state, result: .result, duration: .durationInMillis}'
bash
# Get concise status (Blue Ocean format fields)
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '{id: .id, state: .state, result: .result, startTime: .startTime, duration: .durationInMillis}'

# Example output:
# {
#   "id": "1",
#   "state": "FINISHED",
#   "result": "SUCCESS",
#   "startTime": "2026-03-19T02:50:12.747+0000",
#   "duration": 15110
# }

# Watch for completion
while true; do
  STATUS=$(curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}" \
    -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.state')
  echo "State: $STATUS"
  [[ "$STATUS" == "FINISHED" ]] && break
  sleep 5
done
bash
# Get concise status
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '{state: .status.phase, result: .status.conditions[0].reason, startTime: .status.startTime, completionTime: .status.completionTime}'

# Watch for completion
while true; do
  STATUS=$(curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}" \
    -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.status.phase')
  echo "Status: $STATUS"
  [[ "$STATUS" == "Succeeded" || "$STATUS" == "Failed" ]] && break
  sleep 5
done

Logs and Artifacts (Tenant Access)

Get Console Log

Tenant Method (via KubeSphere API):

bash
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}/log" \
  -H "Authorization: Bearer ${API_TOKEN}"

# Or with kubectl
kubectl get pipelinerun ${RUN_ID} -n ${DEVOPS_PROJECT} -o jsonpath='{.status.log}' 2>/dev/null || echo "Logs via API only"

Note: Console logs may not be available immediately. Poll until ready:

bash
while ! curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}/log" \
  -H "Authorization: Bearer ${API_TOKEN}" | grep -q "Finished:"; do
  echo "Waiting for logs..."
  sleep 5
done
echo "Logs ready!"

List Artifacts

bash
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}/artifacts" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq '.[] | {name: .name, path: .path, size: .size}'

Download Artifacts

Download via KubeSphere API:

bash
export ARTIFACT_NAME="service"
export ARTIFACT_PATH="service"

# Get artifact download URL
ARTIFACT_URL=$(curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}/artifacts" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r ".[] | select(.name==\"${ARTIFACT_NAME}\") | .url")

# Download artifact
curl -s "${KUBESPHERE_API}${ARTIFACT_URL}" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -o "/tmp/${ARTIFACT_NAME}"

# Verify
ls -lh "/tmp/${ARTIFACT_NAME}"
file "/tmp/${ARTIFACT_NAME}"

Alternative: Via kubectl with exec (if artifact is in workspace):

bash
# Find the agent pod (if still running)
AGENT_POD=$(kubectl get pods -n kubesphere-devops-worker -l jenkins/label-digest -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)

# Copy artifact (if pod exists)
if [ -n "$AGENT_POD" ]; then
  kubectl cp ${AGENT_POD}:/home/jenkins/agent/workspace/${PIPELINE_NAME}/${ARTIFACT_NAME} /tmp/${ARTIFACT_NAME}
fi

Managing Credentials

List Credentials

bash
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/credentials" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq '.items[].metadata.name'

Create Credential

SSH Key:

bash
curl -s -X POST "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/credentials" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "v1",
    "kind": "Secret",
    "metadata": {
      "name": "my-ssh-key",
      "namespace": "'${DEVOPS_PROJECT}'",
      "annotations": {
        "kubesphere.io/creator": "tenant-user",
        "kubesphere.io/description": "SSH key for Git"
      }
    },
    "type": "credential.devops.kubesphere.io/ssh",
    "stringData": {
      "username": "git",
      "privateKey": "'$(cat ~/.ssh/id_rsa | sed 's/$/\\n/g' | tr -d '\n')'"
    }
  }'

Username/Password:

bash
curl -s -X POST "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/credentials" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "v1",
    "kind": "Secret",
    "metadata": {
      "name": "my-basic-auth",
      "namespace": "'${DEVOPS_PROJECT}'"
    },
    "type": "credential.devops.kubesphere.io/basic-auth",
    "stringData": {
      "username": "myuser",
      "password": "mypassword"
    }
  }'

Multi-Cluster Operations

KubeSphere supports managing DevOps resources across multiple clusters. Use the /clusters/{cluster-name}/ prefix to forward API requests to specific member clusters.

DevOps Projects are Namespaces

Important: In KubeSphere DevOps, a "DevOps project" is actually a Kubernetes namespace with the devops.kubesphere.io/managed=true label. The DevOpsProject CR is a wrapper resource, but when listing accessible DevOps projects for a tenant, you query namespaces, not DevOpsProject CRs.

Correct API for listing tenant-accessible DevOps projects:

bash
# List DevOps project namespaces (NOT devopsprojects CRs)
GET /clusters/{cluster}/kapis/devops.kubesphere.io/v1alpha3/workspaces/{workspace}/namespaces

This endpoint returns namespaces that:

  1. Have the devops.kubesphere.io/managed=true label
  2. Have the kubesphere.io/workspace={workspace} label
  3. Are accessible to the authenticated tenant

DevOps Project Naming Convention

When users refer to DevOps projects, they may use either a shortname or fullname:

Name TypeExampleSourceDescription
ShortnamedevopstestDevOpsProject CR .metadata.generateNameUser-friendly display name
Fullnamedevopstestc2nj7DevOpsProject CR .metadata.nameActual namespace name

Key Points:

  • The fullname is the actual Kubernetes namespace name that you use in API calls
  • The fullname = DevOpsProject CR's .metadata.name = Namespace's .metadata.name
  • The shortname comes from .metadata.generateName and is used for display purposes

Resolving Ambiguity: When a user provides a name that could match multiple projects:

bash
# First, get all accessible DevOps project namespaces
NAMESPACES=$(curl -s "${KUBESPHERE_API}/clusters/member-1/kapis/devops.kubesphere.io/v1alpha3/workspaces/stone2/namespaces" \
  -H "Authorization: Bearer ${API_TOKEN}")

# Example: User says "devopstest" which could match:
# - Fullname: devopstestc2nj7 (from generateName "devopstest")
# - Fullname: devopstestxyz12 (from generateName "devopstest")
# - Fullname: my-devopstest (different project)

# Check for matches
echo "$NAMESPACES" | jq -r '.items[].metadata.name' | grep "devopstest"
# Output might show:
# devopstestc2nj7
# devopstestxyz12

# If multiple matches found, ask user to confirm:
# "Multiple DevOps projects match 'devopstest':
#  1. devopstestc2nj7
#  2. devopstestxyz12
#  Which one do you want to use?"

Best Practice:

  1. When user provides a name, check if it matches any fullname (namespace name) exactly
  2. If exact match found → use that namespace
  3. If no exact match → check if it matches any shortname (generateName prefix)
  4. If multiple matches → ask user to confirm before proceeding

API Path Patterns

Endpoint TypePath PatternReturnsUse CaseTenant Access
KubeSphere API (workspace-scoped)/clusters/{cluster}/kapis/devops.kubesphere.io/v1alpha3/workspaces/{workspace}/namespacesNamespaces (DevOps projects)List DevOps project namespaces tenant can accessTenant accessible
KubeSphere API (namespace-scoped)/clusters/{cluster}/kapis/devops.kubesphere.io/v1alpha3/namespaces/{namespace}/pipelinesPipelinesPipeline operationsTenant accessible
Kubernetes API (cluster-scoped)/clusters/{cluster}/apis/devops.kubesphere.io/v1alpha3/devopsprojectsDevOpsProject CRsDirect CR accessAdmin only (403)

Key Insight: The /kapis/ endpoints enforce workspace-level RBAC and work for tenants. The /apis/ endpoints require cluster-scoped permissions and will return 403 for tenants. When listing DevOps projects a tenant can access, use the /namespaces endpoint, not /devopsprojects.

List DevOpsProjects Across Clusters

bash
export KUBESPHERE_API="http://kubesphere-apiserver:80"
export USERNAME="stone-ns-admin"
export PASSWORD="P@88w0rd"
export WORKSPACE="stone"

# Get OAuth token
TOKEN=$(curl -s -X POST -H 'Content-Type: application/x-www-form-urlencoded' \
  "${KUBESPHERE_API}/oauth/token" \
  --data-urlencode 'grant_type=password' \
  --data-urlencode "username=${USERNAME}" \
  --data-urlencode "password=${PASSWORD}" \
  --data-urlencode 'client_id=kubesphere' \
  --data-urlencode 'client_secret=kubesphere' | jq -r '.access_token')

# List DevOpsProjects on Host Cluster
echo "=== Host Cluster ==="
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha3/workspaces/${WORKSPACE}/namespaces" \
  -H "Authorization: Bearer ${TOKEN}" | jq -r '.items[] | "\(.metadata.name) (\(.metadata.creationTimestamp))"'

# List DevOpsProjects on Member-1 Cluster
echo "=== Member-1 Cluster ==="
curl -s "${KUBESPHERE_API}/clusters/member-1/kapis/devops.kubesphere.io/v1alpha3/workspaces/${WORKSPACE}/namespaces" \
  -H "Authorization: Bearer ${TOKEN}" | jq -r '.items[] | "\(.metadata.name) (\(.metadata.creationTimestamp))"'

Example Output:

=== Host Cluster ===
(No output - no DevOpsProjects in workspace 'stone' on host)

=== Member-1 Cluster ===
stonedev154cht (2026-03-18T06:46:04Z)

Why Tenant Can't Use /apis/ Endpoints

bash
# ❌ This will return 403 Forbidden for tenants
curl -s "${KUBESPHERE_API}/clusters/member-1/apis/devops.kubesphere.io/v1alpha3/devopsprojects" \
  -H "Authorization: Bearer ${TOKEN}"

# Output:
# {
#   "kind": "Status",
#   "apiVersion": "v1",
#   "status": "Failure",
#   "message": "devopsprojects.devops.kubesphere.io is forbidden: User \"stone-ns-admin\" cannot list resource \"devopsprojects\" in API group \"devops.kubesphere.io\" at the cluster scope",
#   "reason": "Forbidden",
#   "code": 403
# }

# ✅ This works because /kapis/ with workspace scope enforces tenant RBAC
curl -s "${KUBESPHERE_API}/clusters/member-1/kapis/devops.kubesphere.io/v1alpha3/workspaces/stone/namespaces" \
  -H "Authorization: Bearer ${TOKEN}"

# Output:
# {"items":[{"kind":"DevOpsProject","apiVersion":"devops.kubesphere.io/v1alpha3",...}]}

Complete Multi-Cluster Query Script

bash
#!/bin/bash

export KUBESPHERE_API="http://kubesphere-apiserver:80"
export USERNAME="stone-ns-admin"
export PASSWORD="P@88w0rd"
export WORKSPACE="stone"

# Get token
TOKEN=$(curl -s -X POST -H 'Content-Type: application/x-www-form-urlencoded' \
  "${KUBESPHERE_API}/oauth/token" \
  --data-urlencode 'grant_type=password' \
  --data-urlencode "username=${USERNAME}" \
  --data-urlencode "password=${PASSWORD}" \
  --data-urlencode 'client_id=kubesphere' \
  --data-urlencode 'client_secret=kubesphere' | jq -r '.access_token')

# Get list of clusters (requires admin token or cluster list permission)
# For tenants, typically hardcode the clusters they have access to
CLUSTERS=("host" "member-1")

echo "=== DevOpsProjects in Workspace '${WORKSPACE}' Across All Clusters ==="
for CLUSTER in "${CLUSTERS[@]}"; do
  echo -e "\n## Cluster: ${CLUSTER}"
  
  # Use /kapis/ endpoint with workspace scope
  ENDPOINT="${KUBESPHERE_API}"
  if [ "${CLUSTER}" != "host" ]; then
    ENDPOINT="${ENDPOINT}/clusters/${CLUSTER}"
  fi
  
  PROJECTS=$(curl -s "${ENDPOINT}/kapis/devops.kubesphere.io/v1alpha3/workspaces/${WORKSPACE}/namespaces" \
    -H "Authorization: Bearer ${TOKEN}")
  
  # Check if response contains items
  COUNT=$(echo "$PROJECTS" | jq '.items | length')
  
  if [ "$COUNT" -gt 0 ]; then
    echo "$PROJECTS" | jq -r '.items[] | "  - \(.metadata.name) (Created: \(.metadata.creationTimestamp), Status: \(.metadata.annotations."devopsproject.devops.kubesphere.io/syncstatus" // "N/A"))"'
  else
    echo "  No DevOpsProjects found"
  fi
done

Workspace-Scoped API Operations

Query DevOps Projects (Namespaces) by Workspace

Tenants can query DevOps projects (which are namespaces) within their authorized workspaces:

bash
# List DevOps project namespaces in specific workspace
# Note: Returns namespaces with devops.kubesphere.io/managed=true label
# Returns empty if tenant doesn't have workspace access
curl -s "${KUBESPHERE_API}/clusters/host/kapis/devops.kubesphere.io/v1alpha3/workspaces/stone/namespaces?sortBy=createTime&limit=10" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.items[].metadata.name'

# Example output:
# stone-devops

# Query different workspace (returns 0 items if no access)
curl -s "${KUBESPHERE_API}/clusters/host/kapis/devops.kubesphere.io/v1alpha3/workspaces/demo/namespaces?sortBy=createTime&limit=10" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq '.totalItems'

# Example output:
# 0

Important Distinction:

  • DevOps Project = A namespace with devops.kubesphere.io/managed=true label
  • DevOpsProject CR = A Kubernetes custom resource that wraps the namespace
  • To list projects a tenant can access → Use /namespaces endpoint
  • The /namespaces endpoint filters by namespace label kubesphere.io/workspace. If the namespace label doesn't match the workspace, it won't be returned even if the DevOpsProject CR has the correct label.

Verify Workspace Access

bash
# Check accessible workspaces
curl -s "${KUBESPHERE_API}/kapis/tenant.kubesphere.io/v1beta1/workspaces" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.items[].metadata.name'

# Verify specific workspace
curl -s "${KUBESPHERE_API}/kapis/tenant.kubesphere.io/v1beta1/workspaces/stone" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.metadata.name'

GitOps Application Deployment

Create GitOps Application via API

Tenants can deploy applications using KubeSphere GitOps without accessing the ArgoCD namespace:

bash
# Create GitOps Application
curl -s -X POST "${KUBESPHERE_API}/kapis/gitops.kubesphere.io/v1alpha1/namespaces/demo-project/applications" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "gitops.kubesphere.io/v1alpha1",
    "kind": "Application",
    "metadata": {
      "name": "guestbook",
      "namespace": "demo-project"
    },
    "spec": {
      "argoApp": {
        "spec": {
          "project": "default",
          "source": {
            "repoURL": "https://github.com/stoneshi-yunify/argocd-example-apps",
            "targetRevision": "HEAD",
            "path": "guestbook"
          },
          "destination": {
            "server": "https://kubernetes.default.svc",
            "namespace": "demo-project"
          },
          "syncPolicy": {
            "automated": {
              "prune": true,
              "selfHeal": true
            },
            "syncOptions": [
              "CreateNamespace=true"
            ]
          }
        }
      }
    }
  }' | jq -r '.metadata.name'

# Expected output: guestbook

How It Works

  1. Tenant creates Application (gitops.kubesphere.io/v1alpha1) in their namespace
  2. KubeSphere automatically creates corresponding ArgoCD Application in argocd namespace
  3. ArgoCD controller syncs the application to tenant's namespace
  4. Tenant cannot access ArgoCD namespace directly - all operations via KubeSphere API

Verify Application Deployment

Method 1: Check Status Labels (Recommended for Tenants)

The Application resource includes status labels that indicate the current health and sync status:

bash
# Get Application and check status labels
curl -s "${KUBESPHERE_API}/clusters/member-1/kapis/gitops.kubesphere.io/v1alpha1/namespaces/demo-project/applications/guestbook" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '{
  name: .metadata.name,
  health: .metadata.labels["gitops.kubesphere.io/health-status"],
  sync: .metadata.labels["gitops.kubesphere.io/sync-status"],
  argocdApp: .metadata.labels["gitops.kubesphere.io/argocd-application"]
}'

# Expected output when synced and healthy:
# {
#   "name": "guestbook",
#   "health": "Healthy",
#   "sync": "Synced",
#   "argocdApp": "guestbook"
# }

Status Values:

LabelValuesDescription
gitops.kubesphere.io/health-statusHealthy, Progressing, Degraded, Missing, UnknownResource health state
gitops.kubesphere.io/sync-statusSynced, OutOfSyncGit repository sync state

Method 2: Check Detailed Status in .status.argoApp

For more detailed information, parse the .status.argoApp field (JSON string):

bash
# Get detailed sync and health information
curl -s "${KUBESPHERE_API}/clusters/member-1/kapis/gitops.kubesphere.io/v1alpha1/namespaces/demo-project/applications/guestbook" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.status.argoApp' | jq -r '{
  syncStatus: .sync.status,
  healthStatus: .health.status,
  revision: .sync.revision,
  resources: [.resources[] | {kind: .kind, name: .name, status: .status, health: .health.status}],
  images: .summary.images
}'

# Expected output:
# {
#   "syncStatus": "Synced",
#   "healthStatus": "Healthy",
#   "revision": "f946a1c393d50a460cc44944a476971fe13961f4",
#   "resources": [
#     {"kind": "Service", "name": "guestbook-ui", "status": "Synced", "health": "Healthy"},
#     {"kind": "Deployment", "name": "guestbook-ui", "status": "Synced", "health": "Healthy"}
#   ],
#   "images": ["gcr.io/google-samples/gb-frontend:v5"]
# }

Method 3: Check Operation State

For troubleshooting sync operations:

bash
# Get operation state and sync result
curl -s "${KUBESPHERE_API}/clusters/member-1/kapis/gitops.kubesphere.io/v1alpha1/namespaces/demo-project/applications/guestbook" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.status.argoApp' | jq -r '.operationState | {
  phase: .phase,
  message: .message,
  startedAt: .startedAt,
  finishedAt: .finishedAt
}'

# Expected output on success:
# {
#   "phase": "Succeeded",
#   "message": "successfully synced (all tasks run)",
#   "startedAt": "2026-03-27T09:09:12Z",
#   "finishedAt": "2026-03-27T09:09:15Z"
# }

Understanding Destination Cluster

When spec.argoApp.spec.destination.server is https://kubernetes.default.svc and destination.name is empty or in-cluster, the Application deploys to the cluster specified in the API path:

API PathDestination Cluster
/kapis/gitops.kubesphere.io/v1alpha1/namespaces/{ns}/applicationsHost cluster
/clusters/member-1/kapis/gitops.kubesphere.io/v1alpha1/namespaces/{ns}/applicationsmember-1 cluster
/clusters/member-2/kapis/gitops.kubesphere.io/v1alpha1/namespaces/{ns}/applicationsmember-2 cluster

Important for Tenants:

Since tenants may not have permissions to directly query the destination namespace (due to RBAC), always verify deployment via the Application status rather than trying to access deployed resources directly:

bash
# ✅ CORRECT: Check Application status (tenant has permissions)
curl -s "${KUBESPHERE_API}/clusters/member-1/kapis/gitops.kubesphere.io/v1alpha1/namespaces/demo-project/applications/guestbook" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.metadata.labels["gitops.kubesphere.io/sync-status"]'
# Output: "Synced"

# ❌ INCORRECT: Direct namespace access may fail for tenants
curl -s "${KUBESPHERE_API}/clusters/member-1/api/v1/namespaces/demo-project/pods" \
  -H "Authorization: Bearer ${API_TOKEN}"
# May return 403 Forbidden

Tenant Limitations (Important)

ActionTenant CanNotes
Create GitOps App✅ YesVia KubeSphere API
Modify ArgoCD Config❌ NoCannot access argocd namespace
Add App Namespace to ArgoCD❌ NoRequires admin to update application.namespaces
View ArgoCD UI❌ NoNo direct ArgoCD access
View Deployed Resources✅ YesIn own namespace

Complete Tenant Workflow

Step-by-Step: Build and Retrieve Artifacts as Tenant

bash
#!/bin/bash
set -e

# Configuration
export KUBESPHERE_API="https://kubesphere-api.example.com"
export API_TOKEN="<tenant-token>"
export DEVOPS_PROJECT="demo-project"
export PIPELINE_NAME="my-tenant-pipeline"

# 1. List available pipelines
echo "=== Available Pipelines ==="
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.items[].metadata.name'

# 2. Trigger pipeline run
echo "=== Triggering Pipeline ==="
RUN_RESPONSE=$(curl -s -X POST "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"parameters": [{"name": "BRANCH", "value": "main"}]}')

RUN_ID=$(echo $RUN_RESPONSE | jq -r '.metadata.name')
echo "Run ID: $RUN_ID"

# 3. Wait for completion
echo "=== Waiting for Build ==="
while true; do
  STATUS=$(curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}" \
    -H "Authorization: Bearer ${API_TOKEN}" | jq -r '.status.phase')
  echo "Status: $STATUS"
  [[ "$STATUS" == "Succeeded" || "$STATUS" == "Failed" ]] && break
  sleep 10
done

# 4. Get logs
echo "=== Console Log ==="
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}/log" \
  -H "Authorization: Bearer ${API_TOKEN}" | tail -50

# 5. Download artifacts
echo "=== Downloading Artifacts ==="
ARTIFACTS=$(curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines/${PIPELINE_NAME}/runs/${RUN_ID}/artifacts" \
  -H "Authorization: Bearer ${API_TOKEN}")

echo "$ARTIFACTS" | jq -c '.[]' | while read artifact; do
  NAME=$(echo $artifact | jq -r '.name')
  URL=$(echo $artifact | jq -r '.url')
  echo "Downloading: $NAME"
  curl -s "${KUBESPHERE_API}${URL}" -H "Authorization: Bearer ${API_TOKEN}" -o "/tmp/${NAME}"
  ls -lh "/tmp/${NAME}"
done

echo "=== Done ==="

Tenant Limitations & Workarounds

LimitationTenant ImpactWorkaround
No Jenkins tokenCannot use Jenkins API directlyUse KubeSphere /kapis/ endpoints
No kubesphere-devops-system accessCannot view Jenkins master logsView PipelineRun status via API
No agent pod accessCannot exec into agentsArtifacts via API or pipeline steps
Limited logsLogs may be truncatedStore logs in artifacts or external systems
No webhook managementCannot configure webhooks directlyUse KubeSphere UI or request admin

Common Errors and Fixes

Error: 403 Forbidden

bash
# Cause: Token expired or insufficient permissions
# Fix: Refresh token or check RBAC

curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/pipelines" \
  -H "Authorization: Bearer ${API_TOKEN}" -v 2>&1 | grep "HTTP/"
# Should be: HTTP/2 200

Error: Resource not found

bash
# Cause: Wrong namespace or resource doesn't exist
# Fix: Verify namespace and resource names

kubectl get pipelines -n ${DEVOPS_PROJECT}
kubectl auth can-i get pipelines -n ${DEVOPS_PROJECT}

Error: No logs available

bash
# Cause: Run not complete or logs not persisted
# Fix: Wait for completion, check if run succeeded

kubectl get pipelinerun ${RUN_ID} -n ${DEVOPS_PROJECT} -o jsonpath='{.status.phase}'

Workspace API Returns Empty (Namespace Label Mismatch)

Symptom:

bash
# Query workspace API returns 0 items even though DevOpsProject exists
curl -s "${KUBESPHERE_API}/clusters/host/kapis/devops.kubesphere.io/v1alpha3/workspaces/demo/namespaces" \
  -H "Authorization: Bearer ${API_TOKEN}"
# Output: {"items": null, "totalItems": 0}

# But direct query works
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha3/namespaces/demo-project/pipelines" \
  -H "Authorization: Bearer ${API_TOKEN}"
# Output: Returns pipelines successfully

Root Cause: The workspace-scoped API filters namespaces by the label kubesphere.io/workspace. If the namespace label doesn't match the workspace, it won't be returned, even if the DevOpsProject CR has the correct label.

Check Labels:

bash
# Check DevOpsProject label (usually correct)
kubectl get devopsproject demo-project -o jsonpath='{.metadata.labels.kubesphere\.io/workspace}'
# Output: demo

# Check namespace label (may be empty or wrong)
kubectl get ns demo-project -o jsonpath='{.metadata.labels.kubesphere\.io/workspace}'
# Output: "" (EMPTY - this is the problem!)

Fix (Admin Required):

bash
# Update namespace label to match workspace
kubectl label ns demo-project kubesphere.io/workspace=demo --overwrite

# Verify fix
kubectl get ns demo-project -o jsonpath='{.metadata.labels.kubesphere\.io/workspace}'
# Output: demo

# Now workspace API returns the namespace
curl -s "${KUBESPHERE_API}/clusters/host/kapis/devops.kubesphere.io/v1alpha3/workspaces/demo/namespaces" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq '.totalItems'
# Output: 1

Why This Happens:

  • DevOpsProject CR and namespace are separate resources
  • DevOpsProject controller should sync the workspace label to the namespace
  • If the controller missed it or the label was removed, the API filtering breaks
  • Workspace-scoped APIs use namespace labels, not DevOpsProject labels

References

Frequently asked questions

What does the Kubesphere Devops Tenant AI skill do?

Use when operating KubeSphere DevOps as a namespace-scoped tenant with limited permissions, without cluster-admin access, or when accessing DevOps through KubeSphere APIs only

Why use Kubesphere Devops Tenant on TypingMind?

Because you install it once and use it with any model. Kubesphere Devops Tenant is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Kubesphere Devops Tenant in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/kubesphere/kubesphere/tree/master/skills/kubesphere-devops-tenant. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Kubesphere Devops Tenant?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Kubesphere Devops Tenant?

As many as you like. As long as a model supports skills, you can use Kubesphere Devops Tenant with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Kubesphere Devops Tenant AI skill free?

It is published on GitHub by kubesphere. Check the repository for licensing terms. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇