Best for
- Operating as a project admin/operator (not cluster admin)
- Working within tenant namespace boundaries
- No access to Jenkins secrets in kubesphere-devops-system
kubesphere/kubesphere/skills/kubesphere-devops-tenant/SKILL.md
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
Decision brief
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
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/kubesphere/kubesphere --skill "skills/kubesphere-devops-tenant"Inspect the Agent Skill "kubesphere-devops-tenant" from https://github.com/kubesphere/kubesphere/blob/04a29b5c601470fa6bc2f2e92358dcb802a0d414/skills/kubesphere-devops-tenant/SKILL.md at commit 04a29b5c601470fa6bc2f2e92358dcb802a0d414. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
bash export KUBESPHEREAPI="http://kubesphere-apiserver.kubesphere-system.svc:80" export USERNAME="stoneshi" export PASSWORD='P@88w0rd'
Review the “Step 2: Access Workspace Resources” section in the pinned source before continuing.
bash export DEVOPSPROJECT="stone-devops" Must be in "stone" workspace
bash export PIPELINENAME="stone-tenant-pipeline"
Review the “Step 5: Get Logs” section in the pinned source before continuing.
Permission review
The documentation includes network, browsing, or remote request actions.
export KUBESPHERE_API="https://kubesphere-api.example.com"The documentation includes sending, uploading, or posting data to a remote service.
export API_TOKEN=$(curl -s -X POST "${KUBESPHERE_API}/oauth/token" \The documentation includes network, browsing, or remote request actions.
export API_TOKEN=$(curl -s -X POST "${KUBESPHERE_API}/oauth/token" \The documentation includes sending, uploading, or posting data to a remote service.
TOKEN_RESPONSE=$(curl -s -X POST "${KUBESPHERE_URL}/oauth/token" \The documentation asks the agent to run terminal commands or scripts.
sh "echo 'Hello from tenant pipeline'"The documentation asks the agent to read local files, directories, or repositories.
GitHub Personal Access Token (PAT) with repo accessThe documentation asks the agent to run terminal commands or scripts.
git ls-remote --symref -- https://github.com/org/repo.gitEvidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 84/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 17,017 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
This guide covers DevOps operations for namespace-scoped tenants who:
kubesphere-devops-system (Jenkins secrets, tokens)/kapis/devops.kubesphere.io/) for all operationsCritical Distinction: DevOps projects are namespaces, not DevOpsProject CRs. To list accessible DevOps projects:
# 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
kubesphere-devops-system| Capability | Tenant (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 | ❌ No | ✅ kubesphere-devops-system |
| Direct Jenkins API | ❌ No | ✅ Full access |
| View Jenkins Console | ❌ No | ✅ Via NodePort |
| KubeSphere API | ✅ /kapis/ | ✅ /kapis/ |
Tenants authenticate via KubeSphere's OAuth, not Jenkins. See kubesphere-core for complete OAuth authentication details.
# 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:
client_id=kubesphere and client_secret=kubesphereSee kubesphere-core for complete OAuth authentication details including token refresh and common use cases.
Tenants authenticate via KubeSphere's OAuth, not Jenkins:
# 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}..."
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}"
Here's a verified workflow using tenant credentials (stoneshi / P@88w0rd):
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')"
# 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..."}
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}'
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}
# 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
| Aspect | Tenant Behavior |
|---|---|
| Authentication | OAuth with client_id/client_secret = "kubesphere" |
| Token Expiry | 7200 seconds (2 hours) |
| API Version | v1alpha3 for pipelines, v1alpha2 for runs |
| Response Format | Blue Ocean JSON (not Kubernetes resources) |
| Status Fields | .state (QUEUED/RUNNING/FINISHED), .result (SUCCESS/FAILURE) |
| Namespace Isolation | 403 Forbidden for other workspaces |
| Logs Access | ✅ Available via KubeSphere API |
| Artifacts | ✅ Available via /artifacts endpoint |
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'
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 .
Regular Pipeline:
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:
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"
}
}
}'
⚠️ 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):
- Ask if they want to use an existing credential or create a new one
- Create a DevOps credential (
basic-authtype with GitHub PAT) - see Step 1 below- Reference the credential in
git_source.credential_idwhen creating the pipelineIf 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:
devopstestc2nj7)Step 1: Create Credential for GitHub Access
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
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
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:
spec.provider (e.g., github) and spec.secret fieldskubesphere.io/creator annotation when created by tenant⚠️ API Version Notice: The
/kapis/devops.kubesphere.io/v1alpha2/APIs are deprecated. Always preferv1alpha3APIs when available.
For Multi-Branch Pipelines - Three-Step Procedure:
Step 1: List Available Branches
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
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:
v1alpha3 endpoint with ?branch=${BRANCH} query parameterException to v1alpha3 rule: Repository scanning uses v1alpha2 API. This endpoint is not available in v1alpha3.
When to use:
Step 1: Trigger Scan (v1alpha2)
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)
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:
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
# 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}'
# 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: These v1alpha2 endpoints return Blue Ocean format and are deprecated. Use v1alpha3 APIs shown above.
# 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}'
# 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
# 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
Tenant Method (via KubeSphere API):
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:
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!"
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 via KubeSphere API:
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):
# 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
curl -s "${KUBESPHERE_API}/kapis/devops.kubesphere.io/v1alpha2/namespaces/${DEVOPS_PROJECT}/credentials" \
-H "Authorization: Bearer ${API_TOKEN}" | jq '.items[].metadata.name'
SSH Key:
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:
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"
}
}'
KubeSphere supports managing DevOps resources across multiple clusters. Use the /clusters/{cluster-name}/ prefix to forward API requests to specific member clusters.
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:
# List DevOps project namespaces (NOT devopsprojects CRs)
GET /clusters/{cluster}/kapis/devops.kubesphere.io/v1alpha3/workspaces/{workspace}/namespaces
This endpoint returns namespaces that:
devops.kubesphere.io/managed=true labelkubesphere.io/workspace={workspace} labelWhen users refer to DevOps projects, they may use either a shortname or fullname:
| Name Type | Example | Source | Description |
|---|---|---|---|
| Shortname | devopstest | DevOpsProject CR .metadata.generateName | User-friendly display name |
| Fullname | devopstestc2nj7 | DevOpsProject CR .metadata.name | Actual namespace name |
Key Points:
.metadata.name = Namespace's .metadata.name.metadata.generateName and is used for display purposesResolving Ambiguity: When a user provides a name that could match multiple projects:
# 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:
| Endpoint Type | Path Pattern | Returns | Use Case | Tenant Access |
|---|---|---|---|---|
| KubeSphere API (workspace-scoped) | /clusters/{cluster}/kapis/devops.kubesphere.io/v1alpha3/workspaces/{workspace}/namespaces | Namespaces (DevOps projects) | List DevOps project namespaces tenant can access | ✅ Tenant accessible |
| KubeSphere API (namespace-scoped) | /clusters/{cluster}/kapis/devops.kubesphere.io/v1alpha3/namespaces/{namespace}/pipelines | Pipelines | Pipeline operations | ✅ Tenant accessible |
| Kubernetes API (cluster-scoped) | /clusters/{cluster}/apis/devops.kubesphere.io/v1alpha3/devopsprojects | DevOpsProject CRs | Direct CR access | ❌ Admin 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.
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)
# ❌ 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",...}]}
#!/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
Tenants can query DevOps projects (which are namespaces) within their authorized workspaces:
# 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.kubesphere.io/managed=true label/namespaces endpoint/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.# 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'
Tenants can deploy applications using KubeSphere GitOps without accessing the ArgoCD namespace:
# 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
Application (gitops.kubesphere.io/v1alpha1) in their namespaceargocd namespaceMethod 1: Check Status Labels (Recommended for Tenants)
The Application resource includes status labels that indicate the current health and sync status:
# 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:
| Label | Values | Description |
|---|---|---|
gitops.kubesphere.io/health-status | Healthy, Progressing, Degraded, Missing, Unknown | Resource health state |
gitops.kubesphere.io/sync-status | Synced, OutOfSync | Git repository sync state |
Method 2: Check Detailed Status in .status.argoApp
For more detailed information, parse the .status.argoApp field (JSON string):
# 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:
# 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 Path | Destination Cluster |
|---|---|
/kapis/gitops.kubesphere.io/v1alpha1/namespaces/{ns}/applications | Host cluster |
/clusters/member-1/kapis/gitops.kubesphere.io/v1alpha1/namespaces/{ns}/applications | member-1 cluster |
/clusters/member-2/kapis/gitops.kubesphere.io/v1alpha1/namespaces/{ns}/applications | member-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:
# ✅ 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
| Action | Tenant Can | Notes |
|---|---|---|
| Create GitOps App | ✅ Yes | Via KubeSphere API |
| Modify ArgoCD Config | ❌ No | Cannot access argocd namespace |
| Add App Namespace to ArgoCD | ❌ No | Requires admin to update application.namespaces |
| View ArgoCD UI | ❌ No | No direct ArgoCD access |
| View Deployed Resources | ✅ Yes | In own namespace |
#!/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 ==="
| Limitation | Tenant Impact | Workaround |
|---|---|---|
| No Jenkins token | Cannot use Jenkins API directly | Use KubeSphere /kapis/ endpoints |
| No kubesphere-devops-system access | Cannot view Jenkins master logs | View PipelineRun status via API |
| No agent pod access | Cannot exec into agents | Artifacts via API or pipeline steps |
| Limited logs | Logs may be truncated | Store logs in artifacts or external systems |
| No webhook management | Cannot configure webhooks directly | Use KubeSphere UI or request admin |
Error: 403 Forbidden
# 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
# 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
# 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}'
Symptom:
# 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:
# 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):
# 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:
Alternatives
alirezarezvani/claude-skills
App Store Optimization (ASO) toolkit for researching keywords, analyzing competitor rankings, generating metadata suggestions, and improving app visibility on Apple App Store and Google Play Store. Use when the user asks about ASO, app store rankings, app metadata, app titles and descriptions, app store listings, app visibility, or mobile app marketing on iOS or Android. Supports keyword research and scoring, competitor keyword analysis, metadata optimization, A/B test planning, launch checklist
dotnet/skills
Migrates .NET test projects from VSTest to Microsoft.Testing.Platform (MTP). Use when user asks to "migrate to MTP", "switch from VSTest", "enable Microsoft.Testing.Platform", "use MTP runner", set OutputType=Exe only for test projects in Directory.Build.props, or mentions EnableMSTestRunner, EnableNUnitRunner, or UseMicrosoftTestingPlatformRunner. USE FOR: MTP behavioral differences vs VSTest (exit code 8, zero tests discovered, --ignore-exit-code, TESTINGPLATFORM_EXITCODE_IGNORE); centralizing
HKUDS/Vibe-Trading
Create, modify, and optimize quantitative trading strategies, then backtest and evaluate them.
K-Dense-AI/scientific-agent-skills
Distributed computing for larger-than-RAM pandas/NumPy workflows. Use when you need to scale existing pandas/NumPy code beyond memory or across clusters. Best for parallel file processing, distributed ML, integration with existing pandas code. For out-of-core analytics on single machine use vaex; for in-memory speed use polars.